F# 列表模式匹配限制还是只是写得不好?

ras*_*asx 2 f# pattern-matching

鉴于一些CreditScoreInput

type CreditScoreInput = { id: string; score: string; years: int }

let input = [
    { id = "CUSTOMER001"; score = "Medium"; years = 1 }
    { id = "CUSTOMER001"; score = "Medium"; years = 1 }
    { id = "CUSTOMER002"; score = "Medium"; years = 10 }
    { id = "CUSTOMER003"; score = "Bad"; years = 0 }
    { id = "CUSTOMER003"; score = "Bad"; years = 0 }
    { id = "CUSTOMER003"; score = "Bad"; years = 0 }
    { id = "CUSTOMER004"; score = "Good"; years = 0 }
    { id = "CUSTOMER005"; score = "Good"; years = 10 }
]
Run Code Online (Sandbox Code Playgroud)

我的功能validateDuplicates是寻找重复项:

let validate list =
    match list with
    | [] -> failwith "No customers supplied!"
    | _ -> list

let validateDuplicates (group:string * list<CreditScoreInput>) =
    match group with
    | (id, g) when g.Length = 1 -> printf $"No duplicates for {id} is OK.\n"
    | (id, [input1; input2]) -> printf $"Two duplicates for {id} is OK.\n"
    | (id, g) when g.Length > 2 -> printf $"More than two duplicates for {id} is NOT OK.\n"

    true

input
|> validate
|> List.groupBy (fun i -> i.id)
|> List.forall (fun i -> i |> validateDuplicates)
|> ignore
Run Code Online (Sandbox Code Playgroud)

在里面validateDuplicates我注意到下面有一点弯曲group,导致警告:

该表达式的模式匹配不完整。例如,该值(_,[_;_;_])可以指示未被模式覆盖的情况。但是,带有子句的模式规则when可能会成功匹配该值。

有没有办法可以很好地使用编译器来避免此警告?

更新

我不确定我是否应该在这里这样做,但以下是我根据出色的指导进行的更改:

let validateDuplicates (group:string * list<CreditScoreInput>) =
    match group with
    | (id, [_]) -> printf $"No duplicates for {id} is OK.\n"
    | (id, [_; _]) -> printf $"Two duplicates for {id} is OK.\n"
    | (id, g) -> printf $"More than two duplicates for {id} is NOT OK.\n"

input
|> validate
|> List.groupBy (fun i -> i.id)
|> List.iter (fun i -> i |> validateDuplicates)
Run Code Online (Sandbox Code Playgroud)

bri*_*rns 5

只要去掉最后一个when子句,因为你知道它永远是真的:

    match group with
    | (id, g) when g.Length = 1 -> printf $"No duplicates for {id} is OK.\n"
    | (id, [input1; input2]) -> printf $"Two duplicates for {id} is OK.\n"
    | (id, g) -> printf $"More than two duplicates for {id} is NOT OK.\n"
Run Code Online (Sandbox Code Playgroud)

证明:

  • g.Length永远不能为负数或 0
  • 如果g.Length是1那么它将匹配第一种情况
  • 如果g.Length是2那么它将匹配第二种情况
  • 因此,g.Length如果控制达到第三种情况,则始终 > 2。

我建议您编写此代码的方式如下:

let validateDuplicates (id, g : List<_>) =
    match g.Length with
    | 0 -> failwith "Unexpected"
    | 1 -> printf $"No duplicates for {id} is OK.\n"
    | 2 -> printf $"Two duplicates for {id} is OK.\n"
    | _ -> printf $"More than two duplicates for {id} is NOT OK.\n"

input
|> validate
|> List.groupBy (fun i -> i.id)
|> List.iter validateDuplicates
Run Code Online (Sandbox Code Playgroud)

我所做的更改是:

  • 使用List.iter而不是管道List.forallignore.
  • 消除调用中不需要的 lambda validateDuplicates
  • 使用模式匹配来解构输入validateDuplicates
  • 直接匹配 ong.Length而不是 usingwhen子句。
  • 防御性编程:显式检查空列表以明确您的意图。

您可能还需要考虑通过 F# 的类型使您的验证函数变得纯粹(即无副作用)Result

  • 计算链表长度是一个坏习惯,不要习惯它。与 `[]`、`[_]` 和 `[_;_]` 更好匹配 (6认同)
  • 我用一些额外的反馈更新了我的答案。 (2认同)