【问题标题】:F# pattern matching with optional list of tuplesF# 模式匹配可选的元组列表
【发布时间】:2020-09-25 21:22:52
【问题描述】:

我正在尝试将模式匹配用于可选的元组列表,但尽管尝试了我能想到的一切,但我无法编写详尽的匹配表达式。

我很难理解为什么 F# 编译器坚持我在以下示例中的模式并不详尽。

module Mapper.PatternMatchingOddity

type A = A of string
type B = B of string

type ProblemType = ProblemType of (A * B) list option

//Incomplete pattern matches on this expression. Some ([_;_]) may indicate a case...
let matchProblem = function
    |Some [(x:A,y:B)] -> []
    |Some ([_,_]) -> [] //rider says this rule will never be matched
    |None -> []

//same as before    
let matchProblem1 = function
    |Some [_,_] -> []
    |Some [] -> []
    //|Some _ -> []//this removes the warning but what is the case not covered by the previous two?
    |None -> []    

let matchProblem2 (input:ProblemType) =
    match input with //same as before
    |ProblemType (Some [(x:A,y:B)]) -> []
    |ProblemType None  -> []    

我如何编写详尽的匹配以及我在上面遗漏了什么?您能否举一个输入示例,该输入将被接受为这些函数的有效参数并跳过模式?

