- Constructors are not tied to types anymore; they exist independently
type rgb = [`Red | `Green | `Blue];
- Polymorphic constructors exist on their own
There is no need to define them beforehand
/* Error: Unbound constructor Int */
Int(123);
/* Success: type `Int(int) */
`Int(123);
/* use the same polymorphic constructor with different arities and/or type parameters */
`Int("abc", true);
`Int(1.0, 2.0, 3.0);
- Extending polymorphic variants
type rgb = [`Red | `Green | `Blue];
type color = [rgb | `Orange | `Yellow | `Purple];
- Type constraints for parameters
/ * let id: (([> `Blue | `Green | `Red ] as ‘a)) => ‘a = <fun>; */
let id = (x: [> `Red | `Green | `Blue]) => x;
let id = (x: [> rgb]) => x;
x has a lower bound and accepts all types that have at least the given three constructors.
- Polymorphic variant abbreviation patterns
type point = [ `Point(float, float) ];
type shape = [
| `Rectangle(point, point)
| `Circle(point, float)
];
let pi = 4.0 *. atan(1.0);
let computeArea = (s: shape) =>
switch s {
| `Rectangle(`Point(x1, y1), `Point(x2, y2)) =>
let width = abs_float(x2 -. x1);
let height = abs_float(y2 -. y1);
width *. height;
| `Circle(_, radius) => pi *. (radius ** 2.0)
};
type shapePlus = [
| `Rectangle(point, point)
| `Circle(point, float)
| `Triangle(point, point, point)
];
let computeAreaPlus = (sp: shapePlus) =>
switch sp {
| `Triangle(p1, p2, p3) => shoelaceFormula(p1, p2, p3)
| `Rectangle(_, _) as r => computeArea(r)
| `Circle(_, _) as c => computeArea(c)
};
// or abbreviation
let computeAreaPlus = (sp: shapePlus) =>
switch sp {
| `Triangle(p1, p2, p3) => shoelaceFormula(p1, p2, p3)
| #shape as s => computeArea(s)
};
- Best practices: normal variants vs. polymorphic variants
Polymorphic:
-
Reuse: A constructor (possibly along with code processing it) is useful for more than one variant. Constructors for colors fall into this category, for example.
-
Decoupling: A constructor is used in multiple locations, but you don’t want those locations to depend on a single module where the constructor is defined. Instead, you can simply use the constructor without defining it.
-
Extensibility: You expect a variant to be extended later on. Similar to how shapePlus was an extension of shape, earlier in this chapter.
-
Conciseness: Due to the global namespace of polymorphic constructors, you can use them without qualifying them or opening their modules (see next subsection).
-
Use constructors without prior definitions: You can use polymorphic constructors without defining them beforehand via variants. That is occasionally convenient for throw-away types that are only used in single locations.