匹配受歧视的联合案例而不是内容

Chr*_*tig 4 f# pattern-matching discriminated-union

在 F# 中是否可以根据大小写而不是大小写内容来匹配受歧视的联合?例如,如果我想按 case 的元素过滤列表Flag,是否可以这样过滤?目前,我被迫使用三个独立的功能来按照我想要的方式进行过滤。这是我到目前为止所采用的方法:

type Option = 
 {Id : string
  Arg : string}

type Argument =
     | Flag of string
     | Option of Option
     | Unannotated of string

//This is what I'm going for, but it does not work as the "other" match case will never be matched
let LocateByCase (case:Argument) (args : Argument List) =
    args
    |> List.filter (fun x -> match x with
                             | case -> true
                             | _ -> false)

let LocateAllFlags args =
    args
    |> List.filter (fun x -> match x with
                             | Flag y -> true
                             | _ -> false)
let LocateAllOptions args =
    args
    |> List.filter (fun x -> match x with
                             | Option y -> true
                             | _ -> false)

let LocateAllUnannotated args =
    args
    |> List.filter (fun x -> match x with
                             | Unannotated y -> true
                             | _ -> false)
Run Code Online (Sandbox Code Playgroud)

我是否遗漏了 F# 语言的某些方面,可以让这个问题更容易处理?

Fyo*_*kin 5

没有内置方法可以找出 DU 值的情况。面对这种需求时,通常的做法是为每种情况提供适当的功能:

type Argument =
     | Flag of string
     | Option of Option
     | Unannotated of string
    with
     static member isFlag = function Flag _ -> true | _ -> false
     static member isOption = function Option _ -> true | _ -> false
     static member isUnannotated = function Unannotated _ -> true | _ -> false

let LocateByCase case args = List.filter case args

let LocateAllFlags args = LocateByCase Argument.isFlag args
Run Code Online (Sandbox Code Playgroud)

(不用说,这个LocateByCase函数实际上是多余的,但我决定保留它以使答案更清晰)


警告:下面是肮脏的黑客行为

或者,您可以将案例作为报价提供,并为自己创建一个函数来分析该报价,从中找出案例名称,并将其与给定值进行比较:

open FSharp.Quotations

let isCase (case: Expr<'t -> Argument>) (value: Argument) = 
    match case with
    | Patterns.Lambda (_, Patterns.NewUnionCase(case, _)) -> case.Name = value.GetType().Name
    | _ -> false

// Usage:
isCase <@ Flag @> (Unannotated "")  // returns false
isCase <@ Flag @> (Flag "")  // returns true
Run Code Online (Sandbox Code Playgroud)

然后使用这个函数进行过滤:

let LocateByCase case args = List.filter (isCase case) args

let LocateAllFlags args = LocateByCase <@ Flag @> args
Run Code Online (Sandbox Code Playgroud)

然而,这本质上是一个肮脏的黑客行为。它的肮脏和黑客性来自于这样一个事实:因为你不能在编译时要求某种引用形状,所以它会允许无意义的程序。例如:

isCase <@ fun() -> Flag "abc" @> (Flag "xyz")  // Returns true!
isCase <@ fun() -> let x = "abc" in Flag x @> (Flag "xyz")  // Returns false. WTF?
// And so on...
Run Code Online (Sandbox Code Playgroud)

如果编译器的未来版本决定以稍微不同的方式生成引号,并且您的代码将无法识别它们并始终报告漏报,则可能会发生另一个问题。

我建议尽可能避免弄乱报价。表面上看起来很简单,但实际上是简单大于简单