Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# generic function filter list of discriminated unions

F# newbie question. I have a list of discriminated unions like:

type Type1 = { name:string;  id: int;  }
type Type2 = { x: float; y: float;}
type Type3 = { x: float; naam:string}
type Union1 =
    | T1 of Type1
    | T2 of Type2
    | T3 of Type3 
let lst1 = [ T1 {name="nnn";  id=3};  T2 {x=1.1; y=1.3}; T1 {name="naam1";  id=39}; T3{x=0.0; naam="xx"}]; 

//To filter out items of Type1, i do:
let fltT1 (l:list<Union1>) :list<Type1> =
    let rec loop (l:list<Union1>)  (acc:list<Type1>) =
        match l with
        | h::t -> match h with
                   // this is now specific per type
                   | T1{name=n;id=i} -> loop t ({name=n;id=i}::acc)
                   | _ -> loop t acc
        | [] -> acc
    loop l [] |> List.rev 

How can I make such a function generic to specify in the call the required output type (Type1|Type2|Type3)?

like image 662
Rob Frohwein Avatar asked Dec 06 '15 17:12

Rob Frohwein


1 Answers

My approach would probably be just to use List.choose or Seq.choose. It has advantages over filter/map because you only need to perform the pattern match once and it's a lot more concise than a fold.

lst1 
|> List.choose 
    (function
     |T1 res -> Some res
     |_ > None)

choose is something like a map and a filter combined, it returns f(x) for every element where that result was Some and ignores all elements where it was None. In this example, the return type is a Type1 list.


Parametrising a function based on a particular union case is not possible, this is because particular union cases are not themselves types, they are merely constructors for the union type. T1 in your example is not a type but Union1 is. That means, at some point, an explicit pattern match is required to decompose it. (Note that this isn't true of all functional languages, Scala's union cases are modelled with inheritance but F# adopts the approach used by the likes of Haskell, Ocaml, etc.).

As Fyodor Soikin mentioned, you can write a static member or a function to check against each case if you wish, as an example:

static member tryAssumeT1 = function 
    |T1 t1 -> Some t1
    | _ -> None

You can then use this syntax:

lst1 |> List.choose (tryAssumeT1)
like image 124
TheInnerLight Avatar answered Nov 05 '22 18:11

TheInnerLight