If you come from OCaml or are a newcomer reading a tutorial written on OCaml, this guide's for you! But don't forget that reason-tools can convert between OCaml and Reason syntax on the fly.
OCaml | Reason |
---|---|
(* OCaml (*nest*) *) |
/* Reason /*nest*/ */ |
Reason has all of OCaml's infix operators, but a couple of operators are expressed differently. In Reason, structural equality is written as ==
, and reference (physical) equality is written as ===
. In Reason, to achieve the corresponding inequality, simply swap the first character with a !
character. (!=
for structural inequality, and !==
for reference inequality).
Equality | OCaml | Reason |
---|---|---|
Structural | x = y |
x == y |
Reference | x == y |
x === y |
Inequality | OCaml | Reason |
---|---|---|
Structural | x <> y |
x != y |
Reference | x != y |
x !== y |
Reason's lexical scoping is exactly the same as OCaml's, but let bindings syntactically resemble "block scope" which is more familiar to many developers. In Reason, they are created with {}
braces, which may contain both let
bindings and imperative commands, separated by ;
. All blocks evaluate to the last line and the semicolon on the last line is optional. {}
braces are only needed if you have more than one item to chain together via ;
.
OCaml | Reason |
---|---|
let _ = let msg = "Hello" in print_string msg; let msg2 = "Goodbye" in print_string msg2 |
{ let msg = "Hello"; print_string msg; let msg2 = "Goodbye"; print_string msg2 }; |
Reason's {}
syntax removes many commonly reported pain points in OCaml's syntax:
begin
/end
is removed entirely.In Reason, everything that can go between the {}
in Local Scopes and in module bodies. You can usually even cut/paste code between the two contexts. In OCaml, the syntaxes for the two contexts are very different. Local scope requires trailing in
, but module bodies do not and some imperative statements must be assigned to _
or ()
, or else use double ;;
.
OCaml Module Body | Reason Module Body |
---|---|
let ten = 10 let () = imperativeFunc ten ten let () = imperativeFunc 0 0 |
let ten = 10; imperativeFunc ten ten; imperativeFunc 0 0; |
let ten = 10;; imperativeFunc ten ten;; imperativeFunc 0 0;; |
Same as above |
OCaml Local Scope | Reason Local Scope |
let ten = 10 in let _ = imperativeFunc ten ten in imperativeFunc 0 0 |
same as above |
let ten = 10 in begin imperativeFunc ten ten; imperativeFunc 0 0 end |
same as above |
let ten = 10 in ( imperativeFunc ten ten; imperativeFunc 0 0 ) |
same as above |
In Reason, tuples always require parentheses.
OCaml | Reason |
---|---|
let tup = 4, 5 |
let tup = (4, 5); |
let tup = ((1: int), (2:int)) |
let tup = (1: int, 2:int); |
fun ((a: int), (b: int)) -> a |
fun (a: int, b: int) => a; |
In Reason, record values resemble JavaScript, using :
instead of =
. Because Reason tuples always require wrapping parens, records may contain lambdas as values without needing extra parens.
OCaml | Reason |
---|---|
let myRec = {x = 0; y = 10} |
let myRec = {x: 0, y: 10}; |
let myFuncs = { myFun = (fun x -> x + 1); your = (fun a b -> a + b); } |
let myFuncs = { myFun: fun x => x + 1, your: fun a b => a + b }; |
OCaml | Reason |
---|---|
let list = [1; 2; 3] |
let list = [1, 2, 3] |
let list = hd :: tl |
let list = [hd, ...tl]; |
OCaml Tuple | Reason Tuple |
---|---|
type tuple = int * int |
type tuple = (int, int); |
let tup: tuple = (10, 30) |
let tup: tuple = (10, 30); |
OCaml Record | Reason Record |
---|---|
type r = {x: int; y: int} |
type r = {x: int, y: int}; |
let myRec: r = {x = 0; y = 10} |
let myRec: r = {x: 0, y: 10}; |
OCaml Function | Reason Function |
---|---|
type func = int -> int |
type func = int => int; |
let x: func = fun a -> a + 1 |
let x: func = fun a => a + 1; |
OCaml | Reason |
---|---|
let x a b = e |
let x a b => e; |
let x = fun a b -> e |
let x = fun a b => e; |
let x = fun a -> fun b -> e |
let x = fun a => fun b => e; |
OCaml has a function definition (function |
) which is considered to be
equivalent of function a -> match a with ...
. Reason has the same, but
the syntax makes it clear how it is actually an extension of a single argument
function. The single case match is a natural extension of the simple lambda,
and the multicase lambda is a natural extension of the single case lambda.
Form | OCaml | Reason |
---|---|---|
lambda |
fun pat -> e |
fun pat => e |
one match case |
function | pat -> e |
fun | pat => e |
many cases |
function | pat -> e | pat2 -> e |
fun | pat => e | pat2 => e |
In both Reason and OCaml, arguments are annotated with types by (as with
everything else), wrapping them in parenthesis after appending
:typeAnnotation
.
fun (arg : argType) => returnValue;
fun (arg : argType) => fun (arg2 : arg2Type) => returnValue;
fun (arg : argType) (arg2 : arg2Type) => returnValue;
Both Reason and OCaml allow annotating the return type, when using the "super sugared let binding" form.
(* OCaml *)
let myFunc (a:int) (b:int) :int * int = (a, b)
let myFunc (a:int) (b:int) :int list = [1]
let myFunc (a:int) (b:int) :int -> int = fun x -> x + a + b
/* Reason */
let myFunc (a:int) (b:int) :(int, int) => (a, b);
let myFunc (a:int) (b:int) :list int => [1];
let myFunc (a:int) (b:int) :(int => int) => fun x => x + a + b;
Because we're using =>
for all functions everywhere in Reason, there's
one case where we need to add extra parens around a return type that is
itself a function type.
OCaml's type applications (think "generics"), are applied in reverse order.
With OCaml, there are some unintuitive consequences of this.
let x: int list = [2]
type listOfListOfInts = int list list
(* Parsed as: *)
type listOfListOfInts = (int list) list
Things get even more strange when type constructors accept multiple parameters.
Multiple arguments require parenthesis and commas to separate type parameters,
but those parentheses don't represent tuples. The parentheses/comma form must
also be given when constructing type instances such as (int, string) tuple
.
type ('a, 'b) tuple = 'a * 'b
type listOfTuplesOfStringAndInt = (string, int) tuple list
(* Which is parsed as: *)
type listOfTuplesOfStringAndInt = ((string, int) tuple) list
(* Which allows a list of (tuples of (string and int)) *)
let tuples: listOfTuplesOfStringAndInt = [("asdf", 3)]
In summary, Reason unifies almost all of the syntax into simple "function application" style meaning that type parameters follow the same space-separated list pattern seen everywhere else in the syntax. As with everything else, parentheses may be used to enforce precedence. This results in fewer syntactic patterns to learn.
For example, you can imagine list
being a "function" for types that accepts a
type and returns a new type.
OCaml | Reason |
---|---|
let x: int list = [2] type listOfListOfInts = int list list type ('a, 'b) tup = ('a * 'b) type pairs = (int, int) tup list let tuples: pairs = [(2, 3)] |
let x: list int = [2]; type listOfListOfInts = list (list int); type tup 'a 'b = ('a, 'b); type pairs = list (tup int int); let tuples: pairs = [(2, 3)]; |
Because OCaml uses parens and commas to represent multiple arguments to type constructors, it's confusing when one of the arguments to a type constructor is itself a tuple. In OCaml, it's difficult to remember the difference between a type constructor accepting multiple arguments and a type constructor accepting a single argument which happens to be a tuple.
The following examples shows the difference between passing two type
parameters to pair
, and a single type parameter that happens to be a tuple.
OCaml | Reason |
---|---|
type intPair = (int, int) pair |
type intPair = pair int int; |
type pairList = (int * int) list |
type pairList = list (int, int); |
OCaml |
Reason |
---|---|
type myVariant = | HasNothing | HasSingleInt of int | HasSingleTuple of (int * int) | HasMultipleInts of int * int | HasMultipleTuples of (int * int) * (int * int) |
type myVariant = | HasNothing | HasSingleInt int | HasSingleTuple (int, int) | HasMultipleInts int int | HasMultipleTuples (int, int) (int, int); |
let a = HasSingleInt 10 let a = HasSingleTuple (10, 10) let a = HasMultipleInts (10, 10) let a = HasMultipleTuples ((10, 10), (10, 10)) |
let a = HasSingleInt 10; let a = HasSingleTuple (10, 10); let a = HasMultipleInts 10 10; let a = HasMultipleTuples (10, 10) (10, 10); |
let res x = match x with | HasNothing -> 0 | HasSingleInt x -> 0 | HasSingleTuple (x, y) -> 0 | HasMultipleInts (x, y) -> 0 | HasMultipleTuples ((x, y), (q, r)) -> 0 |
let res x = switch x { | HasNothing => 0 | HasSingleInt x => 0 | HasSingleTuple (x, y) => 0 | HasMultipleInts x y => 0 | HasMultipleTuples (x, y) (q, r) => 0 }; |
OCaml | Reason |
---|---|
let res = match x with | A (x, y) -> match y with | None -> 0 | Some i -> 10 | B (x, y) -> 0 |
let res = switch x { | A (x, y) => switch y { | None => 0 | Some i => 10 } | B (x, y) => 0 }; |
Can you spot the error in the OCaml example? This is one of the most common mistakes among OCaml programmers. The nested match
must be wrapped in parentheses, otherwise the Some
case is parsed as belonging to the outer match
. Visually, it's actually:
let res = match x with
| A (x, y) -> match y with
| None -> 0
| Some i -> 10
| B (x, y) -> 0
Reason's mandatory {}
around switch
cases prevents this issue.
OCaml | Reason |
---|---|
module type MySig = sig type t = int val x: int end module MyModule: MySig = struct type t = int let x = 10 end module MyModule = struct module NestedModule = struct let msg = "hello"; end end |
module type MySig = { type t = int; let x: int; }; module MyModule: MySig = { type t = int; let x = 10; }; module MyModule = { module NestedModule = { let msg = "hello"; }; }; |
OCaml | Reason |
---|---|
module type FType = functor (A: ASig) -> functor (B: BSig) -> Result |
module type FType = (A: ASig) => (B: BSig) => Result; |
OCaml | Reason |
---|---|
module F = functor (A: ASig) -> functor (B: BSig) -> struct end |
module F = fun (A: ASig) => fun (B: BSig) => {}; |
module F = functor (A: ASig) (B: BSig) -> struct end |
module F = fun (A: ASig) (B: BSig) => {}; |
module F (A: ASig) (B: BSig) = struct end |
module F (A: ASig) (B: BSig) => {}; |
module Res = F(A)(B) |
module Res = F A B; |
Note: There is currently a known inconsistency where functors do not
conform to function application syntax when in type annotation position - see
the Reason repo's formatTest/modules.re
.
OCaml doesn't require parens around sequences (a;b;c;d)
or tuples (x,y)
, so
that ends up ruling out a bunch of other very convenient syntax rules. Since
Reason always uses {}
to enclose sequences or let bindings, and Reason
always requires ()
around tuples, many other syntax constructs are expressed
more intuitively, without requiring extra wrapping in parenthesis.
This is a welcomed improvement because the OCaml type errors the user would
see were very confusing when it would believe the function's return value
was a tuple with infix ,
comma.
OCaml | Reason |
---|---|
let myFuncs = { myFun = (fun x -> x + 1); your = (fun a b -> a + b); } |
let myFuncs = { myFun: fun x => x + 1, your: fun a b => a + b } |
OCaml | Reason |
---|---|
let x = match prnt with | None -> fun a -> blah (* Extra () required ! *) | Some "_" -> (fun a -> ()) | Some "ml" -> blah |
let x = switch prnt { | None => fun a => blah | Some "_" => fun a => () | Some "ml" => blah }; |
OCaml | Reason |
---|---|
let tuple = ((fun x -> x), 20) |
let tuple = (fun x => x, 20); |
let tuple = (("hi": string), (20: int)) |
let tuple = ("hi": string, 20: int); |
as
precedenceWith Reason, as
has a higher precedence than |
bar. This allows creating as
aliases
for entire rows in pattern matching.
OCaml | Reason |
---|---|
let ppp = match MyThing 20 with | (MyThing x as ppp) | (YourThing x as ppp) -> ppp; |
let ppp = switch (MyThing 20) { | MyThing x as ppp | YourThing x as ppp => ppp; }; |
let | (MyThing _ as ppp) | (YourThing _ as ppp) = ppp; |
let | MyThing _ as ppp | YourThing _ as ppp = ppp; |
Because equalities and their negations have been made more consistent in Reason,
the =
operator is available for mutable field update.
OCaml | Reason |
---|---|
myRec.field <- "next" |
myRec.field = "next" |
In Reason, !
and other prefix operators have lower precedence than dot .
or send #
.
This is more consistent with what other languages do, and is more practical
when (or if) the !
symbol is used to represent boolean not
.
OCaml | Reason |
---|---|
let x = !(foo.bar) |
let x = !foo.bar; |
let x = !(foo#bar) |
let x = !foo#bar; |
let x = !(!foo.bar) |
let x = !(!foo).bar; |
let x = !(!foo#bar) |
let x = !(!foo)#bar; |
let x = !(!(foo.bar)) |
let x = !(!foo.bar); |
let x = !(!(foo#bar)) |
let x = !(!foo#bar); |
let x = !!(foo.bar) |
let x = !!foo.bar; |
let x = !!(foo#bar) |
let x = !!foo#bar; |
let x = !~(foo.bar) |
let x = !~foo.bar; |
let x = !~(foo#bar) |
let x = !~foo#bar; |
Because Reason uses C-style comments, some obscure custom prefix/infix operators must be written differently. The rules for prefix/infix operators are the same as in OCaml syntax, but with the following exceptions:
Specifically, if any character except the first in an prefix/infix operator is a star or forward slash, that must be first escaped with a backslash. These will be parsed without the backslash when added to the AST. When reprinted, the escape backslashes are added back in automatically.
OCaml | Reason |
---|---|
let (/*) a b = a + b |
let (/\*) a b => a + b; |
let x = 12 /-* 23 /-* 12 |
let x = 12 /-\* 23 /-\* 12; |
let y = (/*) a b |
let y = (/\*) a b; |
let (!=*) q r = q + r |
let (!=\*) q r => q + r; |
let res = q (!=*) r |
let res = q (!=\*) r; |
let (!=/*) q r = q + r |
let (!=\/\*) q r => q + r; |
let res = q (!=/*) r |
let res = q (!=\/\*) r; |
If Reason uses ==
to represent OCaml's =
, and
uses ===
to represent OCaml's ==
, then how would Reason represent OCaml's
===
symbol (if it were defined)? Reason provides a way! "Escape" the triple
equals symbol!
Identifier | Meaning | OCaml | Reason |
---|---|---|---|
"===" |
Custom value | x === y |
x \=== y |
In Reason's repl rtop
(a customized utop
), each input is submitted via a single ;
semicolon. OCaml's repl requires two semicolons ;;
.
OCaml | Reason |
---|---|
;; |
; |