Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing specific case from F# DU

Suppose I have the following DU:

type Something =
| A of int
| B of string * int

Now I use it in a function like this:

let UseSomething = function
| A(i) -> DoSomethingWithA i
| B(s, i) -> DoSomethingWithB s i

That works, but I've had to deconstruct the DU in order to pass it to the DoSomethingWith* functions. It feels natural to me to try to define DoSomethingWithA as:

let DoSomethingWithA (a: Something.A) = ....

but the compiler complains that the type A is not defined.

It seems entirely in keeping with the philosophy of F# to want to restrict the argument to being a Something.A, not just any old int, so am I just going about it the wrong way?

like image 371
Akash Avatar asked Mar 15 '13 08:03

Akash


2 Answers

The important thing to note is that A and B are constructors of the same type Something. So you will get inexhaustive pattern matching warning if you try to use A and B cases separately.

IMO, deconstructing all cases of DUs is a good idea since it is type-safe and forces you to think of handling those cases even you don't want to. The problem may arise if you have to deconstruct DUs repetitively in the same way. In that case, defining map and fold functions on DUs might be a good idea:

let mapSomething fa fb = function
| A(i) -> fa i
| B(s, i) -> fb s i

Please refer to excellent Catamorphism series by @Brian to learn about fold on DUs.

That also said that your example is fine. What you really process are string and int values after deconstruction.

You can use Active Patterns to consume two cases separately:

let (|ACase|) = function A i -> i | B _ -> failwith "Unexpected pattern B _"
let (|BCase|) = function B(s, i) -> (s, i) | A _ -> failwith "Unexpected pattern A _"

let doSomethingWithA (ACase i) = ....

but inferred type of doSomethingWithA is still the same and you get an exception when passing B _ to the function. So it's a wrong thing to do IMO.

like image 148
pad Avatar answered Sep 28 '22 01:09

pad


The other answers are accurate: in F# A and B are constructors, not types, and this is the traditional approach taken by strongly typed functional languages like Haskell or the other languages in the ML family. However, there are other approaches - I believe that in Scala, for example, A and B would actually be subclasses of Something, so you could use those more specific types where it makes sense to do so. I'm not completely sure what tradeoffs are involved in the design decision, but generally speaking inheritance makes type inference harder/impossible (and true to the stereotype type inference in Scala is much worse than in Haskell or the ML languages).

like image 39
kvb Avatar answered Sep 28 '22 02:09

kvb