Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F#: how to elegantly select and group discriminated unions?

Tags:

f#

Say I have a list of shapes:

type shape = 
| Circle of float
| Rectangle of float * float

let a = [ Circle 5.0; Rectangle (4.0, 6.0)]

How can I then test e.g. a Circle exists in a? I could create a function for each shape

let isCircle s = 
    match s with
    | Circle -> true
    | _ -> false
List.exists isCircle a

but I feel there must be a more elegant way in F#, other than having to define such a function for each shape type. Is there?

Related question is how to group a list of shapes, based on shape types:

a |> seq.groupBy( <shapetype? >)
like image 813
Emile Avatar asked Jul 29 '10 13:07

Emile


2 Answers

If you're interested in the different categories of shapes, then it makes sense to define another type that exactly captures them:

type shapeCategory = Circular | Rectangular

let categorize = function
    | Circle _ -> Circular
    | Rectangle _ -> Rectangular

List.exists ((=) Circular) (List.map categorize a)

a |> Seq.groupBy(categorize)

Edit - as suggested by Brian, you can alternatively use active patterns instead of a new type. It works out pretty similarly for your examples, but would extend better to more complicated patterns, while the approach above may be better if you're code often works with the categories, and you want a nice union type for them instead of a Choice type.

let (|Circular|Rectangular|) = function 
    | Circle _ -> Circular
    | Rectangle _ -> Rectangular 

List.exists (function Circular -> true | _ -> false) a

let categorize : shape -> Choice<unit, unit> =  (|Circular|Rectangular|) 
a |> Seq.groupBy(categorize)
like image 120
RD1 Avatar answered Oct 21 '22 17:10

RD1


you can combine F# reflection with quotations to get generic solution

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns

type Shape = 
    | Circle of float
    | Rectangle of float * float

let isUnionCase (c : Expr<_ -> 'T>)  = 
    match c with
    | Lambda (_, NewUnionCase(uci, _)) ->
        let tagReader = Microsoft.FSharp.Reflection.FSharpValue.PreComputeUnionTagReader(uci.DeclaringType)
        fun (v : 'T) -> (tagReader v) = uci.Tag
    | _ -> failwith "Invalid expression"

let a = 
    [ Circle 5.0; Rectangle (4.0, 6.0)] 
        |> List.filter (isUnionCase <@ Rectangle @>)
printf "%A" a
like image 38
desco Avatar answered Oct 21 '22 16:10

desco