7

If there's another way to achieve what I'm trying to do below, please let me know. Suppose I have the following sample code

type FooBar = 
  | Foo
  | Bar

let foobars = [Bar;Foo;Bar]

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

foobars |> Seq.filter isFoo

I want to write a generic/higher-order version of isFoo that allows me to filter my list based on all other types of the discriminated union (Bar in this case).

Something like the following, where 'a can be either Foo or Bar

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

However, this attempt yields the following error:

error FS0039: The pattern discriminator 'a' is not defined

4

2 回答 2

5

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

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

如果您想编写一些更复杂的通用函数来检查一个案例然后执行其他操作,那么最简单的选择(通常可以使用)是采用返回trueor的谓词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)

如果您有更复杂的可区分联合,其中某些情况具有参数,则最后一种方法将不起作用,因此它可能不是很有用。例如,如果您有一个选项值列表:

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您可以使用 Reflection 执行各种技巧(检查具有指定名称的案例),还可以使用 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 @>)
于 2012-10-25T14:15:01.900 回答
3

只要联合案例接受相同的参数集,您就可以将构造函数作为参数传递并重构 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)
于 2012-10-25T14:18:42.737 回答