【问题标题】:F# list of discriminated union sub-typesF# 可区分联合子类型列表
【发布时间】:2020-11-30 20:00:58
【问题描述】:

我想向用户展示一个“FParsec 解析器”-加上-“测试数据”列表,他们可以从中交互式地选择并查看解析器在提供的文本上运行的结果。

具体来说,我正在尝试将我的解析器测试收集到这种类型的记录列表中:

type FUNCTION_TEST = 
    | XELEMENT_MATCH of label : string * func : Parser<(int * string), unit> * data : string
    | XKEYVALUE_MATCH of label : string * func : Parser<(string * string), unit> * data : string

let testable_parsers = [( "xElement", xElement, xElement_text3)
                        ( "xKeyValue", xKeyValue, xKeyValue_text)]

xKeyValue 上面抛出错误...

val xKeyValue: Parser<(string * string), unit>

Full name CustomParser.xKeyValue

FS0001:the type '(int * string)' does not match the type 'string * string'

我希望用户看到并选择一个label 字符串并查看运行解析器的结果。

我了解解析器 xElement : Parser&lt;int * string, unit&gt; 与解析器 xKeyValue : Parser&lt;(string * string), unit&gt; 不匹配。这两种子类型都是 FUNCTION_TEST 可区分联合的一部分但是我不能将解析器放在同一个列表中,因为它们的子类型(XELEMENT_MATCHXKEYVALUE_MATCH)不同意。

我想在有区别的联合上使用match ... with 来处理这个问题。

我是 F# 和 FParsec 的新手,没有优雅的想法。我必须用do!printfs 对菜单进行硬编码吗?

经验丰富的 F# 和 FParsec 开发人员如何允许用户从不同类型的选项菜单中进行选择?

【问题讨论】:

    标签: f# discriminated-union fparsec


    【解决方案1】:

    我认为最好的方法是重组您的数据,以便您将所有常见的东西(标签和数据)以及表示函数的值一起保存在记录中 - 然后可以是一个有区别的联合与一个案例每种类型:

    type Function = 
      | Element of Parser<(int * string), unit>
      | KeyValue of Parser<(string * string), unit>
    
    type Test = 
      { Label : string
        Data : string 
        Function : Function }
    

    现在您可以创建一个Test 值列表,您可以轻松地对其进行迭代以获取所有标签和数据:

    let testableParsers = 
      [ { Label = "xElement"; Function = Element(xElement); Data = xElement_text3 }
        { Label = "xKeyValue"; Function = KeyValue(xKeyValue); Datta = xKeyValue_text } ]
    

    当您想要运行特定的Function 时,您仍然需要使用模式匹配,因为这将不得不处理您得到的不同值,但您不需要在其他地方进行模式匹配。

    【讨论】:

    • 感谢您的回答托马斯。为什么 F# 要求我显式构造用户定义的类型? F# 是否无法识别xElement,即Parser&lt;(int * string), unit&gt; 类型的IS,与XELEMENT_MATCH 的类型相同,并且只是自动/隐式构造正确的用户定义类型?当 F# 隐式调用 intstring 等预定义类型的构造函数时,为什么不这样做呢?
    • F# 能否识别与用户定义类型匹配的结构并隐式构造它们?
    • 我不太确定您的建议是什么 - 但我想答案是 F# 会引导您采用某种思维方式 - 您可以自己定义所需的类型。
    【解决方案2】:

    我对@9​​87654321@ 类型有点困惑。看起来您从未使用过该类型或其任何构造函数,如果是这样,您为什么首先定义该类型?

    在考虑了您可能拥有的可能动机之后,您可能希望testable_parsers 列表包含FUNCTION_TEST 类型的值,以便您以后可以匹配它们?

    如果是这样,那么你所拥有的并不是你想要的:你构建的列表包含三个值的元组。

    为了构造FUNCTION_TEST类型的值,你需要应用它的构造函数:

    testableParsers = [
        XELEMENT_MATCH ("xElement", xElement, xElement_text3)
        XKEYVALUE_MATCH ("xKeyValue", xKeyValue, xKeyValue_text)
    ]
    

    但是,如果您的唯一目的是向用户提供选择,那么我什至不会费心使用特殊类型来表示选项。您可以将标签与 functions 配对,以便在用户选择标签时调用:

    testableParsers = [
        "xElement", fun() -> runParser xElement xElement_text3
        "xKeyValue", fun() -> runParser xKeyValue xKeyValue_text
    ]
    

    这样您也不必对FUNCTION_TEST 的值执行match,只需调用该函数即可。

    【讨论】:

    • 感谢 Fyodor 的回答。我保持代码片段简短,希望能节省您的时间。是的,我试图在 FUNCTION_TEST 区分联合子类型上 match...with。因此,将我所有的可测试解析器放在该父类型下,以便它们可以共存于一个列表中。如果 F# 看到类似 XELEMENT_MATCH 的结构,我认为 F# 会执行“鸭子打字”并自动构造 XELEMENT_MATCHXKEYVALUE_MATCH 是错误的吗?当 F# 看到某种类型的东西时,它不会自动构造一个结构化类型吗?
    猜你喜欢
    • 1970-01-01
    • 2013-08-04
    • 1970-01-01
    • 1970-01-01
    • 2020-08-22
    • 2016-03-11
    • 2011-11-19
    • 1970-01-01
    • 2014-09-13
    相关资源
    最近更新 更多