是否可以将有区别的联合标记的类型传递给另一个函数,以便它可以用于模式匹配?
我的意思的非工作示例:
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)
答案 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(_)
的人。