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?
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With