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

F# list pattern matching limitations or just bad writing?

给定一些 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 }
]

我的函数 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

validateDuplicates 内部,我注意到 group 下有一点波浪形,导致出现警告:

Incomplete pattern matches on this expression. For example, the value (_,[_;_;_]) may indicate a case not covered by the pattern(s). However, a pattern rule with a when clause might successfully match this value.

有什么方法可以很好地使用编译器来避免此警告?

更新

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

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)

只需删除最后一个 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"

证明:

  • 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

我所做的更改是:

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

您可能还想考虑通过 F# 的 Result 类型使您的验证函数纯(即没有副作用)。