├── .gitignore ├── .prettierrc ├── README.md ├── assets └── images │ ├── AST.png │ ├── CompilerPhases.jpg │ ├── ErrorMessages.png │ ├── Generated2.png │ ├── JSFromAST.png │ └── TypeInference.png ├── components ├── hocs │ ├── withGraphQL.js │ └── withLink.js └── ui │ ├── Common.js │ ├── Footer.js │ ├── JSONViewer.js │ ├── Provider.js │ └── SimpleProfilePlaceholder.js ├── deck.mdx ├── gatsby-config.js ├── lib └── graphql │ └── config.js ├── now.json ├── package-lock.json ├── package.json ├── slides ├── Author │ ├── components │ │ └── ImagesRow │ │ │ └── index.js │ └── index.js ├── TableOfContents │ ├── components │ │ └── ImagesRow │ │ │ └── index.js │ └── index.js └── WhyCompiler │ └── index.js ├── src └── gatsby-theme-apollo │ └── client.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | *.DS_Store* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "printWidth": 60 4 | } 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functional Programming, React, and Compilers with ReasonML 2 | 3 | These are the slides I gave for a talk at [this](https://www.meetup.com/Chicago-ReasonML/events/vvwszqybcgbcb) ReasonML monthly meetup. 4 | 5 | [Here](https://github.com/dylanirlbeck/re-mini-compiler) is the code for the compiler discussed in the talk. 6 | 7 | A recording of the talk can be found [here](https://www.youtube.com/watch?v=D_ybZoJKQSE). Please let me know your feedback! 8 | 9 | --- 10 | 11 | This project was made with MDX Deck and Code Surfer, and the compiler implementation was inspired by Gary Bernhardt's introductory course on compilers (Destroy All Software), which can be found [here](https://www.destroyallsoftware.com/screencasts/catalog/a-compiler-from-scratch). 12 | -------------------------------------------------------------------------------- /assets/images/AST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylanirlbeck/fp-react-compilers/aa94de7a7eaa7520dc8dd7413fd7f7e52d30a531/assets/images/AST.png -------------------------------------------------------------------------------- /assets/images/CompilerPhases.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylanirlbeck/fp-react-compilers/aa94de7a7eaa7520dc8dd7413fd7f7e52d30a531/assets/images/CompilerPhases.jpg -------------------------------------------------------------------------------- /assets/images/ErrorMessages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylanirlbeck/fp-react-compilers/aa94de7a7eaa7520dc8dd7413fd7f7e52d30a531/assets/images/ErrorMessages.png -------------------------------------------------------------------------------- /assets/images/Generated2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylanirlbeck/fp-react-compilers/aa94de7a7eaa7520dc8dd7413fd7f7e52d30a531/assets/images/Generated2.png -------------------------------------------------------------------------------- /assets/images/JSFromAST.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylanirlbeck/fp-react-compilers/aa94de7a7eaa7520dc8dd7413fd7f7e52d30a531/assets/images/JSFromAST.png -------------------------------------------------------------------------------- /assets/images/TypeInference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylanirlbeck/fp-react-compilers/aa94de7a7eaa7520dc8dd7413fd7f7e52d30a531/assets/images/TypeInference.png -------------------------------------------------------------------------------- /components/hocs/withGraphQL.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ApolloProvider } from "react-apollo"; 3 | import { ApolloProvider as ApolloProviderHooks } from "@apollo/react-hooks"; 4 | import apolloClient from "../../src/gatsby-theme-apollo/client"; 5 | 6 | const withGraphQL = BaseComponent => props => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default withGraphQL; 17 | -------------------------------------------------------------------------------- /components/hocs/withLink.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const withLink = Component => props => { 4 | const { url, ...rest } = props; 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default withLink; 13 | -------------------------------------------------------------------------------- /components/ui/Common.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { css } from "styled-components"; 3 | 4 | const safeGet = (obj = {}, key, defaultValue) => 5 | obj[key] || defaultValue; 6 | 7 | const alignItems = { 8 | center: "center", 9 | end: "flex-end", 10 | start: "flex-start", 11 | }; 12 | 13 | const flexWrap = { 14 | nowrap: "nowrap", 15 | wrap: "wrap", 16 | }; 17 | 18 | const justifyContent = { 19 | around: "space-around", 20 | between: "space-between", 21 | center: "center", 22 | end: "flex-end", 23 | start: "flex-start", 24 | }; 25 | 26 | const flexMixin = css` 27 | align-items: ${({ align }) => 28 | safeGet(alignItems, align, "flex-start")} 29 | justify-content: ${({ justify }) => 30 | safeGet(justifyContent, justify, "flex-start")} 31 | `; 32 | 33 | const marginsAndPaddingsMixin = css` 34 | margin-bottom: ${({ mb }) => mb} 35 | margin-left: ${({ ml }) => ml} 36 | margin-right: ${({ mr }) => mr} 37 | margin-top: ${({ mt }) => mt} 38 | padding-bottom: ${({ pb }) => pb} 39 | padding-left: ${({ pl }) => pl} 40 | padding-right: ${({ pr }) => pr} 41 | padding-top: ${({ pt }) => pt} 42 | `; 43 | 44 | export const Column = styled.div` 45 | ${marginsAndPaddingsMixin} 46 | flex-direction: column; 47 | flex-grow: 1; 48 | `; 49 | 50 | export const Container = styled.div` 51 | ${marginsAndPaddingsMixin} 52 | `; 53 | 54 | export const Row = styled.div` 55 | ${flexMixin} 56 | ${marginsAndPaddingsMixin}; 57 | display: flex; 58 | flex-direction: row; 59 | flex-wrap: ${({ wrap }) => 60 | safeGet(flexWrap, wrap, "nowrap")}; 61 | `; 62 | 63 | export const TouchableContainer = styled.button` 64 | ${flexMixin} 65 | ${marginsAndPaddingsMixin} 66 | `; 67 | -------------------------------------------------------------------------------- /components/ui/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Footer = () => ( 4 | 28 | ); 29 | 30 | export default Footer; 31 | -------------------------------------------------------------------------------- /components/ui/JSONViewer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ObjectInspector, 4 | chromeLight, 5 | } from "react-inspector"; 6 | import { Container } from "./Common"; 7 | 8 | const JSONViewer = props => { 9 | const { data } = props; 10 | 11 | return ( 12 | 19 | 29 | 30 | ); 31 | }; 32 | 33 | export default JSONViewer; 34 | -------------------------------------------------------------------------------- /components/ui/Provider.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Footer from "./footer"; 3 | 4 | const Provider = ({ children, theme, index, slides }) => ( 5 |
13 | {children} 14 |
16 | ); 17 | 18 | export default { 19 | Provider, 20 | }; 21 | -------------------------------------------------------------------------------- /components/ui/SimpleProfilePlaceholder.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Container } from "./Common"; 3 | 4 | const SimpleProfilePlaceholder = () => { 5 | return ( 6 | 17 | 25 | 26 |

John Smith

27 |
28 | ); 29 | }; 30 | 31 | export default SimpleProfilePlaceholder; 32 | -------------------------------------------------------------------------------- /deck.mdx: -------------------------------------------------------------------------------- 1 | import { 2 | CodeSurfer, 3 | CodeSurferColumns, 4 | } from "code-surfer"; 5 | import { 6 | vsDark, 7 | github, 8 | nightOwl, 9 | } from "@code-surfer/themes"; 10 | 11 | import { Image, Footer } from "mdx-deck"; 12 | 13 | import Author from "./slides/Author"; 14 | import TableOfContents from "./slides/TableOfContents"; 15 | import WhyCompiler from "./slides/WhyCompiler"; 16 | 17 | import AST from './assets/images/AST.png'; // with import 18 | import JSFromAST from "./assets/images/JSFromAST.png"; 19 | import Generated2 from "./assets/images/Generated2.png"; 20 | import CompilerPhases from "./assets/images/CompilerPhases.jpg"; 21 | import ErrorMessages from "./assets/images/ErrorMessages.png"; 22 | import TypeInference from "./assets/images/TypeInference.png"; 23 | import { Row } from "./components/ui/Common"; 24 | 25 | export const theme = nightOwl; 26 | 27 | 32 | 33 | ## Functional Programming, React, and Compilers in ReasonML 34 | 35 | Chicago ReasonML Meetup, April 2020 36 | 37 | --- 38 | 39 | 40 | 41 | --- 42 | 43 | ## Why I'm doing this talk 44 | 45 | * I :heart: functional programming 46 | 47 | * ReasonML has made me a better developer 48 | 49 | 50 | 1. Want to share my experience from the world of academia 51 | 2. I want to help other people just get started building cool things with it 52 | 53 | 54 | --- 55 | 56 | ## What I hope you take away 57 | 58 | * An understanding of the core concepts behind functional programming 59 | 60 | * Ideas of what you might want to build in Reason 61 | 62 | 63 | 1. The concepts exist beyond the language (and how they're implemented in 64 | ReasonML) 65 | 2. We'll dive into some examples... 66 | 67 | 68 | --- 69 | 70 | ## ReasonML 71 | 72 | 73 | 74 | --- 75 | 76 | ## Reason 77 | 78 | - New syntax for OCaml that looks like (and compiles to) JS 79 | 80 | - "Mostly" functional (OCaml) 81 | 82 | - 100% type coverage, powerful type inference 83 | 84 | 85 | It's also really, really fast. 86 | 87 | 1. Talk about what OCaml (why is it used in compilers) 88 | 89 | Type coverage - Every line of code 90 | Soundness - Once it compiles, the types are guaranteed to be accurate 91 | 92 | 93 | 94 | --- 95 | 96 | 97 | 98 | --- 99 | 100 | ## [The Functional Approach to Programming](https://www.vitalsource.com/products/the-functional-approach-to-programming-guy-cousineau-v9781107263796?duration=perpetual&utm_source=bing&utm_medium=cpc&utm_campaign=VST%20Tier%20Three%20Shopping&msclkid=3983b13c94dd139dbcf8efba3a4a3980) 101 | 102 | - Functions are first-class citizens 103 | 104 | - Pure functions 105 | 106 | - Immutability 107 | 108 | --- 109 | 110 | 111 | 112 | ```reason title="Functions are first-class citizens" subtitle="Functions are values, too." 113 | // Reason array (the annotation is not needed!) 114 | let arr: array(int) = [|1, 2, 3|]; 115 | 116 | // This is a function 117 | let double = elem => elem*2; 118 | 119 | let doubledArr = Array.map(double, arr); // [|2, 4, 6|] 120 | ``` 121 | 122 | 123 | 124 | 125 | --- 126 | 127 | 128 | 129 | ```reason 1 title="Functions are first-class citizens cont'd" subtitle="Partial Application" 130 | let add = (x, y) => x + y; 131 | 132 | let addFive = add(5); 133 | 134 | let eleven = addFive(6); // 11 135 | 136 | let twelve = addFive(7); // 12 137 | ``` 138 | 139 | ```reason 3 title="Functions are first-class citizens cont'd" subtitle="Partial Application" 140 | let add = (x, y) => x + y; 141 | 142 | let addFive = add(5); 143 | 144 | let eleven = addFive(6); // 11 145 | 146 | let twelve = addFive(7); // 12 147 | ``` 148 | 149 | ```reason 5:7 title="Functions are first-class citizens cont'd" subtitle="Partial Application" 150 | let add = (x, y) => x + y; 151 | 152 | let addFive = add(5); 153 | 154 | let eleven = addFive(6); // 11 155 | 156 | let twelve = addFive(7); // 12 157 | ``` 158 | 159 | 160 | 161 | 162 | Currying and partial application are also conflated 163 | 164 | Partial Application: Fixing a number of arguments to a function, producing 165 | another function of a smaller arity 166 | 167 | Currying - Technique of translating the evaluation of a function that takes 168 | multiple arguments into evaluating a sequence of functions, each with a single 169 | argument 170 | 171 | 172 | add above is really syntax sugar for let add = (x) => (y) => x + y; 173 | 174 | 175 | 176 | --- 177 | 178 | 179 | 180 | ```reason title="Pure functions and side effects" 181 | // Invalid Reason 182 | let incrementValue = value => { 183 | value++ 184 | }; 185 | 186 | // Valid Reason 187 | let incrementValue = value => { 188 | value + 1 189 | }; 190 | ``` 191 | 192 | 193 | 194 | 195 | Pure Functions: No side effects occur throughout the function's 196 | execution. A side effect is basically a state change in something other than the 197 | function that's currently executing it. Also, same result for same input. 198 | 199 | Here we're trying to change the state of a variable.. 200 | 201 | 202 | --- 203 | 204 | 205 | 206 | ```reason title="Immutable data" subtitle="Ask yourself: how are variables defined in mathematics?" 207 | let x = 5; 208 | 209 | // Rest of your program 210 | 211 | Js.log(x); // 5 212 | ``` 213 | 214 | 215 | 216 | 217 | Variables will always be of same value + type throughout the program. This is 218 | very mathematical in nature (what does it mean to be a variable and function in 219 | mathematics). 220 | 221 | 222 | --- 223 | 224 | 225 | 226 | --- 227 | 228 | 229 | 230 | 231 | Flow - Static type checker for JavaScript 232 | Hack compiler - Hack extends PHP with static types 233 | Infer - Static analyzer for Java, C++, C, and Objective-C (for mobile apps) 234 | 235 | 236 | --- 237 | 238 | ## What's in a compiler (condensed) 239 | 240 | 1. **Lexer/Tokenizer** - Create a list of tokens from input code 241 | 242 | 2. **Parser** - Turn the tokens into an AST 243 | 244 | 3. **Code Generation** - Generate output code from the AST 245 | 246 | --- 247 | 248 | ## Define your frontend and backend (and sometimes your type system) 249 | 250 | - Frontend: Mini-C 251 | 252 | - Type System: N/A 253 | 254 | - Backend: JavaScript 255 | 256 | 257 | My implementation is based on one by Gary Benhardt (Destroy All Software)... 258 | 259 | 260 | --- 261 | 262 | ## Lexer/Tokenizer 263 | 264 | Let's define the tokens of our language 265 | 266 | ```C 267 | def f() 1 end 268 | ``` 269 | 270 | 271 | 272 | 273 | --- 274 | 275 | 276 | 277 | What are our "tokens"? 278 | 279 | ``` 280 | def 281 | 282 | f 283 | 284 | ( 285 | 286 | ) 287 | 288 | 1 289 | 290 | end 291 | ``` 292 | 293 | 294 | 295 | --- 296 | 297 | 298 | 299 | 300 | ```reason title="How do we represent these tokens?" subtitle="We use what's called a variant" 301 | // This is a variant 302 | type token = 303 | | DEF // def 304 | | IDENT // f 305 | | OPAREN // ( 306 | | CPAREN // ) 307 | | END // end 308 | | INT // 2,3,etc 309 | | COMMA; // , 310 | ``` 311 | 312 | 313 | 314 | 315 | Tokens are the smallest pieces that are meaningful 316 | 317 | Options of a varaint: Constructors or "tags" 318 | 319 | 320 | --- 321 | 322 | 323 | 324 | How are we going to consume our input program? 325 | 326 | [Regular expressions](https://reasonml.github.io/docs/en/regular-expression#docsNav). 327 | 328 | 329 | 330 | --- 331 | 332 | 333 | 334 | ```reason title="Regular Expressions" 335 | //Tokenizer.re 336 | 337 | // This is a record 338 | type tokenType = { 339 | token, 340 | regExp: Js.Re.t, 341 | }; 342 | 343 | // ^ means match beginning of input 344 | let defToken = {token: DEF, regExp: [%re "/^(\\bdef\\b)/"]}; 345 | let endToken = {token: END, regExp: [%re "/^(\\bend\\b)/"]}; 346 | let identToken = {token: IDENT, regExp: [%re "/^(\\b[a-zA-Z]+\\b)/"]}; 347 | let intToken = {token: INT, regExp: [%re "/^(\\b[0-9]+\\b)/"]}; 348 | let oparenToken = {token: OPAREN, regExp: [%re "/^(\\()/"]}; 349 | let cparenToken = {token: CPAREN, regExp: [%re "/^(\\))/"]}; 350 | let commaToken = {token: COMMA, regExp: [%re "/^(\\,)/"]}; 351 | 352 | // These are in a necessary order, i.e. don't want def to be an identifier 353 | let tokenTypes: array(tokenType) = [| 354 | defToken, 355 | endToken, 356 | identToken, 357 | intToken, 358 | oparenToken, 359 | cparenToken, 360 | commaToken, 361 | |]; 362 | ``` 363 | 364 | ```reason 1:4 title="Regular expressions" subtitle="Record!" 365 | // This is a record 366 | type tokenType = { 367 | token, 368 | regExp: Js.Re.t, 369 | }; 370 | 371 | // ^ means match beginning of input 372 | let defToken = {token: DEF, regExp: [%re "/^(\\bdef\\b)/"]}; 373 | let endToken = {token: END, regExp: [%re "/^(\\bend\\b)/"]}; 374 | let identToken = {token: IDENT, regExp: [%re "/^(\\b[a-zA-Z]+\\b)/"]}; 375 | let intToken = {token: INT, regExp: [%re "/^(\\b[0-9]+\\b)/"]}; 376 | let oparenToken = {token: OPAREN, regExp: [%re "/^(\\()/"]}; 377 | let cparenToken = {token: CPAREN, regExp: [%re "/^(\\))/"]}; 378 | let commaToken = {token: COMMA, regExp: [%re "/^(\\,)/"]}; 379 | 380 | // These are in a necessary order, i.e. don't want def to be an identifier 381 | let tokenTypes: array(tokenType) = [| 382 | defToken, 383 | endToken, 384 | identToken, 385 | intToken, 386 | oparenToken, 387 | cparenToken, 388 | commaToken, 389 | |]; 390 | ``` 391 | 392 | ```reason 7:25 title="Regular Expressions" subtitle="Construct the regexps" 393 | // This is a record 394 | type tokenType = { 395 | token, 396 | regExp: Js.Re.t, 397 | }; 398 | 399 | // ^ means match beginning of input 400 | let defToken = {token: DEF, regExp: [%re "/^(\\bdef\\b)/"]}; 401 | let endToken = {token: END, regExp: [%re "/^(\\bend\\b)/"]}; 402 | let identToken = {token: IDENT, regExp: [%re "/^(\\b[a-zA-Z]+\\b)/"]}; 403 | let intToken = {token: INT, regExp: [%re "/^(\\b[0-9]+\\b)/"]}; 404 | let oparenToken = {token: OPAREN, regExp: [%re "/^(\\()/"]}; 405 | let cparenToken = {token: CPAREN, regExp: [%re "/^(\\))/"]}; 406 | let commaToken = {token: COMMA, regExp: [%re "/^(\\,)/"]}; 407 | 408 | // These are in a necessary order, i.e. don't want def to be an identifier 409 | let tokenTypes: array(tokenType) = [| 410 | defToken, 411 | endToken, 412 | identToken, 413 | intToken, 414 | oparenToken, 415 | cparenToken, 416 | commaToken, 417 | |]; 418 | ``` 419 | 420 | 421 | 422 | --- 423 | 424 | Now we throw it through a tokenizer function and... 425 | 426 | 427 | 428 | 429 | ```reason 430 | // Compiler.re 431 | 432 | let program = "def f(x,y,z) 1 end"; 433 | let tokens = Tokenizer.make(program); 434 | Js.log(tokens); 435 | ``` 436 | 437 | 438 | 439 | --- 440 | 441 | 442 | 443 | ```json title="Tokens!" subtitle="The integers are just the JS representation of the variants" 444 | [ 445 | { token: 3397029, value: 'def' }, 446 | { token: 895974096, value: 'f' }, 447 | { token: -780845765, value: '(' }, 448 | { token: 895974096, value: 'x' }, 449 | { token: -934581835, value: ',' }, 450 | { token: 895974096, value: 'y' }, 451 | { token: -934581835, value: ',' }, 452 | { token: 895974096, value: 'z' }, 453 | { token: 86829255, value: ')' }, 454 | { token: 3647695, value: '1' }, 455 | { token: 3448763, value: 'end' } 456 | ] 457 | ``` 458 | 459 | 460 | 461 | --- 462 | 463 | ## Any questions? 464 | 465 | --- 466 | 467 | ## On to the parser 468 | 469 | Tokens -> [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). 470 | 471 | --- 472 | 473 | ## Abstract Syntax Tree - Integer Node 474 | 475 | 476 | 477 | How do we represent the integer literal 1? 478 | 479 | ``` 480 | VALUE: 1 481 | ``` 482 | 483 | 484 | 485 | 486 | What is the minimum info needed to represent an integer? 487 | 488 | Literals (or constants) are the values we write in a conventional form whose 489 | values is obvious; they also do not change in value. 490 | 491 | 492 | --- 493 | 494 | ## Abstract Syntax Tree - Definition Node 495 | 496 | 497 | 498 | How about the definition `def f(...) ... end` 499 | 500 | ``` 501 | DEF 502 | NAME: "f" 503 | ARGS: [...] 504 | BODY: 505 | ... 506 | ``` 507 | 508 | 509 | 510 | --- 511 | 512 | 513 | 514 | ```reason title="Types" 515 | type integerNode = {value: int} 516 | and callNode = { 517 | name: string, 518 | argExprs: array(exprNode), 519 | } 520 | and varRefNode = {value: string} 521 | and exprNode = 522 | | CallNode(callNode) 523 | | IntegerNode(integerNode) 524 | | VarRefNode(varRefNode); 525 | 526 | type defNode = { 527 | name: string, 528 | argNames: array(string), 529 | body: exprNode, 530 | }; 531 | 532 | type node = 533 | | DefNode(defNode) 534 | | ExprNode(exprNode); 535 | ``` 536 | 537 | ```reason 1:16 title="Types" subtitle="Internal AST nodes" 538 | type integerNode = {value: int} 539 | and callNode = { 540 | name: string, 541 | argExprs: array(exprNode), 542 | } 543 | and varRefNode = {value: string} 544 | and exprNode = 545 | | CallNode(callNode) 546 | | IntegerNode(integerNode) 547 | | VarRefNode(varRefNode); 548 | 549 | type defNode = { 550 | name: string, 551 | argNames: array(string), 552 | body: exprNode, 553 | }; 554 | 555 | type node = 556 | | DefNode(defNode) 557 | | ExprNode(exprNode); 558 | ``` 559 | 560 | ```reason 17:20 title="Types" subtitle="Top level AST nodes" 561 | type integerNode = {value: int} 562 | and callNode = { 563 | name: string, 564 | argExprs: array(exprNode), 565 | } 566 | and varRefNode = {value: string} 567 | and exprNode = 568 | | CallNode(callNode) 569 | | IntegerNode(integerNode) 570 | | VarRefNode(varRefNode); 571 | 572 | and defNode = { 573 | name: string, 574 | argNames: array(string), 575 | body: exprNode, 576 | }; 577 | 578 | type node = 579 | | DefNode(defNode) 580 | | ExprNode(exprNode); 581 | ``` 582 | 583 | 584 | 585 | --- 586 | 587 | Remember, we want to turn this; 588 | 589 | ``` 590 | def f(x) 1 end 591 | ``` 592 | 593 | into something like this: 594 | 595 | ``` 596 | DEF 597 | NAME: "f" 598 | ARGS: ["x"] 599 | BODY: 600 | INTEGER_LITERAL 601 | VALUE: 1 602 | 603 | ``` 604 | 605 | --- 606 | 607 | 608 | 609 | ```reason title="How do we parse integers?" 610 | let parseInteger = tokens => { 611 | let (integerToken, newTokens) = consume(tokens, INT); 612 | let integerNode = {value: int_of_string(integerToken.value)}; 613 | (ExprNode(integerNode), newTokens); 614 | }; 615 | ``` 616 | 617 | ```reason 2 title="Consume the INT token" 618 | let parseInteger = tokens => { 619 | let (integerToken, newTokens) = consume(tokens, INT); 620 | let integerNode = {value: int_of_string(integerToken.value)}; 621 | (ExprNode(integerNode), newTokens); 622 | }; 623 | ``` 624 | 625 | ```reason 3 title="Build the integerNode" 626 | let parseInteger = tokens => { 627 | let (integerToken, newTokens) = consume(tokens, INT); 628 | let integerNode = {value: int_of_string(integerToken.value)}; 629 | (ExprNode(integerNode), newTokens); 630 | }; 631 | ``` 632 | 633 | ```reason 4 title="Construct an ExprNode" 634 | let parseInteger = tokens => { 635 | let (integerToken, newTokens) = consume(tokens, INT); 636 | let integerNode = {value: int_of_string(integerToken.value)}; 637 | (ExprNode(integerNode), newTokens); 638 | }; 639 | ``` 640 | 641 | 642 | 643 | 644 | 645 | Consume gives us back new tokens in a safe way, validates the type is what we 646 | say it is. 647 | 648 | 649 | --- 650 | 651 | 652 | 653 | ```reason title="How do we parse expressions?" 654 | let parseExpr = tokens => 655 | if (peek(tokens, INT, ())) { 656 | parseInteger(tokens); 657 | } else { 658 | parseCall(tokens); 659 | }; 660 | ``` 661 | 662 | 663 | 664 | 665 | Two options for an expression, integer or function call. 666 | 667 | 668 | --- 669 | 670 | ## Let's put it all together and create our AST 671 | 672 | 673 | --- 674 | 675 | 676 | 677 | ```reason title="Parser" 678 | // Will return a tree 679 | let make = tokens => { 680 | let rec parse = tokens => { 681 | switch (Array.length(tokens)) { 682 | | 0 => [||] 683 | | _ => 684 | let (parsedDef, remainingTokens) = parseDef(tokens); 685 | Array.concat([[|parsedDef|], parse(remainingTokens)]); 686 | }; 687 | }; 688 | parse(tokens); 689 | }; 690 | ``` 691 | 692 | ```reason 3:10 title="Parser" subtitle="Recursive parse function" 693 | // Will return a tree 694 | let make = tokens => { 695 | let rec parse = tokens => { 696 | switch (Array.length(tokens)) { 697 | | 0 => [||] 698 | | _ => 699 | let (parsedDef, remainingTokens) = parseDef(tokens); 700 | Array.concat([[|parsedDef|], parse(remainingTokens)]); 701 | }; 702 | }; 703 | parse(tokens); 704 | }; 705 | ``` 706 | 707 | ```reason 11 title="Parser" subtitle="Start parsing!" 708 | // Will return a tree 709 | let make = tokens => { 710 | let rec parse = tokens => { 711 | switch (Array.length(tokens)) { 712 | | 0 => [||] 713 | | _ => 714 | let (parsedDef, remainingTokens) = parseDef(tokens); 715 | Array.concat([[|parsedDef|], parse(remainingTokens)]); 716 | }; 717 | }; 718 | parse(tokens); 719 | }; 720 | ``` 721 | 722 | 723 | 724 | --- 725 | 726 | For an input of 727 | ``` 728 | def f(x) ) end 729 | ``` 730 | 731 | we get this Abstract Syntax Tree: 732 | 733 | ``` 734 | DefNode( 735 | { 736 | name: "f" 737 | argNames: "x" 738 | body: ExprNode( 739 | IntegerNode( 740 | { 741 | value: 1 742 | } 743 | ) 744 | ) 745 | } 746 | ) 747 | ``` 748 | 749 | --- 750 | 751 | ## Questions so far? 752 | 753 | --- 754 | 755 | ## Now we're going to generate JavaScript from our input AST 756 | 757 | 758 | 759 | 760 | --- 761 | 762 | ## Our generate function will be highly recursive, just like the parser 763 | 764 | --- 765 | 766 | 767 | 768 | ```reason title="Generate code for an expression" 769 | let rec generateExpr = exprNode => { 770 | switch (exprNode) { 771 | | CallNode(callNode) => 772 | let name = callNode.name; 773 | let argExprs = joinArgExprs(callNode.argExprs); 774 | {j|$name ($argExprs)|j}; 775 | | IntegerNode(integerNode) => string_of_int(integerNode.value) 776 | | VarRefNode(varRefNode) => varRefNode.value 777 | }; 778 | }; 779 | ``` 780 | 781 | ```reason 3:6 title="Generate a function call" 782 | let rec generateExpr = exprNode => { 783 | switch (exprNode) { 784 | | CallNode(callNode) => 785 | let name = callNode.name; 786 | let argExprs = joinArgExprs(callNode.argExprs); 787 | {j|$name ($argExprs)|j}; 788 | | IntegerNode(integerNode) => string_of_int(integerNode.value) 789 | | VarRefNode(varRefNode) => varRefNode.value 790 | }; 791 | }; 792 | ``` 793 | 794 | ```reason 7:8 title="Generate an integer or variable reference" 795 | let rec generateExpr = exprNode => { 796 | switch (exprNode) { 797 | | CallNode(callNode) => 798 | let name = callNode.name; 799 | let argExprs = joinArgExprs(callNode.argExprs); 800 | {j|$name ($argExprs)|j}; 801 | | IntegerNode(integerNode) => string_of_int(integerNode.value) 802 | | VarRefNode(varRefNode) => varRefNode.value 803 | }; 804 | }; 805 | ``` 806 | 807 | 808 | 809 | --- 810 | 811 | 812 | 813 | ```reason title="Main generator function" 814 | let rec generate_ = node => { 815 | switch (node) { 816 | | DefNode(defNode) => 817 | let {name, argNames, body} = defNode; 818 | let argNamesAsString = joinArgNames(argNames); 819 | // recursively generate body 820 | let bodyAsString = generate_(ExprNode(defNode.body)); 821 | {j|function $name($argNamesAsString) { return $bodyAsString }|j}; 822 | | ExprNode(exprNode) => generateExpr(exprNode) 823 | }; 824 | }; 825 | ``` 826 | 827 | ```reason 9 title="Generate an expression" 828 | let rec generate_ = node => { 829 | switch (node) { 830 | | DefNode(defNode) => 831 | let {name, argNames, body} = defNode; 832 | let argNamesAsString = joinArgNames(argNames); 833 | // recursively generate body 834 | let bodyAsString = generate_(ExprNode(defNode.body)); 835 | {j|function $name($argNamesAsString) { return $bodyAsString }|j}; 836 | | ExprNode(exprNode) => generateExpr(exprNode) 837 | }; 838 | }; 839 | ``` 840 | 841 | ```reason 3:8 title="Generate a function definition" subtitle="Make sure to wrap the body in an ExprNode b/c I messed up the types" 842 | let rec generate_ = node => { 843 | switch (node) { 844 | | DefNode(defNode) => 845 | let {name, argNames, body} = defNode; 846 | let argNamesAsString = joinArgNames(argNames); 847 | // recursively generate body 848 | let bodyAsString = generate_(ExprNode(defNode.body)); 849 | {j|function $name($argNamesAsString) { return $bodyAsString }|j}; 850 | | ExprNode(exprNode) => generateExpr(exprNode) 851 | }; 852 | }; 853 | ``` 854 | 855 | 856 | 857 | --- 858 | 859 | ## Let's put it all together with the compiler 860 | 861 | --- 862 | 863 | 864 | 865 | ```reason 866 | // Compiler.re 867 | 868 | let compile = program => { 869 | program 870 | ->Tokenizer.make 871 | ->Parser.make 872 | ->Generator.make; 873 | }; 874 | 875 | compile("def f(x) 1 end"); 876 | ``` 877 | 878 | ```reason 3 title="Tokenize!" 879 | let compile = program => { 880 | program 881 | ->Tokenizer.make 882 | ->Parser.make 883 | ->Generator.make; 884 | }; 885 | 886 | compile("def f(x, y) add(x, y) end"); 887 | ``` 888 | 889 | ```reason 4 title="Parse!" 890 | let compile = program => { 891 | program 892 | ->Tokenizer.make 893 | ->Parser.make 894 | ->Generator.make; 895 | }; 896 | 897 | compile("def f(x, y) add(x, y) end"); 898 | ``` 899 | 900 | ```reason 5 title="Generate!" 901 | let compile = program => { 902 | program 903 | ->Tokenizer.make 904 | ->Parser.make 905 | ->Generator.make; 906 | }; 907 | 908 | compile("def f(x, y) add(x, y) end"); 909 | ``` 910 | 911 | ```reason 8 title="Compile!" 912 | let compile = program => { 913 | program 914 | ->Tokenizer.make 915 | ->Parser.make 916 | ->Generator.make; 917 | }; 918 | 919 | compile("def f(x, y) add(x, y) end"); 920 | ``` 921 | 922 | 923 | 924 | --- 925 | 926 | 927 | ## And that's our compiler! 928 | 929 | ```reason 930 | compile("def f(x) 1 end"); 931 | ``` 932 | 933 | 934 | 935 | --- 936 | 937 | ### Our mini-compiler also supports arbitrary nested expressions 938 | 939 | ```reason 940 | compile("def f(x, y) add(x,add(x,y)) end") 941 | ``` 942 | 943 | 944 | 945 | --- 946 | 947 | ## Recap 948 | 949 | --- 950 | 951 | ## 1. Structure of a compiler (Tokenizer -> Parser -> Generator) 952 | 953 | 954 | 955 | 956 | 1. Talk about where a type system would fit into the flow of a compiler 957 | 958 | 959 | --- 960 | 961 | ## 2. Features of Reason as a language 962 | 963 | * Variants/Pattern matching: `type something = This | That;` 964 | 965 | * Records: `type somethingElse = {value: int, name: string}` 966 | 967 | * Type Inferencing 968 | 969 | --- 970 | 971 | 972 | ## 3. Reason/OCaml Type system 973 | 974 | Type Inferencing 975 | 976 | 977 | 978 | 979 | Contrast this with JavaScript 980 | 981 | 982 | --- 983 | 984 | ## 3. Reason/OCaml Type system cont'd 985 | 986 | Error Messages 987 | 988 | 989 | 990 | --- 991 | 992 | 993 | 994 | --- 995 | 996 | 997 | 998 | ### We've talked about Reason as a language, so what can we do with it? 999 | 1000 |
1001 |

1002 | ReasonReact! 1003 |

1004 | 1005 |
1006 | 1007 |
1008 | 1009 | --- 1010 | 1011 | 1012 | ## Core concepts of React (from [Jordan Walke]() himself!) 1013 | 1014 | 1015 | 1016 | 1. Minimize application state 1017 | 1018 | 2. Minimize side effects 1019 | 1020 | 1021 | 1022 | 1023 | 1. Reason, and FP languages in general, minimize state because you can't change 1024 | state globally. 1025 | 2. Again, by definition, FP languages are mostly side-effect free. 1026 | 1027 | Jordan Walke did a talk, invented React and Reason etc. 1028 | 1029 | 1030 | --- 1031 | 1032 | 1033 | 1034 | ```reason title="Intro Example" 1035 | [@react.component] 1036 | let make = (~name) => { 1037 | let (count, setCount) = React.useState(() => 0); 1038 | 1039 |
1040 |

{React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")}

1041 | 1044 |
1045 | }; 1046 | ``` 1047 | 1048 | ```reason 2 title="Intro Example" subtitle="Named arguments (props)" 1049 | [@react.component] 1050 | let make = (~name) => { 1051 | let (count, setCount) = React.useState(() => 0); 1052 | 1053 |
1054 |

{React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")}

1055 | 1058 |
1059 | }; 1060 | ``` 1061 | 1062 | ```reason 3 title="Intro Example" subtitle="useState hook" 1063 | [@react.component] 1064 | let make = (~name) => { 1065 | let (count, setCount) = React.useState(() => 0); 1066 | 1067 |
1068 |

{React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")}

1069 | 1072 |
1073 | }; 1074 | ``` 1075 | 1076 | ```reason 6 title="Intro Example" subtitle="Explicitly typed elements" 1077 | [@react.component] 1078 | let make = (~name) => { 1079 | let (count, setCount) = React.useState(() => 0); 1080 | 1081 |
1082 |

{React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")}

1083 | 1086 |
1087 | }; 1088 | ``` 1089 | 1090 | ```reason 1 title="Intro Example" subtitle="React decorator" 1091 | [@react.component] 1092 | let make = (~name) => { 1093 | let (count, setCount) = React.useState(() => 0); 1094 | 1095 |
1096 |

{React.string(name ++ " clicked " ++ string_of_int(count) ++ " times")}

1097 | 1100 |
1101 | }; 1102 | ``` 1103 | 1104 |
1105 | 1106 | 1107 | Main differences between this and JS implementation: 1108 | * Named arguments for props 1109 | * Decorator attribute that tells ReasonReact you want to compile this into a 1110 | function that takes a JS object as props which is how React works 1111 | * React.string wrapper, conversion to an int 1112 | 1113 | 1114 | --- 1115 | 1116 | ## Main practical benefits of Reason + React 1117 | 1118 | * Types - ease of refactoring 1119 | 1120 | * Explicit handling of elements `React.string("hello")` 1121 | 1122 | * First Class Language Tools (immutability, switch statements) 1123 | 1124 | 1125 | 1. Refactoring - this comes from experience 1126 | 2. This is a nice sanity check 1127 | 3. Again, minimize application state, switches help for rendering different HTML 1128 | 1129 | 1130 | --- 1131 | 1132 | 1133 | 1134 | 1135 | ```reason title="Example - ReasonReactRouter" 1136 | // www.hello.com/book/10/edit?name=Jane#author 1137 | 1138 | let url = ReasonReactRouter.useUrl(); 1139 | switch (url.path) { 1140 | | ["book", id, "edit"] => handleBookEdit(id) 1141 | | ["book", id] => getBook(id) 1142 | | ["book", id, _] => noSuchBookOperation() 1143 | | [] => showMainPage() 1144 | | ["shop"] | ["shop", "index"] => showShoppingPage() 1145 | | ["shop", ...rest] => 1146 | /* e.g. "shop/cart/10", but let "cart/10" be handled by another function */ 1147 | nestedMatch(rest) 1148 | | _ => showNotFoundPage() 1149 | }; 1150 | } 1151 | 1152 | ``` 1153 | 1154 | ```reason 1:5 title="Example - ReasonReactRouter" 1155 | // www.hello.com/book/10/edit?name=Jane#author 1156 | 1157 | let url = ReasonReactRouter.useUrl(); 1158 | switch (url.path) { 1159 | | ["book", id, "edit"] => handleBookEdit(id) 1160 | | ["book", id] => getBook(id) 1161 | | ["book", id, _] => noSuchBookOperation() 1162 | | [] => showMainPage() 1163 | | ["shop"] | ["shop", "index"] => showShoppingPage() 1164 | | ["shop", ...rest] => 1165 | /* e.g. "shop/cart/10", but let "cart/10" be handled by another function */ 1166 | nestedMatch(rest) 1167 | | _ => showNotFoundPage() 1168 | }; 1169 | } 1170 | 1171 | ``` 1172 | 1173 | 1174 | 1175 | 1176 | The entire implementation is around 20 lines! 1177 | 1178 | 1179 | --- 1180 | 1181 | 1182 | 1183 | ```reason title="Another Example - Apollo" 1184 | open ApolloHooks 1185 | 1186 | module UserQuery = [%Graphql {| 1187 | query UserQuery { 1188 | currentUser { 1189 | name 1190 | } 1191 | } 1192 | |}]; 1193 | 1194 | [@react.component] 1195 | let make = () => { 1196 | /* Both variant and records available */ 1197 | let (simple, _full) = useQuery(UserQuery.definition); 1198 | 1199 |
1200 | { 1201 | switch(simple) { 1202 | | Loading =>

{React.string("Loading...")}

1203 | | Data(data) => 1204 |

{React.string(data##currentUser##name)}

1205 | | NoData 1206 | | Error(_) =>

{React.string("Get off my lawn!")}

1207 | } 1208 | } 1209 |
1210 | } 1211 | ``` 1212 | 1213 | ```reason 3:9 title="Another Example - Apollo" subtitle="Fully typed GraphQL queries!" 1214 | open ApolloHooks 1215 | 1216 | module UserQuery = [%Graphql {| 1217 | query UserQuery { 1218 | currentUser { 1219 | name 1220 | } 1221 | } 1222 | |}]; 1223 | 1224 | [@react.component] 1225 | let make = () => { 1226 | /* Both variant and records available */ 1227 | let (simple, _full) = useQuery(UserQuery.definition); 1228 | 1229 |
1230 | { 1231 | switch(simple) { 1232 | | Loading =>

{React.string("Loading...")}

1233 | | Data(data) => 1234 |

{React.string(data##currentUser##name)}

1235 | | NoData 1236 | | Error(_) =>

{React.string("Get off my lawn!")}

1237 | } 1238 | } 1239 |
1240 | } 1241 | ``` 1242 | 1243 | ```reason 11:30 title="Another Example - Apollo" subtitle="Render a different component easily" 1244 | open ApolloHooks 1245 | 1246 | module UserQuery = [%Graphql {| 1247 | query UserQuery { 1248 | currentUser { 1249 | name 1250 | } 1251 | } 1252 | |}]; 1253 | 1254 | [@react.component] 1255 | let make = () => { 1256 | /* Both variant and records available */ 1257 | let (simple, _full) = useQuery(UserQuery.definition); 1258 | 1259 |
1260 | { 1261 | switch(simple) { 1262 | | Loading =>

{React.string("Loading...")}

1263 | | Data(data) => 1264 |

{React.string(data##currentUser##name)}

1265 | | NoData 1266 | | Error(_) =>

{React.string("Get off my lawn!")}

1267 | } 1268 | } 1269 |
1270 | } 1271 | ``` 1272 | 1273 | ```reason 18:24 title="Another Example - Apollo" subtitle="Look at how elegant this is!" 1274 | open ApolloHooks 1275 | 1276 | module UserQuery = [%Graphql {| 1277 | query UserQuery { 1278 | currentUser { 1279 | name 1280 | } 1281 | } 1282 | |}]; 1283 | 1284 | [@react.component] 1285 | let make = () => { 1286 | /* Both variant and records available */ 1287 | let (simple, _full) = useQuery(UserQuery.definition); 1288 | 1289 |
1290 | { 1291 | switch(simple) { 1292 | | Loading =>

{React.string("Loading...")}

1293 | | Data(data) => 1294 |

{React.string(data##currentUser##name)}

1295 | | NoData 1296 | | Error(_) =>

{React.string("Get off my lawn!")}

1297 | } 1298 | } 1299 |
1300 | } 1301 | ``` 1302 | 1303 |
1304 | 1305 | --- 1306 | 1307 | ## So what did we (hopefully) learn? 1308 | 1309 | 1310 | 1311 | * Functional programming concepts 1312 | 1313 | * Deep dive into the language by writing a mini-compiler 1314 | 1315 | * Examples of how to use Reason + React to build powerful and extensible web apps 1316 | 1317 | 1318 | 1319 | 1320 | 1. Functions as first-class citizens, pure functions, immutability 1321 | 2. Lexer->Parser->CodeGenerator 1322 | 3. Mention ReasonReactNative 1323 | 1324 | 1325 | --- 1326 | 1327 | # Thank you + Q&A 1328 | 1329 | --- 1330 | 1331 | # Sources 1332 | 1333 | - https://thecodeboss.dev/2016/12/core-functional-programming-concepts/ 1334 | - https://www.youtube.com/watch?v=5fG_lyNuEAw&list=PLDLhOs9UV9w9ms-kFEtR3-LMM3HU8QheA 1335 | - https://reasonml.github.io/ 1336 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: "Hello yoo", 4 | }, 5 | plugins: ["gatsby-theme-apollo"], 6 | }; 7 | -------------------------------------------------------------------------------- /lib/graphql/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is how you could configure a more complicated Apollo Client 3 | * with support for WebSockets, centralized error handling, and authentication 4 | */ 5 | 6 | import fetch from "isomorphic-fetch"; 7 | import { from, split } from "apollo-link"; 8 | import { onError } from "apollo-link-error"; 9 | import { createHttpLink } from "apollo-link-http"; 10 | import { ApolloClient } from "apollo-client"; 11 | // import { WebSocketLink } from "apollo-link-ws"; 12 | import { setContext } from "apollo-link-context"; 13 | import { getMainDefinition } from "apollo-utilities"; 14 | import { InMemoryCache } from "apollo-cache-inmemory"; 15 | 16 | /** 17 | * Apollo cache instance 18 | */ 19 | const cache = new InMemoryCache(); 20 | 21 | /** 22 | * HTTP Link 23 | */ 24 | const httpLink = createHttpLink({ 25 | uri: `https://luij2.sse.codesandbox.io`, 26 | fetch, 27 | }); 28 | 29 | /** 30 | * WebSocket link for subscriptions 31 | */ 32 | // const wsLink = new WebSocketLink({ 33 | // uri: `ws://luij2.sse.codesandbox.io`, 34 | // options: { 35 | // reconnect: true, 36 | // }, 37 | // }); 38 | 39 | /** 40 | * Sets auth token on an individual request 41 | */ 42 | const authorizationLink = setContext( 43 | async (request, previousContext) => { 44 | const headers = { 45 | authorization: `Bearer this_is_some_jwt_or_whatever`, 46 | }; 47 | 48 | return { headers }; 49 | } 50 | ); 51 | 52 | /** 53 | * Single place to handle errors 54 | */ 55 | const handleErrorLink = onError( 56 | ({ forward, graphQLErrors, operation, response }) => { 57 | /** 58 | * Do error handling here 59 | * Send to error handling service, trigger alert, etc. 60 | */ 61 | return forward(operation); 62 | } 63 | ); 64 | 65 | const remoteDataLink = split( 66 | ({ query }) => { 67 | const definition = getMainDefinition(query); 68 | return ( 69 | definition.kind === "OperationDefinition" && 70 | definition.operation === "subscription" 71 | ); 72 | }, 73 | // wsLink, 74 | httpLink 75 | ); 76 | 77 | /** 78 | * Apollo Client instance 79 | */ 80 | export const client = new ApolloClient({ 81 | cache, 82 | link: from([ 83 | authorizationLink, 84 | handleErrorLink, 85 | remoteDataLink, 86 | ]), 87 | }); 88 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fp-react-compilers-reasonml", 3 | "alias": "fp-react-compilers-reasonml.now.sh", 4 | "builds": [ 5 | { 6 | "src": "package.json", 7 | "use": "@now/static-build", 8 | "config": { 9 | "distDir": "public" 10 | } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "1.0.0", 4 | "scripts": { 5 | "start": "mdx-deck deck.mdx", 6 | "build": "mdx-deck build deck.mdx", 7 | "test": "echo \"there are no tests\"" 8 | }, 9 | "devDependencies": { 10 | "code-surfer": "3.0.0-beta.1", 11 | "gatsby-source-graphql": "^2.1.20", 12 | "gatsby-theme-apollo": "^2.0.0", 13 | "mdx-deck": "^4.1.1" 14 | }, 15 | "name": "fp-react-compilers-with-reasonml", 16 | "dependencies": { 17 | "@apollo/react-hooks": "^3.1.3", 18 | "@mdx-deck/themes": "^3.0.8", 19 | "apollo-boost": "^0.4.4", 20 | "apollo-client": "^2.6.4", 21 | "gatsby-theme-mdx-deck": "^4.1.0", 22 | "graphql-tag": "^2.10.1", 23 | "isomorphic-fetch": "^2.2.1", 24 | "lodash": "^4.17.15", 25 | "react-apollo": "^3.1.3", 26 | "react-inspector": "^4.0.0", 27 | "styled-components": "^4.4.0", 28 | "subscriptions-transport-ws": "^0.9.16" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /slides/Author/components/ImagesRow/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image } from "mdx-deck"; 3 | import { Row } from "../../../../components/ui/Common"; 4 | 5 | const PreviousWork = props => { 6 | const { images, ...rest } = props; 7 | return ( 8 | 9 | {images.map(src => { 10 | return ( 11 | 21 | ); 22 | })} 23 | 24 | ); 25 | }; 26 | 27 | export default PreviousWork; 28 | -------------------------------------------------------------------------------- /slides/Author/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Row } from "../../components/ui/Common"; 3 | import ImagesRow from "./components/ImagesRow"; 4 | 5 | const Author = props => { 6 | return ( 7 |
8 | 9 |

Dylan Irlbeck   ·  

10 |

Intern  

11 |

@  

12 | 17 | 25 | 26 |
27 | 35 | 43 |
44 | ); 45 | }; 46 | 47 | export default Author; 48 | -------------------------------------------------------------------------------- /slides/TableOfContents/components/ImagesRow/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Image } from "mdx-deck"; 3 | import { Row } from "../../../../components/ui/Common"; 4 | 5 | const PreviousWork = props => { 6 | const { images, ...rest } = props; 7 | return ( 8 | 9 | {images.map(src => { 10 | return ( 11 | 21 | ); 22 | })} 23 | 24 | ); 25 | }; 26 | 27 | export default PreviousWork; 28 | -------------------------------------------------------------------------------- /slides/TableOfContents/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const createStyleGetter = activeStep => step => ({ 4 | opacity: activeStep === step ? 1 : 0.2, 5 | }); 6 | 7 | const steps = [ 8 | { title: "The Functional Approach" }, 9 | { title: "Compiler Theory" }, 10 | { title: "ReasonReact" }, 11 | ]; 12 | 13 | const TableOfContents = props => { 14 | const { activeStep } = props; 15 | 16 | const getStyle = createStyleGetter(activeStep); 17 | 18 | return ( 19 |
20 | {steps.map((step, index) => { 21 | const { title } = step; 22 | return

{title}

; 23 | })} 24 |
25 | ); 26 | }; 27 | 28 | export default TableOfContents; 29 | -------------------------------------------------------------------------------- /slides/WhyCompiler/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ImagesRow from "../Author/components/ImagesRow"; 3 | 4 | const WhyCompiler = props => { 5 | return ( 6 |
7 |

Why write a compiler?

8 |
  • 9 | Show off the core features of Reason 10 |
  • 11 |
  • 12 | It's what Reason/OCaml is especially good at 13 |
  • 14 | 22 |
    23 | ); 24 | }; 25 | 26 | export default WhyCompiler; 27 | -------------------------------------------------------------------------------- /src/gatsby-theme-apollo/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Apollo configurations can get MUCH more sophisticated than this 3 | * This is the most minimal setup you can use with SSR support 4 | */ 5 | 6 | import fetch from "isomorphic-fetch"; 7 | import ApolloClient from "apollo-boost"; 8 | 9 | const client = new ApolloClient({ 10 | fetch, 11 | uri: "https://luij2.sse.codesandbox.io", 12 | }); 13 | 14 | export default client; 15 | --------------------------------------------------------------------------------