F#: Meaning of "type X = Y of Z"

Viewed 62

What does type X = Y of Z mean in F#? In particular, does the Y token have any material purpose? If I want to express the fact that X is a type backed by the underlying type Z, why do I need Y in that expression?

According to https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/keyword-reference, the of keyword is used to express one of these:

  1. Discriminated Unions
  2. Delegates
  3. Exception Types

Strictly speaking, according to the docs, type X = Y of Z doesn't fall into ANY of the above 3 categories.

  1. It's not Discriminated Union syntax because it's lacking the pipe | character.
  2. It's not a Delegate, because the delegate keyword is absent.
  3. It's not an Exception Type because the exception type is absent.

So it seems like type X = Y of Z is invalid syntax even though it is used pervasively in https://fsharpforfunandprofit.com/posts/conciseness-type-definitions/. Very puzzling - is it that the docs are inaccurate?

2 Answers

Contrary to your intuition, such declaration is, in fact, a discriminated union - one with just one case. Yes, discriminated unions with only one case are totally legal and are sometimes used for type safety or for hiding implementation details.

If you don't want to have that extra Y in there, that is also legal:

type X = Z

This is called "type alias". Its advantage over discriminated union is resource consumption (both performance and memory), but its drawback is that it doesn't provide any extra safety, because X ends up being equivalent and interchangeable with Z in all contexts.

It's not Discriminated Union syntax because it's lacking the pipe | character.

It's possible to omit the first pipe in a Discriminated union:

type DU = A of int | B

This is bad style for standard multi-line definitions but can be appropriate for one-liners. In your example there is only one case so it is possible to omit the pipe completely, which admittedly causes confusion. It would improve clarity to include the bar: type X = | Y of Z

In type X = Y of Z, does the Y token have any material purpose?

Y is used to disambiguate between cases, and for matching against data. If there are are likely to be more cases in future then it makes sense to have this scaffolding for matching. If you know that there will only ever be one case then a DU is not the most appropriate type, since a record type X = { Y: Z } is a simpler construct which achieves the same purpose.

Related