是否可以将区分的联合标记作为参数传递?

时间:2014-03-26 09:45:15

标签: f# pattern-matching discriminated-union

是否可以将有区别的联合标记的类型传递给另一个函数,以便它可以用于模式匹配?

我的意思的非工作示例:

type Animal = Pig of string | Cow of string | Fish of string

let animals = [Pig "Mike"; Pig "Sarah"; Fish "Eve"; Cow "Laura"; Pig "John"]

let rec filterAnimals animalType animals =
    if animals = [] then
        []
    else
        let rest = filterAnimals animalType (List.tail animals)
        match List.head animals with
        |animalType animal -> animal::rest // <- this doesn't work
        |_ -> rest

printfn "%A" (filterAnimals Pig animals)

5 个答案:

答案 0 :(得分:13)

如果案例之间没有语义重叠,则受歧视的联合最有效。

在您的示例中,每个案例包含具有相同含义的相同组件,string表示“动物的名称”。但那是语义上的重叠!然后歧视联盟将迫使你做出你不想要的区别:你想要被迫区分“猪的名字”和“牛的名字” ;你只想想想“动物的名字”。

让我们做一个更适合的类型:

type Animal = Pig  | Cow  | Fish
type Pet = Animal * string

let animals = [(Pig, "Mike"); (Fish, "Eve"); (Pig, "Romeo")

使用该类型,过滤掉非Pig s是一行的:

animals |> List.filter (fst >> (=) Pig) 

如果不是每只动物都有名字,请使用选项类型:

type Pet = Animal * string option

如果您知道每个Pig都有一个名字,但<{1}}没有Fish 会为您的动物使用受歧视的联盟:这些情况没有重叠。

答案 1 :(得分:7)

您可以更改filterAnimals功能以Partial Active Pattern作为输入:

let rec filterAnimals (|IsAnimalType|_|) animals =
    if animals = [] then
        []
    else
        let rest = filterAnimals (|IsAnimalType|_|) (List.tail animals)
        match List.head animals with
        | IsAnimalType animal -> animal::rest
        | _ -> rest

然后你可以为猪定义一个活跃的部分模式:

let (|IsPig|_|) candidate =
    match candidate with
    | Pig(_) -> Some candidate
    | _ -> None

您可以像这样调用函数(FSI示例):

> filterAnimals (|IsPig|_|) animals;;
val it : Animal list = [Pig "Mike"; Pig "Sarah"; Pig "John"]

实际上,您可以像这样减少部分活动模式:

let (|IsPig|_|) = function | Pig(x) -> Some(Pig(x)) | _ -> None

事实证明你甚至可以内联它们:

> filterAnimals (function | Pig(x) -> Some(Pig(x)) | _ -> None) animals;;
val it : Animal list = [Pig "Mike"; Pig "Sarah"; Pig "John"]
> filterAnimals (function | Fish(x) -> Some(Fish(x)) | _ -> None) animals;;
val it : Animal list = [Fish "Eve"]
> filterAnimals (function | Cow(x) -> Some(Cow(x)) | _ -> None) animals;;
val it : Animal list = [Cow "Laura"]

答案 2 :(得分:4)

不,它不可能只传递标签,以便能够单独处理标签和字符串,您可以像这样定义它们:

type AnimalType = Pig  | Cow  | Fish
type Animal = Animal of AnimalType * string

let animals = [Animal (Pig, "Mike"); Animal (Pig, "Sarah"); Animal (Fish, "Eve"); Animal (Cow, "Laura"); Animal (Pig, "John")]

let rec filterAnimals animalType animals =
    if animals = [] then
        []
    else
        let rest = filterAnimals animalType (List.tail animals)
        match List.head animals with
        | Animal (x, animal) when x = animalType -> animal::restwork
        |_ -> rest

printfn "%A" (filterAnimals Pig animals)

或者,您只能使用AnimalType * string

元组

<强>更新

关于如果结构并不总是相同会发生什么情况的评论中的问题,您可以使用以下技巧:您可以比较两个判别联盟的类型,因为每个标记被编译为不同的子类。

type Animal = 
    | Person of string * string 
    | Pig of string 
    | Cow of string 
    | Fish of string

let animals = [Pig "Mike"; Pig "Sarah"; Fish "Eve"; Cow "Laura"; Pig "John"]

let rec filterAnimals animalType animals =
    if animals = [] then
        []
    else
        let rest = filterAnimals animalType (List.tail animals)
        match List.head animals with
        | x when animalType.GetType() = x.GetType() -> x::rest
        |_ -> rest

printfn "%A" (filterAnimals (Pig "") animals)

但在走这条路之前,你应该考虑一下你是否真的需要像这样建模你的问题。

即使您决定使用此结构,我宁愿使用内置过滤器功能,请参阅@polkduran提出的解决方案。

答案 3 :(得分:4)

为了完整起见,我会列出这个解决方案 如果您输入 quoted ,则可以推断出代码的名称:

open Microsoft.FSharp.Quotations

type Animal = Pig of string | Cow of string | Fish of string

let isAnimal (animalType : Expr) (animal : Expr) =
    match animal with
        | NewUnionCase(at, _) ->
            match animalType with
                | Lambda (_, NewUnionCase (aatt, _)) -> aatt.Name = at.Name
                | _ -> false
        | _ -> false

let animal = <@ Pig "Mike" @>
isAnimal <@ Pig @> animal  // True
isAnimal <@ Cow @> animal  // False

虽然这是非常冗长的,如果你想引用一个列表而不是单个值,它会更加如此。

略有不同的版本,我们仅引用动物类型,可以让您根据需要轻松过滤动物列表(以一些可疑的字符串比较为代价):

open Microsoft.FSharp.Quotations

type Animal = Pig of string | Cow of string | Fish of string

let isAnimal (animalType : Expr) animal =
    match animalType with
        | Lambda (_, NewUnionCase (aatt, _)) -> animal.ToString().EndsWith(aatt.Name)
        | _ -> false

let animal = Pig "Mike"  // No quote now, so we can process Animal lists easily
isAnimal <@ Pig @> animal  // True
isAnimal <@ Cow @> animal  // False

let animals = [Pig "Mike"; Pig "Sarah"; Fish "Eve"; Cow "Laura"; Pig "John"]
let pigs = animals |> List.filter (isAnimal <@ Pig @>)

后一版本并不比将标记名称作为字符串传递更好。

答案 4 :(得分:2)

这并没有直接回答你的问题,而是建议另一种方法来实现你想要的。 您可以使用现有的高阶函数List.filter和模式匹配来过滤列表:

let pigs = animals |> List.filter (function |Pig(_)-> true |_->false ) 

我认为这是一种更惯用的方法:您使用模式过滤列表,在这种情况下,您过滤动物只保留满足模式Pig(_)的人。