Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 267
Soldalma Avatar asked Mar 09 '23 02:03

Soldalma


2 Answers

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.

like image 178
scrwtp Avatar answered May 02 '23 19:05

scrwtp


Something you could do instead is convert your arguments to a common type for printing, then print that value out instead. And you still get the advantages of pattern matching and discriminated unions :)

Here is an example of this approach

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

let bar y =
    let myStr = 
      match y with
      | X1 x -> string x  
      | X2 x -> string x
      | X3 x -> x
    printfn "%s" myStr

bar (X1 5)
like image 35
JosephStevens Avatar answered May 02 '23 20:05

JosephStevens