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

时间:2012-10-25 14:09:14

标签: 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的通用/高阶版本,它允许我根据所有其他类型的区分联合(在这种情况下为Bar)过滤我的列表。

如下所示,其中'a可以是Foo或Bar

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

但是,此尝试会产生以下错误:

  

错误FS0039:模式鉴别器'a'未定义

2 个答案:

答案 0 :(得分:5)

如果您只想过滤列表,那么最简单的选择是使用function来编写标准模式匹配:

[ Foo; Bar; Foo ]
|> List.filter (function Foo -> true | _ -> false)

如果你想编写一个更复杂的泛型函数来检查一个案例,然后做其他事情,那么最简单的选项(通常会起作用)就是采用一个返回true或{{的谓词1}}:

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 is case item = 
  if case = item then
    true
  else
    false

// You can just pass it 'Foo' as the first parameter to 
// `is` and use partial function application
[ Foo; Bar; Foo ]
|> List.filter (is Foo)

// In fact, you can use the built-in equality test operator
[ Foo; Bar; Foo ] |> List.filter ((=) Foo)

你可以使用Reflection做各种技巧(检查具有指定名称的情况),你也可以使用F#语录来获得更好更安全的语法,但我不认为这是值得的,因为使用模式匹配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. 为您提供了非常明确的代码。

编辑 - 出于好奇,一个使用反射的解决方案(并且速度慢,不安全,没有人应该在实践中使用它,除非你真的知道什么你在做什么)看起来像这样:

function

它使用引号来标识联合案例,因此您可以编写如下内容:

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"

答案 1 :(得分:3)

只要union情况接受相同的参数集,您就可以将构造函数作为参数传递并重构DU以进行比较。

FooBar有参数时,它看起来更具吸引力:

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)