Get started with Reason
genkio opened this issue · 0 comments
study notes of Nik Graf's Get Started with Reason course on egghead .
TOC
- reason-cli and rtop
- basic data types and operators
- let binding, type inference and type aliases
- scope
- if else and switch
- records
- variants and pattern matching variants using switch
- eliminate illegal states with variants
- type option
- declare functions
- tuple
- list
- array
- reference equality vs structural (deep) equality
- pattern matching using switch
- type parameters
- mutable let binding
- exceptions
- imperative loops (for & while)
- modules
- first steps using Reason with BuckleScript
reason-cli and rtop
installation
npm install -g reason-cli@latest-macos
start with rtop
$ rtop
ctrl r
for searching histories.
basic data types and operators
float type
+.
for adding floats.- use utility functions like
float_of_int
(destinationType_of_sourceType
) for type conversion.
string and char type
++
for string concatenation.- string must in double quotes, single quote (with single character) is for char type.
unit type
();
which is equivalent of undefined in JavaScript.- use for functions with side-effects, for example
print_int(42);
returns type unit.
let binding, type inference and type aliases
let binding
- similar to variable declaration.
let <name>:<type> = <expression>;
- let binding is immutable, however can be shadowed by binding same name with different expression (expression with different types would also be fine).
type alias
type score = int;
thenlet x:score = 100;
ortype scores = list(score);
- type alias can also be shadowed.
scope
lexical scoping
- create local scope using curly braces
{ 42; };
- last state will automatically be returned in multi-lines block.
{
print_endline("hello");
42;
};
- let binding defined inside of scoped can't be accessed outside, and it can shadow the outside binding.
- bind result of a scope to a name
let meaningOfLife = {
let a = 40;
let b = 1;
a + b + 1;
};
let meaningOfLife: score = 42;
if else and switch
if, is an expression, and can be bound to let binding
let isMorning = true;
let greeting = if (isMorning) { "Good Morning"; } else { "Hello"; };
greeting;
- : string = "Good Morning"
every branch of if else must be evaluated to the same type
let greeting = if (isMorning) { "Good Morning"; } else { 42; };
Error: This expression has type score but an expression was expected of type string
if can be used with functions with side-effects.
if (isMorning) { print_endline("Good Morning") };
same as if (isMorning) { print_endline("Good Morning") } else {()};
switch pattern
/*
switch (<value>) {
| <pattern1> => <case1>
| <pattern2> => <case2>
};
*/
switch with fall through handling.
let lamp =
switch (1) {
| 0 => "off"
| 1 => "on"
| _ => "off"
};
let lamp: string = "on";
alternative of handling fall through.
let lamp =
switch (1) {
| 0 => "off"
| 1 => "on"
| other => {
print_endline("Invalid value: " ++ string_of_int(other));
"off";
}
};
records
stores various types of data into one, and reference them by name.
type person = { name: string, age: int };
- : person = { name: "Joe", age: 35 }
accessing the records field, same as JavaScript, with dot notation.
let joe = { name: "Joe", age: 35 };
joe.name;
records fields destructing, again, same as JavaScript
let { name: joesname, age: joesage } = joe;
/*and, punning shorthand syntax*/
let { name } = joe;
use punning shorthand to create records (just like es6 :)
let name = "joe";
let age = 35;
let joe = { name, age };
immutable update records with spread operator.
let joe = { ...joe, name: "john" };
mutable update records with = operator.
/*creating mutable field*/
type animal = { species: string, mutable scary: bool };
let lion = { species: "Lion", scary: true };
/*mutate field value*/
lion.scary = false;
create a record without declaring its type by declaring its fields using pub keyword.
let anna = { pub name = "Anna", pub eyeColor = "brown" };
/*access fields*/
anna#name;
variants and pattern matching variants using switch
similar to enum yet more powerful.
type answer =
| Yes /*this is known as constructor, and it has to be capital cased*/
| No
| Maybe;
enrich the type system together with switch
let isReasonGreat = Yes;
let message =
switch (isReasonGreat) {
| Yes => "Yay"
| No => "Nope"
| Maybe => "Maybe" /*if any one of the constructor is missing here, a type error will be shown*/
};
let message: string = "Yay";
variant constructor can be used to hold data
type item =
| Note(string)
| Todo(string, bool);
let newTodo = Todo("new todo", false);
switch(newTodo) {
| Note(text) => text
| Todo("new todo", false) => "exact match found";
| Todo(text, isDone) => text ++ " is done: " ++ string_of_bool(isDone);
};
- : string = "new todo is done: false"
eliminate illegal states with variants
type request =
| Loading
| Error
| Success(string);
let state = Error;
let ui =
switch(state) {
| Loading => "loading..."
| Error => "Something went wrong"
| Success("") => "Your name is missing"
| Success(name) => "Your name is " ++ name
};
let ui: string = "Something went wrong";
type option
None and Some
let meaningOfLife = None;
let meaningOfLife = 42;
let message =
switch(meaningOfLife) {
| None => "Sadly I don't know"
| Some(value) => "The meaning of life is :" ++ value
};
declare functions
starting simple, exact same as JavaScript
let plusOne = x => x + 1;
plusOne(3);
- : int = 4
with type declared
let add = (x: int, y: int): int => x + y;
add(2, 2);
- : int = 4
with block
let add = (x, y) => {
let z = float_of_int(x);
y +. z; /*as explained above, this will be returned automatically, AND there's actually no return keyword in Reason*/
};
partial argument, partial application and currying
let add = (x, y) => x + y;
let addTwo = add(2);
addTwo(2);
- : int = 4
/*or*/
add(2)(2);
/*another example*/
let numbers = [4, 11, 5];
let add = (x, y) => x + y;
List.map(x => add(4, x), numbers);
/*or*/
List.map(add(4), numbers);
- : list(int) = [8, 15, 9]
label parameter
let name = (~firstName, ~lastName) => firstName ++ " " ++ lastName;
name(~lastName="doe", ~firstName="joe");
- : string = "joe doe"
/*works really well with partial application*/
name(~lastName="doe")(~firstName="joe");
/*as syntax*/
let name = (~firstName as f, ~lastName as l) => f ++ " " ++ l;
/*with default value*/
let name = (~firstName, ~lastName="doe") => firstName ++ " " ++ lastName;
/*calling function with default parameter to get the output value instead of curried function*/
let name = (~firstName, ~lastName="doe", ()) => firstName ++ " " ++ lastName;
name(~firstName="joe", ());
/*with parameter as option type*/
let name = (~firstName, ~lastName=?, ()) => {
switch(lastName) {
| Some(value) => firstName ++ " " ++ value
| None => firstName
};
};
name("joe", ());
name(~firstName="joe", ~lastName="doe", ());
chain functions using the pipe/reverse-application operator
/*normally you would do*/
let info = String.capitalize(String.lowercase("ALERT"));
/*with Reason*/
let info = "ALERT" |> String.lowercases |> String.capitalize;
/*even cooler*/
[8,3,6,1]
|> List.sort(compare)
|> List.rev
|> List.find(x => x < 4);
recursive function
tuple
the immutable and fixed position data structure that can hold values of different types.
/*start simple*/
("joe", 42);
/*with different types*/
(42, true, "joe");
/*nested*/
((4, 3), 32);
/*with custom type*/
type point = (int, int);
let myPoint: point = (4, 3);
/*util functions to access first and second element*/
fst(myPoint);
snd(myPoint);
/*destructuring*/
let (x, y, z) = (1, 2, 3);
/*partial destructuring*/
let (_, _, z) = (1, 2, 3);
list
the homogeneous (can't have items with different types) and immutable data structure
/*start simple*/
let myList = [1, 2, 3];
/*create list out of variants*/
type item =
| Measurement(int)
| Note(string);
[Measurement(2), Note("hello")];
/*append list with util function*/
List.append([1,2,3], [4,5]);
/*append list with shorthand syntax*/
[1,2] @ [3,4];
/*with spread operator which only can do append not prepend*/
[0, ...[1,2,3]];
/*access the nth item*/
List.nth([2, 3], 0);
/*access item with switch*/
let message =
switch(myList) {
| [] => "This list is empty"
| [head, ...rest] => "The head of the list is " + string_of_int(head)
};
/*util functions*/
List.map(x => x + 1, [1, 2, 3]);
array
like list but mutable.
/*create array*/
let myArray = [|2, 3|];
/*accessing and mutating the element*/
myArray[0];
myArray[0] = 1;
reference equality vs structural (deep) equality
==
structural equality works all data structures, it does deep comparison so it works with nested structure as well.
!=
checks if two structures aren't equal.
===
checks if comparators share the same memory locations, which does not use that often, shouldComponentUpdate hook maybe one of the places it could be useful.
{ name: "joe" } === { name: "joe" }
- : bool = false
let joe = { name: "joe" }
joe === joe;
- : bool = true
joe === { name: "joe" }
- : bool = true
pattern matching using switch
start simple with matching structural equality
switch("Hello") {
| "Hello" => "English"
| "Bonjour" => "French"
| _ => "Unknown"
};
/*partial check*/
let myTodo = ("study Reason", false);
switch(myTodo) {
| (_, true) => "Congrats"
| (_, false) => "Too bad"
};
/*or extract part of the structure with name*/
let myTodo = ("study Reason", false);
switch(myTodo) {
| (_, true) => "Congrats"
| (text, false) => "Too bad, " ++ text ++ " is not finished"
};
/*extract head and tail from list using spread operator*/
switch(["a", "b", "c"]) {
| [head, ...tail] => print_endline(head) /*note, destrucuring list outside of switch is not recommended, as empty list can lead to runtime error*/
| _ => print_endline("all other cases")
};
/*matching array*/
switch([|"a", "b", "c"|]) {
| [|"a", "b", _|] => print_endline("a, b and something")
| [|_, x, y|] => print_endline("something and " ++ x ++ " " ++ y)
| _ => print_endline("an array")
};
/*matching records*/
type todo = {
text: string,
checked: bool
};
let myTodo = { text: "study Reason", checked: false };
switch(myTodo) {
| {text, checked: true} => "Congrats, you finished: " ++ text /*panning*/
| {text, checked: false} => "Too bad, you didn't finish: " ++ text
};
/*matching variant*/
type item =
| Note(string)
| Todo(string, bool);
let myTodo = Todo("study Reason", false);
switch(myTodo) {
| Note(text) => text
| Todo(text, checked) => text ++ " is done: " ++ string_of_bool(checked)
};
use pipe to match multiple possible values
type request =
| Success(string)
| Error(int);
switch(Error(502)) {
| Success(result) => result
| Error(500 | 501 | 502) => "server error"
| Error(code) => "unknown error"
};
use custom matching logic with when
keyword
let isServerError = code => code >= 500 && code <= 511;
switch(Error(502)) {
| Success(result) => result
| Error(code) when isServerError(code) => "server error"
| Error(code) => "unknown error"
};
when using pattern matching with variants, try apply handling logic for every variant constructor and avoid the fall through case as well as to use when
, explicitness is the key to avoid potential bugs in the program.
ternary operator, syntax sugar for boolean switching
let message = isMorning ? "Good morning" : "Hello";
/*is the syntactical sugar for*/
switch(isMorning) {
| true => "Good morning"
| false => "Hello"
};
type parameters
type can accept parameters similar to generics
let mylist: list(string) = ["hello", "world"];
type can be treated as function, which take in the parameter and return a new type. It helps to reduce repetition and create more generic types.
type coordinate('a) = ('a, 'a);
let locationOne: coordinate(int) = (10, 20);
let locationOne: coordinate(float) = (10.0, 20.2);
/*create our own option type*/
type ourOption('a) =
| OurNone
| OurSome('a);
ourSome(42);
ourSome("Reason");
/*to take multiple parameters*/
type ship('a, 'b) = {
id: 'a,
cargo: 'b
};
{ id: 223, cargo: ["Apple"] };
mutable let binding
/*create ref*/
let foo = ref(5);
/*mutate ref*/
foo := 6;
/*retrieve ref*/
foo^;
/*a simple example*/
let game =
| Menu
| Playing
| Gameover;
let store = ref(Playing);
store := GameOver;
store := Menu;
exceptions
raise exception
/*raise exception*/
raise(Not_found);
create exception
exception InputClosed(string);
raise(InputClosed("the stream has closed"));
handle exception
/*handle exception, exceptions are another types of variants*/
try (raise(Not_found)) {
| Not_found => ":("
};
/*exception keyword*/
try (List.find(x => x == 42), []) {
| item => "Found it"
| exception Not_found => "Not found"
};
imperative loops (for & while)
for loop
for (x in 2 to 8) {
print_int(x);
print_string(" ");
};
for (x in 8 downto 2) {
print_int(x);
print_string(" ");
};
while
let x = ref(0);
while (x^ < 5) {
print_int(x^);
x := x^ + 1;
};
modules
a simple module
module Math = {
let pi = 3.14;
let add = (x, y) => x + y;
};
Math.pi;
Math.add(2, 3);
wrap type definition with module
module School = {
type profession =
| Teacher
| Director;
};
let personOne = School.Teacher;
open module, shorthands for accessing what's inside of an module
let greeting =
School.(
switch (personOne) {
| Teacher => "hello teacher"
| Director => "hello director"
};
);
/*local open also work with other data types*/
module Circle = {
type point = {
x: float;
y: float;
};
};
let center = Circle.{x: 1.2, y: 2.3};
/*use open keyword to open module to bring module's definition to current scope, be careful though*/
open School;
let personTwo = Teacher;
extend module with keyword include
module Game = {
type states =
| NotStarted
| Running
| Won
| Lost;
};
module VideoGame = {
include Game;
let isWon = state => state == Won;
};
module signature with type keyword, all signatures must be supplied, newly added signature in the child module won't be available to outside cause it's not part of the (parent) signature.
module type EstablishmentType = {
type profession =
| Salesperson
| Engineer;
let professionDescription: profession => string;
};
module Company: EstablishmentType = {
type profession =
| Salesperson
| Engineer;
let professionDescription = p =>
switch (p) {
| Salesperson => "Selling software"
| Engineer => "Building software"
};
let product = "software";
};
Company.professionDescription(Company.Engineer);
Company.product /*Error: Unbound value*/
first steps using Reason with BuckleScript
install BuckleScript platform with npm install -g bs-platform
create project with bsb -init my-new-project -theme basic-reason
create your first file module.
/*Main.re*/
print_endline("Hello World");
build your module with npm run build
or npm run start
to build your module automatically.
run your module by node Main.bs.js