简体   繁体   English

如何编写F#联合类型选择器?

[英]How to write an F# union type chooser?

Is there a better way to do this if F#? 如果使用F#,还有更好的方法吗?

type T =
    | A of int
    | B of string
    static member chooseA x = match x with A i -> Some i | _ -> None
    static member chooseB x = match x with B s -> Some s | _ -> None

The usecase is the following: 用例如下:

let collection = [A 10; B "abc"]
let aItems = collection |> Seq.choose T.chooseA
let bItems = collection |> Seq.choose T.chooseB

Thanks! 谢谢!

Use List.partition to split your source elements: 使用List.partition拆分源元素:

type T =
    | A of int
    | B of string

let collection = [A 10; B "abc"; A 40; B "120"]

let result = List.partition (function | A _ -> true | _ -> false) collection

val result : T list * T list = ([A 10; A 40], [B "abc"; B "120"])

Then you can use fst and snd to select the relevant lists. 然后,您可以使用fst和snd选择相关列表。

This is awkward, but I can see why it is not an important case F#'s design. 这很尴尬,但是我可以理解为什么它不是F#设计的重要案例。 Usually, there is a solution that allows for a complete pattern match instead of multiple, somewhat incomplete ones. 通常,有一种解决方案允许完整的模式匹配,而不是多个(有些不完整)的模式匹配。 For example, the two concrete item sequences can be constructed like this: 例如,可以这样构造两个具体的项目序列:

let aItems, bItems =
    let accA, accB = ResizeArray(), ResizeArray()
    collection |> Seq.iter (function A i -> accA.Add i | B s -> accB.Add s)
    seq accA, seq accB

A similar solution without mutation can be made if you dislike it, but I see little reason to worry about encapsulated mutation. 如果您不喜欢,可以采用类似的无突变解决方案,但是我几乎没有理由担心封装突变。 Note that the results are cast to seq . 请注意,结果强制转换为seq

This uses pattern matching in the manner it is designed for: 这将按照其设计方式使用模式匹配:

  • If another case is added to T , a warning will appear in the handling function, which is exactly where editing should continue: determining how to treat the new input case. 如果将另一个大小写添加到T ,则处理功能中将显示一条警告,这正是继续编辑的地方:确定如何处理新输入的大小写。
  • The program doesn't needlessly iterate the input multiple times for each kind of input, but rather goes over it once and handles each item when first encountered. 该程序不必为每种输入重复多次输入,而只需遍历一次并在初次遇到时处理每个项目。

If the above isn't suitable, you can still shorten the question's code a bit by using the function keyword and declaring the chooser function as a lambda. 如果上述方法不合适,您仍然可以通过使用function关键字并将声明选择器函数声明为lambda来稍微缩短问题的代码。 For example: 例如:

let aItems = collection |> Seq.choose (function A i -> Some i | _ -> None)

Note that this is lazy, just like the proposal in the question: here, every iteration over aItems will needlessly iterate over all the B cases in the input. 请注意,这是惰性的,就像问题中的建议一样:在这里,对aItems每次迭代aItems将不必要地遍历输入中的所有B例。

I can offer the following variant: 我可以提供以下变体:

open System.Reflection

type T =
    | A of int
    | B of string

let collection = [A 10; B "abc"; A 40; B "120"]

let sp (col: T list) (str:string) = 
      if col=[] then []
      else  
        let names = "Is" + str
        col |> List.filter(fun x-> let t = x.GetType()
                                   if t.GetProperty(names) = null then false
                                   else 
                                   t.InvokeMember(names, BindingFlags.GetProperty, null, x, null) :?> bool)
            |> List.map(fun y ->
                y.GetType().InvokeMember("get_Item", BindingFlags.InvokeMethod, null, y, null))

sp collection "A" |> printfn "%A\n"
sp collection "B" |> printfn "%A\n"   
sp collection "C" |> printfn "%A\n"

Print: 打印:

[10; 40]

["abc"; "120"]

[]

http://ideone.com/yAytQk http://ideone.com/yAytQk

I'm new to F#, so I think that can be done easier 我是F#的新手,所以我认为这可以轻松完成

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM