Quick overview:Variants
Variant types model values that may assume one of many known variations. Thisfeature is similar to "enums" in other languages, but each variant form mayoptionally specify data that is carried along with it.
This is a very powerful feature of the Reason language when used withpattern matching.
Variants require a type definition that specifies all of the "constructors",or "tags", for that variant. Constructors must start with an uppercase letter.
Here the constructors for theanimal variant areCat,Dog,Horse,andSnake:
type animal = |Cat |Dog |Horse |Snake;To create a value of typeanimal use one of the constructors:
let fluffy =Cat;let spot =Dog;Variant constructors can have data associated with them:
type linkedList = |Node(int, linkedList) |Empty;The constructors can now accept their data as arguments. Creating a linkedlist[1, 2, 3] looks like:
let x =Node(1,Node(2,Node(3,Empty)));Note: This is how the corelist structure isactually implemented. The constructors are just hidden behind some syntax.
The primary way to interact with variants is throughpattern matching. When pattern matching over a variantthe compiler can ensure that all cases are covered exhaustively so that thereis no undefined behavior. This is also helpful when adding new cases to avariant later, because the compiler will present a clear list of code that needsto be updated.
let interact = (animal) => {switch (animal) { |Cat =>"pet" |Dog =>"throw ball" |Horse =>"ride" |Snake =>"run away" };};It is possible to interact with a constructor's data while pattern matching:
let checkFirst = (linkedList) => {switch (linkedList) { |Node(value, _) =>"First is:"++ string_of_int(value) |Empty =>"The list is empty" };};Options are a commonly used variant built into the Reason language. Theyrepresent the presence or absence of a value. It is similar to the concept of"nullable" values in other languages.
Details:Options
Often times variants will hold a single predefined record type:
type cat = { name: string,};type dog = { breed: string,};type animal = |Cat(cat) |Dog(dog);let x =Cat({name:"Fluffy"});If that record type is only ever used within that single variant, it can bemore concise to define the variant and record type at the same time usinginline records:
type animal = |Cat({name: string}) |Dog({breed: string});let x =Cat({name:"Fluffy"});Note: This is a somewhat recent language feature and not all online REPL'swill support this syntax. Any recent version of Reason will support inlinerecords though.
The variant type needs to be in scope when referencing any constructor of thatvariant. An easy way to fix this error is with an explicit type annotation:
moduleAnimal = {type t = |Cat |Dog |Horse |Snake;};/* Error: Unbound constructor */let x =Cat;/* No error */let y:Animal.t =Cat;There is a subtle difference between constructors that accept two arguments andconstructors that accept one argument that is a 2-tuple. Pay close attentionto which kind of variant you are dealing with:
type oneArgument = |OneArg((int, string));type twoArguments = |TwoArgs(int, string);let x =OneArg((1,"one"));let y =TwoArgs(2,"two");Using a one-argument variant that accepts a 2-tuple is common when dealing withstructures that usetype parameters.list((int, int)) is used becauselist(int, int) is not valid.
In some cases it can be tempting to write a variant that is "un-tagged":
type t = | int | string;let x: t =100;let x: t ="one";The Reason languagedoes not support this feature. All variantsmusthave constructors. The above code is actually a syntax error. Instead write:
type t = |Int(int) |String(string);let x: t =Int(100);let x: t =String("one");