9

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

我的意思的非工作示例:

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)
4

5 回答 5

13

如果案例之间没有语义重叠,则可区分联合效果最好。

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

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

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

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

使用这种类型,过滤掉非Pigs 是单行的:

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

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

type Pet = Animal * string option

如果您知道每个动物都有名字,但没有名字,那么您将为您的动物使用有区别的联合:这些案例没有重叠。PigFish

于 2014-03-26T11:26:01.247 回答
7

您可以更改filterAnimals函数以将部分活动模式作为输入:

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"]
于 2014-03-26T10:11:53.527 回答
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 提出的解决方案。

于 2014-03-26T10:05:48.420 回答
4

Just for completeness, I'll list this solution.
If you quoted your input, you'd be able to reason about the names of the tags:

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

This is admittedly quite verbose though, and it would become even more so if you wanted to quote a list instead of a single value.

A slightly different version, where we quote only the animal type, would let you easily filter lists of animals, as you need (at the price of some questionable comparison of strings):

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 @>)

The latter version is not really superior to passing the tag name as a string.

于 2014-03-26T11:44:17.537 回答
2

This does not answer your question directly but suggests an alternative way to achieve what you want. You could filter your list using the existing high order function List.filter and pattern matching:

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

I think this is a more idiomatic approach: you filter your list using a pattern, in this case you filter your animals keeping only those who satisfy the pattern Pig(_).

于 2014-03-26T12:37:09.567 回答