Skip to content

Instantly share code, notes, and snippets.

@AshCoolman
Last active July 25, 2020 08:35
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AshCoolman/91ff0465e2e0aa84928fc7977c16e85d to your computer and use it in GitHub Desktop.
Save AshCoolman/91ff0465e2e0aa84928fc7977c16e85d to your computer and use it in GitHub Desktop.
ReasonML

Language basics

let is just an expression and akin to a function!

let greeting = hello
let greeting = "hi"; /* shadowed */
let scoped = {
	let part1 = "hello";
	let part2 = "world";
};
/* part1 & part2 are not accessible here */
  • Highlight of Reason

Annotations

let score = 10; /* inference */
let score: int = 10;
let i = 5;
let i: int = 5;
let i = (5: int) + (4: int);
let add = (x: int, y: int) : int => x + y;
let drawCircle = (~radius as r: int) : unit => ...; /* "as" means this is a labeled argument */

??? I presume (x, y) => x + y is regular function form

Aliases

type score = int
let x: score = 10;

Design decisions

  1. Types can be inferred
  2. Type coverage always 100%
  3. Type system is sound. As long as code compiles - every integer is an integer
let greeting = "Hello
 world!";

let oneSlash = "\\";

let greetings = "Hi" ++ " and howdy";

let greetingAndOneSlash = {|Hello
World
\
blah..
|};
/* ABOVE: no special chars, hooks for pre-processors */

Useful preprocessors:

let w = {js|世界|js} /* Unicode support */
let w = {j|你好,$world|j} /* Unicode & interpolation char support */
  • Reason std lib for strings

  • Js.String from BuckleScript

  • Don't misuse strings, there are language features for unique ids, identifier into data structure, name of an object field, enum, etc

Design decisions

Though for JS compilation, you'd use [%bs.re] and Js.Re instead, since Str is not available.

??? Do i need to write different code for different targets?

Char

let blah = 'a';

Does NOT support Unicode or UTF-8

