Search code examples
f#discriminated-union

Why can't I simplify this iteration through a list of members of a discriminated union?


Frequently one wants to iterate (with either map, iter, or fold) through a collection of heterogeneous objects (different types). One way to deal with this is to create a discriminated union, which allows one to create a list with the objects suitably converted to DU cases. The following code does that in a simple example:

type MYDU = | X1 of int
            | X2 of float
            | X3 of string

let bar (y: MYDU) =
    match y with
    | X1 x -> printfn "%A" x  
    | X2 x -> printfn "%A" x
    | X3 x -> printfn "%A" x

[X1(1); X2(2.0); X3("3"); X1(4)]
|> List.map bar |> ignore

This code runs fine and prints

1
2.0
"3"
4

Great! But I wonder if one can avoid repeating the call to printfn. I tried the following and it does not compile:

let baz (y: MYDU) =
    match y with
    | X1 x | X2 x | X3 x -> printfn "%A" x // red squiggly line under X1 x

The compiler issues this message:

This expression was expected to have type 'int' but here has type 'float'

I suspect avoiding repetition is feasible but I must be making a basic mistake. Any suggestions?


Solution

  • You're not making a mistake there, it's just not something F#'s type system would allow.

    You can have multiple patterns on the left side of the match case arrow, but they are required to bind the same set of values (incl. the types). Here, x has a different type for each pattern, and that's enough for the compiler to complain.

    There are ways to alleviate the pain (you could have a member on the DU that would return a boxed value, or you could have an active pattern that would do the boxing in the match case), but they're highly situational. Splitting the patterns into separate cases and repeating the right side for each one of them is always a better solution in a vacuum.