Web / Mobile / VR / AR / IoT / AI
Software architect, consultant, author
Performance and compilation time is blazing fast
yarn global add reason-cli@latest-macos
Install Reason CLI
open ReasonML repl
rtop
100% Type inference
Types can be inferred
Tupples
Block scope
Destructuring
Variants
Parameterized variants
Built in variants
We can easily create more complex data structures
Binary tree definition:
Records
record fields can be mutable only if explicitly specified
you can use any types within record. such as tupples or variants
Pattern matching
Looks like switch statement but it's not.
You can destructure into variables when pattern matching
If not covering all use cases it won't compile
Functions
let add = (a,b) => a + b;
There are no nullary functions. Every function return unit type
Destructuring function params
let cross = ((a1, a2, a3), (b1, b2, b3)) => (
a2 * b3 - a3 * b2,
a3 * b1 - a1 * b3,
a1 * b2 - a2 * b1,
);
let computation = (~x, ~y, ~z) => x + y / z;
named params
let cross = (~vector1 as (a1, a2, a3), ~vector2 as (b1, b2, b3)) => (
a2 * b3 - a3 * b2,
a3 * b1 - a1 * b3,
a1 * b2 - a2 * b1,
);
named params destructuring
let add = (~x=0, ~y=0, ()) => x + y;
let add = (~x: int=0, ~y: int=0, ()) => x + y;
defaults
you can use "guards" in pattern matching
functions can be recursive by stating that explicitly
let add = (a,b) => a + b
add(2)(3)
all functions are curried by default
Reverse application operator enables chaining
[4, 2, 1, 3, 5]
|> List.map(x => x + 1)
|> List.filter(x => x < 5)
|> List.sort(compare);
operators are just functions and you can create your own
Under the hood list is self-recursive paramterized variant
type mylist('a) = | Nil; | Cons('a, mylist('a))
let abc = Cons("a", Cons("b", Cons("c", Nil)));
Lists
let abc = ["a", "b", "c"]
Lists
List.nth(someList, 2);
access list item
sort and then reverse
List.rev(
List.sort(
compare,
[8,6,4,3,3,2,6,8,4]
)
);
[8,6,4,3,3,2,6,8,4] |> List.sort(compare) |> List.rev;
Or easier
let test =
switch (someList) {
| [] => "Empty"
| [first, ...last] => "Head of the list is " ++ string_of_int(first)
};
pattern matching on lists
Arrays
sort and then reverse
let myArray = [| "a", "b", "c" |];
myArray[2];
let filterArray = (filter, arr) =>
arr
|> Array.to_list
|> List.filter(filter)
|> Array.of_list;
Filtering
Modules
let make = () => "";
let logStr = (str: string, log: string) => log ++ str ++ "\n";
let print = (log: string) => print_string(log);
Every file is a module Everything is exported from module
You can control how much to export via interfaces. Interface of a module is also called its signature
signatures are defined in rei files.
Log.re
Log.rei
type t;
let make: (unit) => t;
let logStr: (string, t) => t;
let print: (t) => unit;
yarn add bs-platform --dev --exact
yarn add reason-react --exact
Bucklescript
JS primitives: https://rescript-lang.org/apis/latest/js
Installing libraries
{
"dependencies": {
"bs-jest": "^0.1.5"
}
}
{
"bs-dependencies": [
"bs-jest"
],
···
}
Bucklescript(Rescript) objects are like records
type person = {
"age": int,
"name": string
};
type declaration is optional
let me = {
"age": 5,
"name": "Big ReScript"
}
access
let age = me["age"]
FFI
[%raw {|
console.log('here is some javascript for you')
|}];
raw js
type person = {
name: string,
friends: array<string>,
age: int,
}
[@bs.module("MySchool")] external john: person = "john"
let johnName = john.name
var MySchool = require("MySchool");
var johnName = MySchool.john.name;
convert JS object to Record
Bucklescript Externals
[@bs.val] external bindingToBeCalledInReason: typeSignature = "functionNameOnGlobalScope"
external is a keyword for declaring a value in BuckleScript/OCaml/Reason:
[@bs.val] external setTimeout: (unit => unit, int) => float = "";
[@bs.val] external clearTimeout : float => unit = "";
It's like let but the body is a string
Globals
Abstract type
type timeout;
[@bs.val] external setTimeout: (unit => unit, int) => timeout = "";
[@bs.val] external clearTimeout: timeout => unit = "";
Null, Undefined, Option
If you're receiving, for example, a JS string that can be null and undefined, type it as:
[@bs.module "MyConstant"] external myId: Js.Nullable.t(string) = "myId"
To pass nullable string to a module
[@bs.module "MyIdValidator"] external validate: Js.Nullable.t(string) => bool = "validate";
let personId: Js.Nullable.t(string) = Js.Nullable.return("abc123");
let result = validate(personId);
Import Exports
[@bs.module "path"] external dirname : string => string = "dirname";
default imports
[@bs.module "./student"] external studentName : string = "default";
module Decode {
let planet = planet =>
Json.Decode.{
name: planet |> field("name", string),
rotation_period: planet |> field("rotation_period", string),
orbital_period: planet |> field("orbital_period", string),
diameter: planet |> field("diameter", string),
climate: planet |> field("climate", string),
gravity: planet |> field("gravity", string),
terrain: planet |> field("terrain", string),
surface_water: planet |> field("surface_water", string),
population: planet |> field("population", string),
residents: planet |> field("residents", array(string)),
films: planet |> field("films", array(string)),
created: planet |> field("created", string),
edited: planet |> field("edited", string),
url: planet |> field("url", string)
}
let planets = json => Json.Decode.list(planet, json);
let result = json => Json.Decode.{
count: json |> field("count", int),
previous: json |> field("previous", optional(string)),
next: json |> field("next", optional(string)),
results: json |> field("results", array(planet))
}
}
let print_decoded_planets = _ => {
Js.Promise.(
Fetch.fetch("https://swapi.co/api/planets")
|> then_(Fetch.Response.json)
|> then_(
json => {
let response = Decode.result(json);
response.results |> Array.to_list |> List.iter((planet) => {
switch (planet) {
| plnt => print_endline(plnt.name)
}
})
resolve(json);
}
)
|> then_(
json => json |> Js.Json.stringify
|> resolve
)
|> catch(err => {
Js.log(err)
resolve("")
}
)
);
}
Json encoding and decoding with bs-json
Js.Promise.(
Fetch.fetch("https://swapi.co/api/planets")
|> then_(Fetch.Response.json)
|> then_(
json => Js.Json.stringify(json)
|> print_endline
|> resolve
)
);
Fetching data with bs-fetch
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
switch (ReactDOM.querySelector("#root")) {
| Some(root) => ReactDOM.render(<React.StrictMode><div>{"Hello" |> ReasonReact.string }</div></React.StrictMode>, root)
| None => ()
}
First let's convert rendering to DOM
To bring in our App component from JS we need to type it explicitly for Reason.
module App = {
[@bs.module "./App.js"][@react.component]
external make: (_) => React.element = "default";
}
For that we can use one Bucklescript external
[@bs.module "./serviceWorker.js"]
external unregister: _ => unit = "unregister"
unregister();
Now let's convert App component
[@bs.val] external require: string => string = "";
let logo = require("./logo.svg");
require("./App.css");
The rest is pretty straightforward
[@react.component]
let make = () => {
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
{ "Edit <code>src/App.js</code> and save to reload." |> ReasonReact.string }
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
{"Learn React" |> ReasonReact.string}
</a>
</header>
</div>
}
@VladimirNovick