Tips & Tricks

  • Char compiles to int (0-255)
  • You can pattern match on chars
  • Convert String->Char "a".[0]
  • Convert Char->String `String.make(1, 'a')
(1,2) == (2,1) /* physical equal */
(1,2) !== (2,1) /* Referential equal */

Usuage

BuckleScript provides bindings to Js true & false. Not interchangable (Js.to_bool and Js.Boolean.to_js_boolean).

Tips & Tricks

Physical equal:

  • handy, but deep
  • some comparisons not clear (e.g. foo equal to lazy foo/)
  • Modular implicits are on their way

Something something variants????

type bool = True | False, no need to hard-code a boolean, but constructors are comiled into less readable representation (here)

  • 32bits, will truncate

  • Careful when binding to JS numbers. Long ones might be truncated.

  • Reason Int32 & Js.Int

  • no Reason Float, Js.Float

  • SERIOUS limitations

Why the heck can't I just use an overloaded + for both int and float? Why is it that each time I find a performant language with great types and interop and community, I find these kind of flaws?

Tuple

https://reasonml.github.io/docs/en/tuple.html

  • immutable
  • ordered
  • fixed-size
  • heterogeneous (w/multiple types)
let ageAndName = (int, string) = (24, "blah");
/* A tuple type alias */
type vector = (float, float, float);
let vel: vector = (1.0, 0.3, 0.0);

Note: no tuple size 1 - just use value

Usage

Can use fst & snd to get first and second elements docs

Normally use destructuring let (_, y, _) = vel

Tips & Tricks

Generally, use tuple for local conveniance, while records for long-lived data that is passed around.

AKA product type, (string, int) is written as string * int in some places. Tuple is really a cartesian product (!!! Thus the exhaustive switch)

Record

Like js objects but:

  • lighter
  • immutable by default
  • fixed in field names/types
  • fast
  • more rigidly typed

Usage

Type:

type person = {
	age: int,
	name: string
};

^ This is required

Value:

let me = {
	age: 5,
	name: "Big reason"
}

Access:

let age = me.age;

Record needs explicit type definition

let me: School.person = {age: 20, name: "blah"}
/* less preferred...*/
let me = School.{age: 30, name:"blah"};
let me = {School.age: 20, name: "blah"};

Immutable Update

Spread operator is:

  • immutable
  • fast

Mutable Update

Use reassignment via =

Punning

type horsePower = { power: int, metric: bool}
let metric = true;
let a = {power: 10, metric}

Same field name/Type can be combined

NOTE: cannot do with single field {foo} (block that returns value foo)

Tips & Tricks

Interop with JavaScript

You can convert between JS objects and records. But a better way without conversion overhead is to use Reason Objects

type payload = {. "name": string };
[@bs.module "myAjaxLibrary"] external sendQuery : payload => unit = "sendQuery"; /* ??? is this an import */
sendQuery({"name": "Reaon"});

??? I don't kow what this does

Dot in type definition signifies object type notation - nothing to do with a record

Record Types are found by field name

type person = {age: int, name: string};
type monstor = {age: int, tentacles: bool};

let getAge = (entitiy) => entity.age; /* infers entity is of type monstor ??? Why not person ??? */

let kraken = {age: 9999, tentacles: true};
let me = {age: 5, name: "Baby guy"};

getAge(kraken);
getAge(me); /* FAILURE - getAge only works with monstor */

If need "duck typing", use Reason objects ??? double-check

Design decisions

  1. Data should be fixed most of the time, hence records are relevant
  2. Variations in data, should be handled by variants
  3. Very fast, as its 2 assembly instructions (field lookup + access)
  4. Name-based typing a.k. explicity type declarations mean type error messages are easier to read. Refactoring is easier; changing a record type's fields lets the compiler know its the same record, but with wrong field names sometimes.
type myResponseVariant =
  | Yes
  | N
  | PrettyMuch;

let crushingIt = Yes;

Most data structures express "this AND that", variants allow "this OR that" ??? Does this mean "this OR other, AND that OR another"

Yes, No, and PrettyMuch are constructors (aka tags)

Note: constructors must be capitilised

Usage

switch allows you to exhaustively check every case of a variant:

let message =
  switch (crushingIt) {
    No => "No worries, keep going"
    Yes => "Great"
    PrettyMuch => "Nice"
  };
/* EDIT: Missing | before constructors
*/

Variant needs and Explicit definition

Bring into separate file

/* Zoo.re *
type animal = Dog | Cat;

/* example.re */
let pet: Zoo.animal = Dog;

Constructor arguments

type account =
  | None
  | Instagram(string)
  | Facebook(string, int)

You can also pattern match

let greeting =
  switch (myAccount) {
  | None => "Hi!"
  | Facebook(name, age) => "Hi " ++ name ++ " you have age of " ++ age
  | Instagram(name) => "Hi" ++ name
  };

Option & List

The standard library includes:

Option

  • simulate nullable values. Reason can default every value to be non-nullable. An int will never be int or null or undefined. If you want a nullable int, you use option(int) - whose values can be None or Some(int) - both must be switched.

List

type list('a) = Empty | Head('a, list('a));

A list that holds a is either empty, or it holds that value plus another list

Reason has sugar for this [1,2,3] aka Head(1, Head(2, Head(3, Empty))). All must be switched, including Empty

Other

Variant like types like string, int float array and data structures can be switched over.

??? How is an int, variant-like

Tips * Tricks

Variants must have constructors

foo = int | string; /* INCORRECT */
foo = Int(int) | String(string); /* correct */

Interop with JavaSCript

Many js functions have many combinations of arguments.

e.g. myLib.draw() takes either number or string

You could do this:

/* reserved for internal use */
[@bs "myLib"] external draw : 'a => unit = "draw";

type animal =
  | MyFloat(float)
  | MyString(string);

let betterDraw = (animal) =>
  switch (animal) {
  | MyFloat(f) => draw(f)
  | MyString(s) => draw(s)
};

But this is better - two externals that compile to the same JS call:

[@bs.module "myLib"] external drawFloat : float => unit = "draw";

[@bs.module "myLib"] external drawString : string => unit = "draw";

BuckleScript provides a few more ways to do this

Variant types are found by field name

Same as records - a function can't accept an arbitrary constructor shared by two variants (if you need this, it is a feature called polymorphic variant)

Design decisions

Unlike branching control flow of if else, which is O(n) (linear), a Reason switch over a variant is compiled to a constant-time format O(1) ??? how

List & Array

  • Homogeneous
  • Immutable
  • Fast at prepending

Reason lists are simple, singly linked lists

Usage

Favours using the following attributes:

~following` preceeding EDIT