【问题讨论】:

    标签: f# pattern-matching


    【解决方案1】:

    好问题!我认为许多开始使用 F# 的人都在努力解决列表、选项和元组的交互方式。让我首先说:编译器是正确的。简短的回答是:您只匹配单例列表。让我试着更深入地解释一下。

    本质上,您的类型是('a * 'b) list option。在您的情况下,'a'b 本身就是使用字符串区分的单一案例。让我们稍微简化一下,看看如果我们孤立地查看您的类型的每个部分会发生什么(您可能已经知道这一点,但将其放在上下文中可能会有所帮助):

    1. 首先,您的类型是选项。这有两个值,NoneSome 'a。要匹配一个选项,您可以执行类似

      的操作
      match o with 
      | Some value -> value 
      | None -> failwith "nothing"`
      
    2. 接下来,您的类型是一个列表。列表中的项目用分号; 分隔。一个空列表是[],一个单例列表(一个包含一个项目)是[x],多个项目是[x;y...]。要将某些内容添加到列表的开头,请使用 ::。列表是一种特殊类型的可区分联合,匹配它们的语法模仿了列表构造的语法:

      match myList with
      | [] -> "empty"
      | [x] -> printfn "one item: %A" x
      | [x; y] -> printfn "two items: %A, %A" x y
      | x::rest -> printfn "more items, first one: %A" x
      
    3. 第三,您的列表类型本身就是一个元组类型。要解构或匹配元组类型,您可以使用逗号 ,,与 match (x, y) with 1, 2 -> "it's 1 and 2!" ... 一样。

    4. 结合所有这些,我们必须匹配一个选项(外部)然后列表(中间)然后是元组。 Some [] 表示空列表,None 表示没有列表,Some [a, b] 表示单例列表,Some (a,b)::rest 表示包含一个或多个项目的列表。


    现在我们已经了解了理论,让我们看看我们是否可以处理您的代码。首先让我们看一下警告信息:

    此表达式的模式匹配不完整。 Some ([_;_]) 可能表示一个案例...

    这是正确的,您的代码中的项目由表示元组的, 分隔,并且消息显示Some [something; something](下划线表示“任何东西”),这是两个项目的列表。但是添加它对您没有多大帮助,因为列表仍然可能比 2 长。

    骑手说这条规则永远不会匹配

    Rider 是正确的(它调用了下面的 FSC 编译器服务)。该行上方的规则是Some [(x:A,y:B)](此处不需要:A:B),它匹配任何带有元组的Some 单例数组Some [_,_] 的作用相同,只是它不捕获变量中的值。

    这消除了警告,但前两个没有涵盖的情况是什么?

    它删除了警告,因为Some _ 意味着Some 带有任何东西,因为_ 意味着:它是任何东西的占位符。在这种情况下,它匹配空列表、2 项列表、3 项列表和 n 项列表(在该示例中,您唯一匹配的是 1 项列表)。

    你能举个例子来说明一个可以被接受为有效参数的输入吗

    是的。您不匹配的有效输入是Some [](空列表)、Some [A "a", B "x"; A "2", B "2"](两个项目的列表)等。


    让我们举第一个例子。你有这个:

    let matchProblem = function
        |Some [(x:A,y:B)] -> []  // matching a singleton list
        |Some ([_,_]) -> []   // matches a singleton list (will never match, see before)
        |None -> []  // matches None
    

    这是您(可能)需要的:

    let notAProblemAnymore = function
        // first match all the 'Some' matches:
        | Some [] -> "empty"  // an empty list
        | Some [x,y] -> "singleton"  // a list with one item that is a tuple
        | Some [_,a;_,b] -> "2-item list"  // a list with two tuples, ignoring the first half of each tuple
        | Some ((x,y)::rest) -> "multi-item list" 
               // a list with at least one item, and 'rest' as the 
               // remaining list, which can be empty (but won't, 
               // here it has at least three items because of the previous matches)
        | None -> "Not a list at all" // matching 'None' for absence of a list
    

    总结一下:您正在匹配一个只有一个项目的列表,编译器抱怨您错过了其他长度的列表(空列表和具有多个项目的列表)。

    通常没有必要将option 与列表一起使用,因为空列表已经意味着没有数据。因此,每当您发现自己编写 option list 类型时,请考虑是否仅使用 list 就足够了。这将使匹配更容易。

    【讨论】:

    • 太棒了。我希望我可以多次投票。我把这个警告读了一百万遍,但我的大脑坚持解释';'作为元组中的分隔符('*')您的解释使我所缺少的观点非常清楚。顺便说一句,我更改了代码,编译器对“空”、“单项”和“多项列表”情况感到满意,我认为我们的“2项列表”情况与“单项”情况完全相同,只是它允许访问到元组元素。您关于 list 是 DU 的一个特例的评论非常有趣,谢谢。
    • @grumpyrodr 很高兴我能提供服务!如果您的用例只需要零个或两个项目,永远不会更多,永远不会,您应该选择('a * 'b) option 样式类型。固定大小的列表最多包含 7 个左右的项目,最好将其结构化为元组。如果项目可以是可变的,请使用列表。
    • 谢谢。我的用例是零个或多个正好两个项目的元组。问题中的代码是从真实代码简化而来的。我的用例是映射列表,因此是元组列表。我同意:选项在这里是多余的。一个空列表为我提供了我需要的语义,但我想在删除它之前确保我了解选项的使用是否与我的问题相关;)
    【解决方案2】:

    你正在苦苦挣扎,因为你的榜样太“榜样”了。

    让我们将您的示例转换为更有意义的示例:检查输入,以便

    • 如果是 none 则打印“nothing”,否则:
    • 如果它的元素为零,则打印“空”
    • 如果它只有一个元素,则打印“on one element: ...”
    • 如果它有两个元素,则打印“我们有两个元素:...”
    • 如果它包含三个元素,则打印“存在三个元素:...”
    • 如果它包含三个以上的元素,则打印“哦,伙计,第一个元素是……,第二个元素是……,第三个元素是……,还有 N 个元素”

    现在您可以看到您的代码仅涵盖前 3 种情况。所以 F# 编译器是正确的。

    重写代码:

    let matchProblem (ProblemType input) =
        match input with
        | None -> printfn "nothing"
        | Some [] -> ...
        | Some [(x, y)] -> ...
        | Some [(x1, y1); (x2, y2)] -> ...
        | Some [(x1, y1); (x2, y2); (x3, y3)] -> ...
        | Some (x1, y1) :: (x2, y2) :: (x3, y3) :: rest -> // access rest.Length to print the number of more elements
    

    请注意,我在参数 ProblemType input 上使用了模式匹配,以便我可以方便地提取输入。这使得后面的模式更简单。

    就我个人而言,当我学习 F# 时,我并不了解许多功能/语法,直到我将它们用于生产代码中。

    【讨论】:

      猜你喜欢
      • 2015-01-25
      • 1970-01-01
      • 2019-10-12
      • 1970-01-01
      • 1970-01-01
      • 2012-07-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多