import { CodeSurfer, CodeSurferColumns, } from "code-surfer"; import { vsDark, github, nightOwl, } from "@code-surfer/themes";
import { Image, Footer } from "mdx-deck";
import Author from "./slides/Author"; import TableOfContents from "./slides/TableOfContents"; import WhyCompiler from "./slides/WhyCompiler";
import AST from './assets/images/AST.png'; // with import import JSFromAST from "./assets/images/JSFromAST.png"; import Generated2 from "./assets/images/Generated2.png"; import CompilerPhases from "./assets/images/CompilerPhases.jpg"; import ErrorMessages from "./assets/images/ErrorMessages.png"; import TypeInference from "./assets/images/TypeInference.png"; import { Row } from "./components/ui/Common";
export const theme = nightOwl;
Made with ❤️ by @dylanirlbeck
Chicago ReasonML Meetup, April 2020
-
I ❤️ functional programming
-
ReasonML has made me a better developer
-
An understanding of the core concepts behind functional programming
-
Ideas of what you might want to build in Reason
<Image size={200} src={"https://avatars0.githubusercontent.com/u/20414525?s=280&v=4"} />
-
New syntax for OCaml that looks like (and compiles to) JS
-
"Mostly" functional (OCaml)
-
100% type coverage, powerful type inference
- Talk about what OCaml (why is it used in compilers)
Type coverage - Every line of code Soundness - Once it compiles, the types are guaranteed to be accurate
-
Functions are first-class citizens
-
Pure functions
-
Immutability
// Reason array (the annotation is not needed!)
let arr: array(int) = [|1, 2, 3|];
// This is a function
let double = elem => elem*2;
let doubledArr = Array.map(double, arr); // [|2, 4, 6|]let add = (x, y) => x + y;
let addFive = add(5);
let eleven = addFive(6); // 11
let twelve = addFive(7); // 12let add = (x, y) => x + y;
let addFive = add(5);
let eleven = addFive(6); // 11
let twelve = addFive(7); // 12let add = (x, y) => x + y;
let addFive = add(5);
let eleven = addFive(6); // 11
let twelve = addFive(7); // 12Partial Application: Fixing a number of arguments to a function, producing another function of a smaller arity
Currying - Technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument
add above is really syntax sugar for let add = (x) => (y) => x + y;
// Invalid Reason
let incrementValue = value => {
value++
};
// Valid Reason
let incrementValue = value => {
value + 1
};Here we're trying to change the state of a variable..
let x = 5;
// Rest of your program
Js.log(x); // 5Flow - Static type checker for JavaScript Hack compiler - Hack extends PHP with static types Infer - Static analyzer for Java, C++, C, and Objective-C (for mobile apps)
-
Lexer/Tokenizer - Create a list of tokens from input code
-
Parser - Turn the tokens into an AST
-
Code Generation - Generate output code from the AST
-
Frontend: Mini-C
-
Type System: N/A
-
Backend: JavaScript
Let's define the tokens of our language
def f() 1 endWhat are our "tokens"?
def
f
(
)
1
end
// This is a variant
type token =
| DEF // def
| IDENT // f
| OPAREN // (
| CPAREN // )
| END // end
| INT // 2,3,etc
| COMMA; // ,Options of a varaint: Constructors or "tags"
How are we going to consume our input program?
//Tokenizer.re
// This is a record
type tokenType = {
token,
regExp: Js.Re.t,
};
// ^ means match beginning of input
let defToken = {token: DEF, regExp: [%re "/^(\\bdef\\b)/"]};
let endToken = {token: END, regExp: [%re "/^(\\bend\\b)/"]};
let identToken = {token: IDENT, regExp: [%re "/^(\\b[a-zA-Z]+\\b)/"]};
let intToken = {token: INT, regExp: [%re "/^(\\b[0-9]+\\b)/"]};
let oparenToken = {token: OPAREN, regExp: [%re "/^(\\()/"]};
let cparenToken = {token: CPAREN, regExp: [%re "/^(\\))/"]};
let commaToken = {token: COMMA, regExp: [%re "/^(\\,)/"]};
// These are in a necessary order, i.e. don't want def to be an identifier
let tokenTypes: array(tokenType) = [|
defToken,
endToken,
identToken,
intToken,
oparenToken,
cparenToken,
commaToken,
|];// This is a record
type tokenType = {
token,
regExp: Js.Re.t,
};
// ^ means match beginning of input
let defToken = {token: DEF, regExp: [%re "/^(\\bdef\\b)/"]};
let endToken = {token: END, regExp: [%re "/^(\\bend\\b)/"]};
let identToken = {token: IDENT, regExp: [%re "/^(\\b[a-zA-Z]+\\b)/"]};
let intToken = {token: INT, regExp: [%re "/^(\\b[0-9]+\\b)/"]};
let oparenToken = {token: OPAREN, regExp: [%re "/^(\\()/"]};
let cparenToken = {token: CPAREN, regExp: [%re "/^(\\))/"]};
let commaToken = {token: COMMA, regExp: [%re "/^(\\,)/"]};
// These are in a necessary order, i.e. don't want def to be an identifier
let tokenTypes: array(tokenType) = [|
defToken,
endToken,
identToken,
intToken,
oparenToken,
cparenToken,
commaToken,
|];// This is a record
type tokenType = {
token,
regExp: Js.Re.t,
};
// ^ means match beginning of input
let defToken = {token: DEF, regExp: [%re "/^(\\bdef\\b)/"]};
let endToken = {token: END, regExp: [%re "/^(\\bend\\b)/"]};
let identToken = {token: IDENT, regExp: [%re "/^(\\b[a-zA-Z]+\\b)/"]};
let intToken = {token: INT, regExp: [%re "/^(\\b[0-9]+\\b)/"]};
let oparenToken = {token: OPAREN, regExp: [%re "/^(\\()/"]};
let cparenToken = {token: CPAREN, regExp: [%re "/^(\\))/"]};
let commaToken = {token: COMMA, regExp: [%re "/^(\\,)/"]};
// These are in a necessary order, i.e. don't want def to be an identifier
let tokenTypes: array(tokenType) = [|
defToken,
endToken,
identToken,
intToken,
oparenToken,
cparenToken,
commaToken,
|];Now we throw it through a tokenizer function and...
// Compiler.re
let program = "def f(x,y,z) 1 end";
let tokens = Tokenizer.make(program);
Js.log(tokens);[
{ token: 3397029, value: 'def' },
{ token: 895974096, value: 'f' },
{ token: -780845765, value: '(' },
{ token: 895974096, value: 'x' },
{ token: -934581835, value: ',' },
{ token: 895974096, value: 'y' },
{ token: -934581835, value: ',' },
{ token: 895974096, value: 'z' },
{ token: 86829255, value: ')' },
{ token: 3647695, value: '1' },
{ token: 3448763, value: 'end' }
]Tokens -> Abstract Syntax Tree.
How do we represent the integer literal 1?
VALUE: 1
Literals (or constants) are the values we write in a conventional form whose values is obvious; they also do not change in value.
How about the definition def f(...) ... end
DEF
NAME: "f"
ARGS: [...]
BODY:
...
type integerNode = {value: int}
and callNode = {
name: string,
argExprs: array(exprNode),
}
and varRefNode = {value: string}
and exprNode =
| CallNode(callNode)
| IntegerNode(integerNode)
| VarRefNode(varRefNode);
type defNode = {
name: string,
argNames: array(string),
body: exprNode,
};
type node =
| DefNode(defNode)
| ExprNode(exprNode);type integerNode = {value: int}
and callNode = {
name: string,
argExprs: array(exprNode),
}
and varRefNode = {value: string}
and exprNode =
| CallNode(callNode)
| IntegerNode(integerNode)
| VarRefNode(varRefNode);
type defNode = {
name: string,
argNames: array(string),
body: exprNode,
};
type node =
| DefNode(defNode)
| ExprNode(exprNode);type integerNode = {value: int}
and callNode = {
name: string,
argExprs: array(exprNode),
}
and varRefNode = {value: string}
and exprNode =
| CallNode(callNode)
| IntegerNode(integerNode)
| VarRefNode(varRefNode);
and defNode = {
name: string,
argNames: array(string),
body: exprNode,
};
type node =
| DefNode(defNode)
| ExprNode(exprNode);Remember, we want to turn this;
def f(x) 1 end
into something like this:
DEF
NAME: "f"
ARGS: ["x"]
BODY:
INTEGER_LITERAL
VALUE: 1
let parseInteger = tokens => {
let (integerToken, newTokens) = consume(tokens, INT);
let integerNode = {value: int_of_string(integerToken.value)};
(ExprNode(integerNode), newTokens);
};let parseInteger = tokens => {
let (integerToken, newTokens) = consume(tokens, INT);
let integerNode = {value: int_of_string(integerToken.value)};
(ExprNode(integerNode), newTokens);
};let parseInteger = tokens => {
let (integerToken, newTokens) = consume(tokens, INT);
let integerNode = {value: int_of_string(integerToken.value)};
(ExprNode(integerNode), newTokens);
};let parseInteger = tokens => {
let (integerToken, newTokens) = consume(tokens, INT);
let integerNode = {value: int_of_string(integerToken.value)};
(ExprNode(integerNode), newTokens);
};let parseExpr = tokens =>
if (peek(tokens, INT, ())) {
parseInteger(tokens);
} else {
parseCall(tokens);
};// Will return a tree
let make = tokens => {
let rec parse = tokens => {
switch (Array.length(tokens)) {
| 0 => [||]
| _ =>
let (parsedDef, remainingTokens) = parseDef(tokens);
Array.concat([[|parsedDef|], parse(remainingTokens)]);
};
};
parse(tokens);
};// Will return a tree
let make = tokens => {
let rec parse = tokens => {
switch (Array.length(tokens)) {
| 0 => [||]
| _ =>
let (parsedDef, remainingTokens) = parseDef(tokens);
Array.concat([[|parsedDef|], parse(remainingTokens)]);
};
};
parse(tokens);
};// Will return a tree
let make = tokens => {
let rec parse = tokens => {
switch (Array.length(tokens)) {
| 0 => [||]
| _ =>
let (parsedDef, remainingTokens) = parseDef(tokens);
Array.concat([[|parsedDef|], parse(remainingTokens)]);
};
};
parse(tokens);
};For an input of
def f(x) ) end
we get this Abstract Syntax Tree:
DefNode(
{
name: "f"
argNames: "x"
body: ExprNode(
IntegerNode(
{
value: 1
}
)
)
}
)
<img style={{ height: '30%' }} src={AST} /> <img style={{ height: '30%' }} src={JSFromAST} />
let rec generateExpr = exprNode => {
switch (exprNode) {
| CallNode(callNode) =>
let name = callNode.name;
let argExprs = joinArgExprs(callNode.argExprs);
{j|$name ($argExprs)|j};
| IntegerNode(integerNode) => string_of_int(integerNode.value)
| VarRefNode(varRefNode) => varRefNode.value
};
}; let rec generateExpr = exprNode => {
switch (exprNode) {
| CallNode(callNode) =>
let name = callNode.name;
let argExprs = joinArgExprs(callNode.argExprs);
{j|$name ($argExprs)|j};
| IntegerNode(integerNode) => string_of_int(integerNode.value)
| VarRefNode(varRefNode) => varRefNode.value
};
}; let rec generateExpr = exprNode => {
switch (exprNode) {
| CallNode(callNode) =>
let name = callNode.name;
let argExprs = joinArgExprs(callNode.argExprs);
{j|$name ($argExprs)|j};
| IntegerNode(integerNode) => string_of_int(integerNode.value)
| VarRefNode(varRefNode) => varRefNode.value
};
}; let rec generate_ = node => {
switch (node) {
| DefNode(defNode) =>
let {name, argNames, body} = defNode;
let argNamesAsString = joinArgNames(argNames);
// recursively generate body
let bodyAsString = generate_(ExprNode(defNode.body));
{j|function $name($argNamesAsString) { return $bodyAsString }|j};
| ExprNode(exprNode) => generateExpr(exprNode)
};
}; let rec generate_ = node => {
switch (node) {
| DefNode(defNode) =>
let {name, argNames, body} = defNode;
let argNamesAsString = joinArgNames(argNames);
// recursively generate body
let bodyAsString = generate_(ExprNode(defNode.body));
{j|function $name($argNamesAsString) { return $bodyAsString }|j};
| ExprNode(exprNode) => generateExpr(exprNode)
};
}; let rec generate_ = node => {
switch (node) {
| DefNode(defNode) =>
let {name, argNames, body} = defNode;
let argNamesAsString = joinArgNames(argNames);
// recursively generate body
let bodyAsString = generate_(ExprNode(defNode.body));
{j|function $name($argNamesAsString) { return $bodyAsString }|j};
| ExprNode(exprNode) => generateExpr(exprNode)
};
};// Compiler.re
let compile = program => {
program
->Tokenizer.make
->Parser.make
->Generator.make;
};
compile("def f(x) 1 end");let compile = program => {
program
->Tokenizer.make
->Parser.make
->Generator.make;
};
compile("def f(x, y) add(x, y) end");let compile = program => {
program
->Tokenizer.make
->Parser.make
->Generator.make;
};
compile("def f(x, y) add(x, y) end");let compile = program => {
program
->Tokenizer.make
->Parser.make
->Generator.make;
};
compile("def f(x, y) add(x, y) end");let compile = program => {
program
->Tokenizer.make
->Parser.make
->Generator.make;
};
compile("def f(x, y) add(x, y) end");compile("def f(x) 1 end");compile("def f(x, y) add(x,add(x,y)) end")1. Talk about where a type system would fit into the flow of a compiler
-
Variants/Pattern matching:
type something = This | That; -
Records:
type somethingElse = {value: int, name: string} -
Type Inferencing
Type Inferencing
<img style={{ height: '40%' }} src={TypeInference}/>
Contrast this with JavaScriptError Messages
<img style={{ height: '40%' }} src={ErrorMessages}/>
Core concepts of React (from Jordan Walke himself!)
-
Minimize application state
-
Minimize side effects
Jordan Walke did a talk, invented React and Reason etc.
[@react.component]
let make = (~name) => {
let (count, setCount) = React.useState(() => 0);
<div>
<p> {React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")} </p>
<button onClick={_ => setCount(_ => count + 1)}>
{React.string("Click me")}
</button>
</div>
};[@react.component]
let make = (~name) => {
let (count, setCount) = React.useState(() => 0);
<div>
<p> {React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")} </p>
<button onClick={_ => setCount(_ => count + 1)}>
{React.string("Click me")}
</button>
</div>
};[@react.component]
let make = (~name) => {
let (count, setCount) = React.useState(() => 0);
<div>
<p> {React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")} </p>
<button onClick={_ => setCount(_ => count + 1)}>
{React.string("Click me")}
</button>
</div>
};[@react.component]
let make = (~name) => {
let (count, setCount) = React.useState(() => 0);
<div>
<p> {React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")} </p>
<button onClick={_ => setCount(_ => count + 1)}>
{React.string("Click me")}
</button>
</div>
};[@react.component]
let make = (~name) => {
let (count, setCount) = React.useState(() => 0);
<div>
<p> {React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")} </p>
<button onClick={_ => setCount(_ => count + 1)}>
{React.string("Click me")}
</button>
</div>
};-
Types - ease of refactoring
-
Explicit handling of elements
React.string("hello") -
First Class Language Tools (immutability, switch statements)
// www.hello.com/book/10/edit?name=Jane#author
let url = ReasonReactRouter.useUrl();
switch (url.path) {
| ["book", id, "edit"] => handleBookEdit(id)
| ["book", id] => getBook(id)
| ["book", id, _] => noSuchBookOperation()
| [] => showMainPage()
| ["shop"] | ["shop", "index"] => showShoppingPage()
| ["shop", ...rest] =>
/* e.g. "shop/cart/10", but let "cart/10" be handled by another function */
nestedMatch(rest)
| _ => showNotFoundPage()
};
}
// www.hello.com/book/10/edit?name=Jane#author
let url = ReasonReactRouter.useUrl();
switch (url.path) {
| ["book", id, "edit"] => handleBookEdit(id)
| ["book", id] => getBook(id)
| ["book", id, _] => noSuchBookOperation()
| [] => showMainPage()
| ["shop"] | ["shop", "index"] => showShoppingPage()
| ["shop", ...rest] =>
/* e.g. "shop/cart/10", but let "cart/10" be handled by another function */
nestedMatch(rest)
| _ => showNotFoundPage()
};
}
open ApolloHooks
module UserQuery = [%Graphql {|
query UserQuery {
currentUser {
name
}
}
|}];
[@react.component]
let make = () => {
/* Both variant and records available */
let (simple, _full) = useQuery(UserQuery.definition);
<div>
{
switch(simple) {
| Loading => <p>{React.string("Loading...")}</p>
| Data(data) =>
<p>{React.string(data##currentUser##name)}</p>
| NoData
| Error(_) => <p>{React.string("Get off my lawn!")}</p>
}
}
</div>
}open ApolloHooks
module UserQuery = [%Graphql {|
query UserQuery {
currentUser {
name
}
}
|}];
[@react.component]
let make = () => {
/* Both variant and records available */
let (simple, _full) = useQuery(UserQuery.definition);
<div>
{
switch(simple) {
| Loading => <p>{React.string("Loading...")}</p>
| Data(data) =>
<p>{React.string(data##currentUser##name)}</p>
| NoData
| Error(_) => <p>{React.string("Get off my lawn!")}</p>
}
}
</div>
}open ApolloHooks
module UserQuery = [%Graphql {|
query UserQuery {
currentUser {
name
}
}
|}];
[@react.component]
let make = () => {
/* Both variant and records available */
let (simple, _full) = useQuery(UserQuery.definition);
<div>
{
switch(simple) {
| Loading => <p>{React.string("Loading...")}</p>
| Data(data) =>
<p>{React.string(data##currentUser##name)}</p>
| NoData
| Error(_) => <p>{React.string("Get off my lawn!")}</p>
}
}
</div>
}open ApolloHooks
module UserQuery = [%Graphql {|
query UserQuery {
currentUser {
name
}
}
|}];
[@react.component]
let make = () => {
/* Both variant and records available */
let (simple, _full) = useQuery(UserQuery.definition);
<div>
{
switch(simple) {
| Loading => <p>{React.string("Loading...")}</p>
| Data(data) =>
<p>{React.string(data##currentUser##name)}</p>
| NoData
| Error(_) => <p>{React.string("Get off my lawn!")}</p>
}
}
</div>
}-
Functional programming concepts
-
Deep dive into the language by writing a mini-compiler
-
Examples of how to use Reason + React to build powerful and extensible web apps
