Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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).

A is not a type, it is just a constructor for Something. There's no way you can avoid pattern matching, which is not necessarily a bad thing.

That said, F# does offer a thing called active patterns, for instance

let (|AA|) = function 
    | A i -> i 
    | B _ -> invalidArg "B" "B's not allowed!"

which you can then use like this:

let DoSomethingWithA (AA i) = i + 1

But there's no real reason why you would want to do that! You still do the same old pattern matching under the hood, plus you risk the chance of a runtime error.

In any case, your implementation of UseSomething is perfectly natural for F#.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top