Immutable prepend

let myList: list(int) = [1,2,3];
let anotherList = [0, ...myList]; /* List.cons */

The preceeding code does not mutate, and is in constant time (??? one function call of like Head(val, Head(...))

[a, ...b, ...c] /* syntax error, use List.concat instead */

Using an arbitrary item in the middle of a list is discourages, performance & overheard is O(n).

Access

switch is usually used to access list items:

let message =
  switch (myList) {
  | [] => "Its empty"
  | [a, ...rest] => "The head of the list is" ++ sting_of_int(a)
  };

Tips & Tricks

An empty list is a parameter-less variant - which compiles to an integer

Design decisions

Might later create a list data structure with all-around fast perf

Array

Like lists, except:

  • mutable
  • fast Random-access & updates
  • fix-sized on native (flexible on JavaScript)
let myArray = [|"hi", "there"|];

Usage

Stand lib Array and ArrayLabel module. JS compilation has JS.Array bindings API.

let myArray: array(string) = [|"hi", "there"|];
let firstItem = myArray[0]; /* "Hello" */
myArray[0] = "hey";
/* overwrote item 0 */

The above is syntactic sugar for Array.get/Array.set

Tips & Tricks

Reason and JavaScript arrays map against each other - even if fix-sized on native.

Simple:

let greet = (name) => "Hello" ++ name;

Usage:

greet("world!");

Multi arguments comma separated, multi expression functions surrounded in {...}

No argument

A (mathematical) function always has an argument, but in a program we may have a function only for side effects.

Reason functions always have arguments, () is a value called "unit"

Labeled

let addCoordinates = (~x, ~y) => { ... }

addCoordinates(~x=5, ~y=6);

Curried therefore (??? why does currying provide this) arguments can be in any order

let drawCircle = (~radius as r, ~color as c) => { ... }

Note: ~radius on its own, is punning shorthand

Currying

Reason functions can automatically be partially called:

let add = (x, y) => x + y;
let addFive = add(5);
let eleven = addFive(6);

The preceeding is syntactic sugar for:

let add (x) => (y) => x + y;

OCaml optimizes this to avoid unnecessary function allocation

Optional labelled arguments

let drawCircle = (~color, ~radius=?, ()) => {
  setColor(color);
  switch (radius) {
  | None => startAt(1,1)
  | Some(r_) => startAt(r_, r)
  };

radius is obviously being wrapped in Option

Why is there a unit?

Because drawCircle has variable arguments, given a single argument its unclear if an invocation is partial and awaiting a radius, or the programmer's intention was to use the default, thus "unit" is a kind of delimiter:

let curried = drawCircle(~color);
let actualResult = drawCircle(~color, ());

OCaml will presume the optional is omitted when the positional argument is provided.

Explicitly passed optional

When u don't know the arguments:

let result =
  switch (payloadRadius) {
  | None => drawCircle(~color, ())
  | Some(r) => drawCircle(~color, ~radius=r, ())
  };

This is tedius, hence shorthand:

let result = drawCircle(~color, ~radius=?payloadRadius, ());

Optional with default

let drawCircle = (~radius=1, ~color, ()) => {
  setColor(color);
  startAt(radius, radius);
};

Recursive functions

By default, a value can't see a binding that points to it (????), but including the rec keyword in a let binding makes this possible.

rec lets functions see/call themselves:

let rec neverTerminate = () => neverTerminate();

Mutually recursive functions

use rec with and:

let rec callSecond = () => callFirst() /* No semi colon */
and callFirst = () => callSecond();

Tips & Tricks

Declaration:

/* anonymous function. Listed for completeness only */
(x) => (y) => 1;
/* sugar for the above */
(x, y) => 1;
/* assign to a name */
let add = (x, y) => 1;

/* labeled */
let add = (~first as x, ~second as y) => x + y;
/* with punning sugar */
let add = (~first, ~second) => first + second;

/* labeled with default value */
let add = (~first as x=1, ~second as y=2) => x + y;
/* with punning */
let add = (~first=1, ~second=2) => first + second;

/* optional */
let add = (~first as x=?, ~second as y=?) => switch (x) {...};
/* with punning */
let add = (~first=?, ~second=?) => switch (first) {...};

With type annotation:

/* anonymous function */
(x: int) => (y: int): int => 1;
/* sugar for the above */
(x: int, y: int): int => 1;
/* assign to a name */
let add = (x: int, y: int): int => 1;

/* labeled */
let add = (~first as x: int, ~second as y: int) : int => x + y;
/* with punning sugar */
let add = (~first: int, ~second: int) : int => first + second;

/* labeled with default value */
let add = (~first as x: int=1, ~second as y: int=2) : int => x + y;
/* with punning sugar */
let add = (~first: int=1, ~second: int=2) : int => first + second;

/* optional */
let add = (~first as x: option(int)=?, ~second as y: option(int)=?) : int => switch (x) {...};
/* with punning sugar */
/* note that the caller would pass an `int`, not `option int` */
/* Inside the function, `first` and `second` are `option int`. */
let add = (~first: option(int)=?, ~second: option(int)=?) : int => switch (first) {...};

Application:

/* anonymous application. Listed for completeness only */
add(x)(y);
/* sugar for the above */
add(x, y);

/* labeled */
add(~first=1, ~second=2);
/* with punning sugar */
add(~first, ~second);

/* application with default value. Same as normal application */
add(~first=1, ~second=2);

/* explicit optional application */
add(~first=?Some(1), ~second=?Some(2));
/* with punning */
add(~first?, ~second?);

Application w/type annotation

/* anonymous application */
add(x: int)(y: int);

/* labeled */
add(~first=1: int, ~second=2: int);
/* with punning sugar */
add(~first: int, ~second: int);

/* application with default value. Same as normal application */
add(~first=1: int, ~second=2: int);

/* explicit optional application */
add(~first=?Some(1): option(int), ~second=?Some(2): option(int));
/* with punning sugar */
add(~first: option(int)?, ~second: option(int)?);

Standalone type signature

/* first arg type, second arg type, return type */
type foo = int => int => int;
/* sugar for the above */
type foo = (int, int) => int;

/* labeled */
type foo = (~first: int, ~second: int) => int;

/* labeled with default value */
type foo = (~first: int=?, ~second: int=?) => int;

In interface files

Annotate impl. in *.re file

let add: int => int => int;
/* sugar for above */
let add: (int, int) => int;

Note this is not exporting the type - it is only annotating an exiting value bar in the implementation file

Are expressions (yay!)

let message = if (isMorning) {
  "Good morning!"
} else {
  "Hello!"
}

The final else, implicitly gives the value for unit aka (())

e.g. this will cause a type error (both int and unit):

let result = if (showMenu) { 1 + 2};

Ternaries

let message = isMorning ? "yay" : "nay";

Usage

  • Pattern matching supercedes much if/else/ternaries
  • Use if-else if you have 2 branches

Design decisions

Reason ternary is sugar for switch over bool

Type argument!

Types can accept paramters(~generics in other languages)

Approx. type is a function, that takes args and returns a new type

Parameters start with '

Why? to kill duplications.

Before:

type intVector = (int, int, int);
type floatVector = (float, float, float);

let buddy: intVector = (1,2,3);

After:

type vector('a) = ('a, 'a, 'a);

type intVectorAlias = vector(int);

let buddy: intVectorAlias = (1,2,3);
let buddy: vector(floact) = (0.1, 0.2, 0.3);

With inference:

let buddy = (1,2,3); /* vector(int) */

let greetings = ["a", "b"]; /* list(string) */

If types didn't accept params, the standard library would need to define listOfString, listOfInt etc

Types can receive multiple arguments and be composed:

type result('a, 'b) =
  | Ok('a)
  | Error('b);

type payload = {data: string };

type resultCache('errorType) = list(result(payload, 'errorType));

let payloadResults: resultCache(string) = [
  Ok({data: "Nice"}),
  Ok({data: "Also nice"}),
  Error("Some sort of error")
]

Mutually recursive types

type student = {taughtBy: teacher}
and techer = {students: list(student)};

Design decisions

  • Types are pervasive through out Reason - thus unavoidable. Type-level functions allow shorthand expressions (list(students)) AND it allows the creation of small boilerplace (listOfStudents)
  • It creates a trade-off space between fancy types and simple types

Destructuring

let someInts = (10, 20);
let (first, second) = someInts;

type person ={name: string, age: int};
let guy = {name: "Guy" age: 30};
let {name, age:years} = guy;
/* Created name, and years (aliased age) */

let fn = (~person as {name}) => { /* can use name here */ };

Pre-requisite reading is Variant

Usage

Consider a variant:

type payload =
  | Bad(int)
  | Ok(string)
  | None;

While using switch you can "destructure" data into msg and errorCode:

let data = Good("Product shipped");
let message =
  switch (data) {
  |  Good(msg) => "Yay" ++ msg
  |  Bad(errorCode) => "Error encountered" ++ string_of_int(errorCode)
  };

But this is more than desctructing, as the compiler will complain:

Warning 8: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
NoResult

This is pattern matching, conditional and exhaustive.

Note only literals can be a pattern!

The following behaves unexpectedly - it assumes you are matching on any string, and binding that to the name myMessage

let myMessage = "Hello";
switch (greeting) {
| myMessage => print_endline("Hi to you")
};
| _ => /* underscore is a catch all */

When clauses

if sugar:

let message =
  switch (data) {
  | GoodResult(theMessage) => ...
  | BadResult(errorCode) when isServerError(errorCode) => ...
  | BadResult(errorCode) => ... /* otherwise */
  | NoResult => ...
  };

Match on exceptions

switch (List.find((i) => i === theItem, myItems)) {
| item => print_endline(item)
| exception Not_found => print_endline("No such item found!")
};

Nested patterns

switch (student) {
| {name: "Jane" | "Joe"} => ...
| {name: "Bob", Job: Progammer({fullTime: Yes | Maybe})} => ...
};

Patterns everywhere

You can put a pattern anywhere you'd put a variable declaration:

let (Left v | Right v) = i;

??? what does this mean? This (<type> <variable>) doesn't look like previous patterns (<type>). And I can't guess what is going to be resolved against Left v | Right v, on which will then be assigned the value ofi.

Tips & Tricks

Flatten your pattern-match whenever you can

Don't abuse fall-through, try to only use for infinite possibilities

This is tempting:

let optionBoolToJsBoolean = (opt) =>
  switch (opt) {
  | Some(true) => Js.true_
  | _ => Js.false_
  };

But it is non-exhuastive - best to be explicit:

let optionBoolToJsBoolean = (opt) =>
  switch (opt) {
  | Some(true) => Js.true_
  | Some(false)
  | None => Js.false_
  };

Design decisions

Pattern matching corresponds to case analysis in maths - it brings structure to if-else logic.

Mutation

Mutate let-binding

Use ref to allow *mutation

let foo = ref(5);

Get value of ref using ^:

let five = foo^; /* 5 */
let foo = {contents: 5};
let five = foo.contents;
foo.contents = 6;

Before reaching for ref, you can "mutate" by overriding let bindings

let startVal = 1;
let endVal = 3;

for (i in startVal to endVal) {

}

for (i in endVal downto startValue) {
}

while (testCondition) {

  • Reason JSX is not tied to ReactJS; that translate to function calls

Capitalized tag:

<Comp foo={bar} />

becomes

([@JSX] Comp.make(~foo=bar, ~children=[], ()));

Uncapitalized tag:

<div foo={bar}> child1 child2 </div>

becomes

([@JSX] div(~foo=bar, ~children=[child1, child2], ()));

Children withough wrapping in array

<Comp> ...foo </Comp>

This passes the value foo without wrapping:

([@JSX] Comp.createElement(~foo=bar, children=foo, ()));

Usage

JSX tag that shows most features:

<MyComponent
  booleanAttribute={true}
  stringAttribute="string"
  intAttribute=1
  forcedOptional=?{Some("hello")}
  onClick={reduce(handleClick)}>
  <div> {ReasonReact.stringToElement("hello")} </div>
</MyComponent>

Departures from JS JSX

  • Attributes and children don't mandate {}, but they are shown for easy of learning- refmt nukes some and turns some into parens
  • No support for prop spread
  • Punning <input checked /> does not desugar to <input checked=true /> but <input checked=checked />

Tips & Tricks

[@JSX] attribute is a hook for potential ppx macros (????) to spot a function wanting to format as jsx. Once you spot the function to you can turn it into any other expression (????). Thus anyone can use JSX syntax without needing a specific librry e.g. ReasonReact.

JSX calls supports the features of labeled functions: optional, explicitly passed option and optional w/default

Design decisions

What’s the point? Hopefully these were nice demonstrations that the discussion around syntax could a bit more advanced than the typical one surrounding whitespace correction! Especially the first and last examples: part of what Reason tries to accomplish is to insert a layer of iteration speed in-between the slow-moving language semantics and the fast-moving userland libraries. We can observe users and capture relevant patterns, upstream some into the macros, then upstream some of that into the syntax, and finally upstream the worthy parts into the language itself and remove the need for certain libraries and tools in the first place. We can also potentially go the other way around: remove deprecated features by downstreaming them into the syntax, then downstream them into macros, then into libraries, then nothing.

  • Macros (???) + lib-agnostic JSX means every library can have JSX without hassle. Add some visual familiaritie to underlying language without compromising on semantics. Reason wants to let more ppl use OCaml, while discarding debates around syntax and formatting (??? by allowing macros to restructure as libs see fit?)

  • How does react handle single vs multiple children? w/runtime logic, syntax transforms and variadic argument detection/marking (see here). Such dynamic usage is complicated. Reason uses children spread to make wrapping (or not) explicit.

aka Foreign function interface aka interop is how Reason communcates with other languages like C or JavaScript.

external myCFunc  int => string = "theCFunc";

BuckleScript specific external binding:

[@bs.val] external getElementsByClassname : string => array(Dom.element) = "document.getElementsByClassName";

Usage

Use external value/function binding as if it was a normal let binding

Tips & Tricks

Javascript devs need to learn about BuckleScript externals

SMALL ASIDE ON EXTERNAL

external is like let, except the body is a string. This string usually has specific meanings depending on context:

  • native OCAML: usually points to a C function
  • BuckleScript: usually decorated with [@bs.blahblah] attributes

Once declared, you can use an external as a normal value

BuckleScript externals are inlined into their callers during compilation and are competely erased. In practive, when you bind a JavaScript function on the BS side and use it, all trace of such binding disappear from the output (??? compiled file?).

Note Need to use externals and [bs.blahblah] (??? typo ??? Why?) attributes in the interface files. Otherwise the inlining won't happen.

Design decisions

Reason takes interop seriously to help gradual conversion. FFI is good to intergrating with dirty existing code.

Used in exceptional cases, throw a variant

let getItem = (theList) =>
  if (...) {
    /* return the found item here */
  } else {
    raise(Not_found)
  };

let result =
  try (getItem([1, 2, 3])) {
  | Not_found => 0 /* Default value if getItem throws */
  };

Design decisions

  • OCaml/Reason throwing is cheap, Javasript is heavy. These days, OCaml/Reason standard lib come with option returning functions instead. (!!! Thus modern == !throw)

Mostly use record for name:values, this is unlike JavaScript objects.

Usage

Type declaration

  • Not needed

  • closed version, must have this exact (???public???) shape

type tesla = {
  .
  color: string
};
  • open version, can have additional values & methods
type car('a) = {
  ..
  color: string
} as 'a;
  • polymorphic (requires type param)

Creation

type tesla = {.
  drive: int => int
};

let obj: tesla = {
  val hasEnvy = ref(false);
  pub drive = (speed) => {
    this#enableEnvy(true);
    speed
  };
  pri enableEnvy = (envy) => hasEnvy := envy
};
  • In Reason, this always works correctly
type tesla('a) = {
  ..
  drive: int => int
} as 'a;

let obj: tesla({. drive: int => int, doYouWant: unit => bool}) = {
  val hasEnvy = ref(false);    // ??? ref means create on `this`?
  pub drive = (speed) => {
    this#enableEnvy(true);
    speed
  };
  pub doYouWant = () => hasEnvy^; // bool?
  pri enableEnvy = (envy) => hasEnvy := envy
};


obj#doYouWant();

Tips & Tricks

BuckScript's object

  • ## field access
  • #= field mutations
  • always type Js.t
  • compile to JS objects
  • Like mini inline-files
module School = {
  type profession = Teacher | Director;
  
  let person1 = Teacher;
  let getProfession = (person) =>
    switch(person) => {
    | Teacher => "Teach"
    | Director => "Direct"
    };
};
  • contents (values, methods & types) accessed using let bob: School.profession = School.Teacher
module A = {
  module B = {
    let c = "hello";
  };
};

let message = A.B.c;

opening a module

#### Local open

let message =
  School.(
    switch(person1) {
    | Teacher => "Hello teacher!"
    | Director => "Hellow director!"
    }
  );

Global open (avoid)

open School

Extending/Including modules

include statically spreads contents into new module

open (content into your scope so no prefix) != include (copies over definition of a module statically, then opens)

Every .re is a module

  • Implicit module creation is expressive:

Signatures

  • Signature = module's type
  • Can be explicit in .rei file
/* Picking up previous section's example */
module type EstablishmentType = {
  type profession;
  let getProfession: profession => string;
};
  • can have more contents than declared. You can have private enforced contents (implementation details that must exist)
  • profession is abstract - doesn't matter what it is, but it must be the same in any module that falls under the same interface.
module Company : EstablishmentType = {
  type profession = CEO | Designer | Engie;
  let getProfession = (person) => ... ;
  let person1 = ...;
  let person2 = ...;
};
  • Also hides underlying concrete type
  • Extend with include(module type of BaseComponent)

Module functions (functors)

Modules are different layer, so functors are a special case.

  • use module (not let)
  • input/output modules
  • require annotating arguments
module type Comparable = {
  type t;
  let equal: (t,t) => bool;
};

/* this is returned */
module MakeSet (Item: Comparable) => {
  type backingType = list(Item.t);
  let empty = [];
  let add = (currentSet: backingType, newItem: Item.t) : backingType =>
    if (List.exists((x) => Item.equal(x, newItem), currentSet)) {
      currentSet
    } else {
      [
        newItem,
	...currentSet
      ]
     };
}

Module functions types

  • like module types, functor types control visibility.
  • type syntax same for Functors & Function, except types capitalized (as modules)
module type COmparable = ...

module type MakeSetType = (Item: Comparable) => {
  type backingType;
  let empty: backingType;
  let add: (backingType, Item.t) => backingType;
};

module MakeSet: MakeSetType = (Item: Comparable) => {
  ...
};

Drawbacks

- Its different - thus it has non-expected behavior e.g. Cant pass into a tuple

  • Use more normal features like record/function
  • BuckleScript supports js promises
let doSomethingToAPromise = (somePromise) => {
  somePromise
  |> Js.Promise.then_(value => {
    Js.log(value);
    Js.Promise.resolve(value + 2)
  })
  |> Js.Promise.catch(err => {
    Js.log2("Failure!!", err);
    Js.Promise.resolve(-2)
  })
}
  • no async/await yet
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment