F# 模式与可选的元组列表匹配



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

我很难理解为什么 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# 开始的人都在努力解决列表、选项和元组的交互方式。首先我要说:编译器是正确的。简短的回答是:您只匹配单例列表。让我试着更深入地解释一下。

你的类型是('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 []和没有列表的NoneSome [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是否就足够了。这将使匹配更容易。

你很挣扎,因为你的例子太"例子"。

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

  • 如果为 none,则打印"无",否则:
  • 如果它有零元素,则打印"空">
  • 如果它只有一个元素,则打印"ony 一个元素:...">
  • 如果它有两个元素,则打印"我们有两个元素:...">
  • 如果它有三个元素,则打印"有三个元素:...">
  • 如果它有三个以上的元素,则打印"哦,伙计,第一个元素是...,第二个元素是...,第三个元素是...,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# 时,直到我在生产代码中使用它们,我才了解许多功能/语法。

最新更新