14

I'm defining a monadic observable/reactive parser. This behaves quite differently to a normal parser as it is a continuous query. The underlying type is:

IObservable<'a> -> IObservable<'b>

From looking at various parser implementations in functional languages, it seems as though the more appropriate way to define things is a single case discriminated union:

type Pattern<'a,'b> = Pattern of (IObservable<'a> -> IObservable<'b>)

Which means I then need to extract the underlying function to use it:

let find (Pattern p) = p

The question is: Is this just by convention, or for purposes of later extension, or is there a reason to do this even if the definition never changes?

Bonus question: If it's just for a more convenient type signature, why not just use a type alias:

type Pattern<'a,'b> = IObservable<'a> -> IObservable<'b>

I've advanced quite a way through this, and haven't found a case where composability is affected by not using the DU.

pad
  • 41,040
  • 7
  • 92
  • 166
yamen
  • 15,390
  • 3
  • 42
  • 52

2 Answers2

16

F# compiler doesn't preserve information about type abbreviation, so you do not benefit from type inference at all. Type signature can be understood as program specification; letting the type checker do their job is a good way to ensure correctness of your programs.

You need explicitly specify type annotation everywhere in the case of type alias:

type Vector = float * float

// val add : float * float -> float * float -> Vector
let add ((x1, y1): Vector) ((x2, y2): Vector): Vector = (x1 + y1, x2 + y2)

but it doesn't give you transparency as using DUs:

type Vector = V of float * float

// val add : Vector -> Vector -> Vector
let add (V(x1, y1)) (V(x2, y2)) = V(x1 + y1, x2 + y2)

In complex programs clear type signatures indeed make it easier to maintain composability.

Not only it's simpler to add more cases to single-case DUs, but also it's easier to extend DUs with member and static methods. One example is you often override ToString() for pretty printing.

pad
  • 41,040
  • 7
  • 92
  • 166
  • Thank you, that's actually a pretty concrete reason. Definitely don't want to be typing `IObservable<'a> -> IObservable<'b>` everywhere instead of `Pattern<'a,'b>`. – yamen Apr 14 '12 at 22:34
  • 2
    A follow up many moons later. I did end up adding a second case to the union, and thus the universe is in balance. – yamen Mar 24 '14 at 21:02
0

From what I understand, single case discriminated union type are there to provide a name that is semantically relevant to your problem domain, to an otherwise general purpose back end type whose name is 'string'.

It is a light abstraction leakage fix for semantics, and only that, AFAIK

nicolas
  • 9,549
  • 3
  • 39
  • 83
  • I don't think that's true. That's what a type alias is. A single case discriminated union is a full class, and in this case it's wrapping a function (or delegate in C# talk). – yamen Apr 14 '12 at 08:55
  • indeed. but I dont know any other use than that... also, note that you can add some methods to union types. – nicolas Apr 14 '12 at 09:00