F#使用泛型类型作为模式鉴别器

F#使用泛型类型作为模式鉴别器,f#,pattern-matching,discriminated-union,F#,Pattern Matching,Discriminated Union,如果有其他方法来实现我下面要做的事情,请让我知道。假设我有以下示例代码 type FooBar = | Foo | Bar let foobars = [Bar;Foo;Bar] let isFoo item = match item with | Foo _ -> true | _ -> false foobars |> Seq.filter isFoo 我想编写一个通用/高阶版本的isFoo,它允许我根据所有其他类型的歧视联合(在本例中为B

如果有其他方法来实现我下面要做的事情,请让我知道。假设我有以下示例代码

type FooBar = 
  | Foo
  | Bar

let foobars = [Bar;Foo;Bar]

let isFoo item  = 
  match item with
  | Foo _ -> true
  | _ -> false

foobars |> Seq.filter isFoo
我想编写一个通用/高阶版本的isFoo,它允许我根据所有其他类型的歧视联合(在本例中为Bar)过滤我的列表

类似于下面的内容,其中“a”可以是Foo或Bar

let is<'a> item  = 
  match item with
  | a _ -> true
  | _ -> false

let is如果您只想筛选列表,那么最简单的选择是使用
函数
编写标准模式匹配:

[ Foo; Bar; Foo ]
|> List.filter (function Foo -> true | _ -> false)
如果您想编写一些更复杂的泛型函数来检查一个案例,然后执行其他操作,那么最简单的选择(通常会起作用)是使用返回
true
false
的谓词:

let is cond item  = 
  if cond item then
    true
  else
    false

// You can create a predicate using `function` syntax
is (function Foo -> true | _ -> false) <argument>
如果您有更复杂的判别并集,其中某些情况下有参数,则最后一种方法将不起作用,因此它可能不是很有用。例如,如果您有选项值列表:

let opts = [ Some(42); None; Some(32) ]
opts |> List.filter (is Some) // ERROR - because here you give 'is' a constructor 
                              // 'Some' instead of a value that can be compared. 
您可以使用反射(检查具有指定名称的案例)执行各种技巧,也可以使用F#引号获得更好、更安全的语法,但我认为这不值得,因为使用
函数
的模式匹配可以提供非常清晰的代码

编辑-出于好奇,一个使用反射的解决方案(速度慢,不适合键入,除非你真的知道自己在做什么,否则任何人都不应该在实践中使用它)可能是这样的:

open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations

let is (q:Expr) value = 
  match q with
  | Patterns.Lambda(_, Patterns.NewUnionCase(case, _)) 
  | Patterns.NewUnionCase(case, _) ->
      let actualCase, _ = FSharpValue.GetUnionFields(value, value.GetType())
      actualCase = case
  | _ -> failwith "Wrong argument"
type Case = Foo of int | Bar of string | Zoo

[ Foo 42; Zoo; Bar "hi"; Foo 32; Zoo ]
|> List.filter (is <@ Foo @>)
它使用引号来标识工会案例,因此您可以这样写:

open Microsoft.FSharp.Reflection
open Microsoft.FSharp.Quotations

let is (q:Expr) value = 
  match q with
  | Patterns.Lambda(_, Patterns.NewUnionCase(case, _)) 
  | Patterns.NewUnionCase(case, _) ->
      let actualCase, _ = FSharpValue.GetUnionFields(value, value.GetType())
      actualCase = case
  | _ -> failwith "Wrong argument"
type Case = Foo of int | Bar of string | Zoo

[ Foo 42; Zoo; Bar "hi"; Foo 32; Zoo ]
|> List.filter (is <@ Foo @>)
type Case=Foo of int | Bar of string | Zoo
[富42;动物园;酒吧“嗨”;富32;动物园]
|>List.filter(is)

只要联合用例接受相同的参数集,就可以将构造函数作为参数传递,并重新构造DU进行比较

Foo
Bar
具有以下参数时,它看起来更具吸引力:

type FooBar = Foo of int | Bar of int

let is constr item = 
    match item with
    | Foo x when item = constr x -> true
    | Bar x when item = constr x -> true
    | _ -> false
在您的示例中,构造函数没有参数。因此,您可以用更简单的方式编写
is

type FooBar = Foo | Bar

let is constr item = item = constr

[Bar; Foo; Bar] |> Seq.filter (is Foo)

@托马斯:我试图避免每次都要指定一个完整的谓词。ifs不是超级功能吗(即我可以使用let is case item=case=item)?@JohannesRudolph Writing
let is case item=case=item
只要您的DU中没有参数,就可以工作。例如,它不适用于选项类型,因为
是Some(Some(42))
不会进行类型检查。虽然在DU案例没有参数的情况下,您可以非常整洁地编写
items |>List.filter(=)Foo)
,这可能是您想要的,您甚至不需要任何函数。见编辑:-)@Thomas:是的,这是最好的。不幸的是,我的DU有参数,我对这个例子过于简单化了。不过现在我对这个问题有了更好的理解,这让我意识到我无法将泛型类型转换为编译时检查模式匹配表达式。@JohannesRudolph我添加了一个使用反射的更“泛型”的解决方案,但这确实是一个过火(而且速度也很慢),所以我不认为应该使用:-),只是为了看看可能发生什么。我不认为在这种情况下使用模式匹配是有帮助的。事实上(我在写我的第一个答案时也没有意识到),你可以说
let is=(=)
:-)。如果
Foo
Bar
有参数,它会更有吸引力。当item=constr x
时,通过分解
项进行比较:
|Foo x。是的,这更有意义。在使用参数的情况下,它实际上很有用。在没有的情况下,它只是
=
:-)的一个奇特实现