简体   繁体   中英

How to write an F# union type chooser?

Is there a better way to do this if 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:

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.

This is awkward, but I can see why it is not an important case F#'s design. 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 .

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.
  • 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. 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.

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

I'm new to F#, so I think that can be done easier

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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