├── .gitignore ├── reason_logo.png ├── bsconfig.json ├── package.json ├── src ├── Guide.bs.js └── Guide.re └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | lib 4 | .merlin -------------------------------------------------------------------------------- /reason_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amartincolby/ReasonML-Quick-Guide/HEAD/reason_logo.png -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReasonML-quick-guide", 3 | "reason": { 4 | "react-jsx": 3 5 | }, 6 | "sources": { 7 | "dir" : "src", 8 | "subdirs" : true 9 | }, 10 | "bsc-flags": ["-bs-super-errors", "-bs-no-version-header"], 11 | "package-specs": [{ 12 | "module": "commonjs", 13 | "in-source": true 14 | }], 15 | "suffix": ".bs.js", 16 | "namespace": true, 17 | "bs-dependencies": [ 18 | "reason-react" 19 | ], 20 | "refmt": 3 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reasonml_quick_guide", 3 | "description": "A one-page guide to ReasonML.", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/amartincolby/ReasonML-Quick-Guide", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/amartincolby/ReasonML-Quick-Guide" 9 | }, 10 | "author": "Aaron Martin-Colby (https://github.com/amartincolby/)", 11 | "license": "MIT", 12 | "scripts": { 13 | "build": "bsb -make-world", 14 | "start": "bsb -make-world -w -ws _ ", 15 | "clean": "bsb -clean-world", 16 | "server": "moduleserve ./ --port 8000", 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "keywords": [ 20 | "BuckleScript", 21 | "ReasonReact", 22 | "reason-react" 23 | ], 24 | "dependencies": { 25 | "react": "16.8.1", 26 | "react-dom": "16.8.1", 27 | "reason-react": "0.7.0" 28 | }, 29 | "devDependencies": { 30 | "bs-platform": "^7.0.1", 31 | "moduleserve": "^0.8.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Guide.bs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var $$Array = require("bs-platform/lib/js/array.js"); 4 | var Block = require("bs-platform/lib/js/block.js"); 5 | var Curry = require("bs-platform/lib/js/curry.js"); 6 | var React = require("react"); 7 | var Random = require("bs-platform/lib/js/random.js"); 8 | var Caml_obj = require("bs-platform/lib/js/caml_obj.js"); 9 | var Belt_Array = require("bs-platform/lib/js/belt_Array.js"); 10 | var Caml_array = require("bs-platform/lib/js/caml_array.js"); 11 | var Caml_int32 = require("bs-platform/lib/js/caml_int32.js"); 12 | var Pervasives = require("bs-platform/lib/js/pervasives.js"); 13 | var Caml_oo_curry = require("bs-platform/lib/js/caml_oo_curry.js"); 14 | var CamlinternalOO = require("bs-platform/lib/js/camlinternalOO.js"); 15 | var Caml_exceptions = require("bs-platform/lib/js/caml_exceptions.js"); 16 | 17 | function addInts(a, b) { 18 | return a + b | 0; 19 | } 20 | 21 | function intAdder(x, y) { 22 | return x + y | 0; 23 | } 24 | 25 | console.log("best of times"); 26 | 27 | console.log("worst of times"); 28 | 29 | function blockScope(param) { 30 | return "Return string"; 31 | } 32 | 33 | console.log("42"); 34 | 35 | var lexicalValue = "To be or not to be."; 36 | 37 | console.log(lexicalValue); 38 | 39 | console.log("Teacher"); 40 | 41 | Pervasives.print_int(101); 42 | 43 | function add2(a, b) { 44 | return a + b | 0; 45 | } 46 | 47 | var myMutableNumber = { 48 | contents: 120 49 | }; 50 | 51 | myMutableNumber.contents = 240; 52 | 53 | var copyOfMyMutableNumber = myMutableNumber.contents; 54 | 55 | var author1 = { 56 | name: "Charles Dickens", 57 | age: 58 58 | }; 59 | 60 | var author2 = { 61 | name: "Charles Dickens", 62 | age: 58 63 | }; 64 | 65 | var author3 = { 66 | name: "Victor Hugo", 67 | age: 83 68 | }; 69 | 70 | Caml_obj.caml_equal(author1, author2); 71 | 72 | Caml_obj.caml_equal(author1, author3); 73 | 74 | Caml_obj.caml_notequal(author1, author3); 75 | 76 | var bigObj = /* :: */[ 77 | 10, 78 | /* :: */[ 79 | 10000000, 80 | /* :: */[ 81 | 10000000, 82 | /* [] */0 83 | ] 84 | ] 85 | ]; 86 | 87 | var smallObj = /* :: */[ 88 | 11, 89 | /* :: */[ 90 | 1, 91 | /* :: */[ 92 | 1, 93 | /* [] */0 94 | ] 95 | ] 96 | ]; 97 | 98 | Caml_obj.caml_greaterthan(bigObj, smallObj); 99 | 100 | function polishAdder(a, b) { 101 | return a + b | 0; 102 | } 103 | 104 | var $plus = Caml_int32.imul; 105 | 106 | function $(a, b) { 107 | return Caml_int32.imul(a - b | 0, 3); 108 | } 109 | 110 | var world = "🌍"; 111 | 112 | var helloWorld = "hello, " + (String(world) + ""); 113 | 114 | var emailSubject = "Hi John Wayne, you're a valued customer"; 115 | 116 | var secondTrainJourney = { 117 | destination: "London", 118 | capacity: 45, 119 | averageSpeed: 105.0 120 | }; 121 | 122 | var frostedFlakes = { 123 | name: "Kellog's Frosted Flakes", 124 | amount: 500 125 | }; 126 | 127 | frostedFlakes.amount = 200; 128 | 129 | var name = "General Mills Chex"; 130 | 131 | var chex = { 132 | name: name, 133 | amount: 250 134 | }; 135 | 136 | var $$class = CamlinternalOO.create_table(["addNumber"]); 137 | 138 | var ids = CamlinternalOO.new_methods_variables($$class, [ 139 | "increment", 140 | "addNumber" 141 | ], ["aNumber"]); 142 | 143 | var increment = ids[0]; 144 | 145 | var addNumber = ids[1]; 146 | 147 | var aNumber = ids[2]; 148 | 149 | CamlinternalOO.set_methods($$class, [ 150 | increment, 151 | (function (self$1, num) { 152 | self$1[aNumber].contents = Caml_int32.imul(self$1[aNumber].contents, num); 153 | return /* () */0; 154 | }), 155 | addNumber, 156 | (function (self$1, inputValue) { 157 | Curry._2(self$1[0][increment], self$1, inputValue); 158 | return self$1[aNumber].contents; 159 | }) 160 | ]); 161 | 162 | function obj_init(env) { 163 | var self = CamlinternalOO.create_object_opt(0, $$class); 164 | self[aNumber] = { 165 | contents: 42 166 | }; 167 | return self; 168 | } 169 | 170 | CamlinternalOO.init_class($$class); 171 | 172 | var newObject = obj_init(0); 173 | 174 | var $$class$1 = CamlinternalOO.create_table(["buildTruck"]); 175 | 176 | var ids$1 = CamlinternalOO.new_methods_variables($$class$1, [ 177 | "buildTruck", 178 | "buildEngine" 179 | ], ["drive"]); 180 | 181 | var buildTruck = ids$1[0]; 182 | 183 | var buildEngine = ids$1[1]; 184 | 185 | var drive = ids$1[2]; 186 | 187 | CamlinternalOO.set_methods($$class$1, [ 188 | buildTruck, 189 | (function (self$2, make, model) { 190 | return "You built a new " + (String(make) + (" " + (String(model) + (" with " + (String(self$2[drive]) + "-wheel drive."))))); 191 | }), 192 | buildEngine, 193 | (function (self$2, cylinders) { 194 | return "You installed a " + (String(cylinders) + "-cylinder engine."); 195 | }) 196 | ]); 197 | 198 | function obj_init$1(env) { 199 | var self = CamlinternalOO.create_object_opt(0, $$class$1); 200 | self[drive] = 4; 201 | return self; 202 | } 203 | 204 | CamlinternalOO.init_class($$class$1); 205 | 206 | var newTruck = obj_init$1(0); 207 | 208 | var toyotaTundra = Caml_oo_curry.js3(715262353, 1, newTruck, "Toyota", "Tundra"); 209 | 210 | var $$class$2 = CamlinternalOO.create_table([ 211 | "checkSpeed", 212 | "accelerate", 213 | "brake" 214 | ]); 215 | 216 | var ids$2 = CamlinternalOO.new_methods_variables($$class$2, [ 217 | "checkSpeed", 218 | "brake", 219 | "accelerate" 220 | ], ["speed"]); 221 | 222 | var checkSpeed = ids$2[0]; 223 | 224 | var brake = ids$2[1]; 225 | 226 | var accelerate = ids$2[2]; 227 | 228 | var speed = ids$2[3]; 229 | 230 | CamlinternalOO.set_methods($$class$2, [ 231 | accelerate, 232 | (function (self$3, param) { 233 | if (self$3[speed].contents < 155) { 234 | self$3[speed].contents = (self$3[speed].contents << 0); 235 | return /* () */0; 236 | } else { 237 | return 0; 238 | } 239 | }), 240 | brake, 241 | (function (self$3, param) { 242 | if (self$3[speed].contents > 0) { 243 | self$3[speed].contents = self$3[speed].contents - 1 | 0; 244 | return /* () */0; 245 | } else { 246 | return 0; 247 | } 248 | }), 249 | checkSpeed, 250 | (function (self$3) { 251 | return self$3[speed].contents; 252 | }) 253 | ]); 254 | 255 | function obj_init$2(env) { 256 | var self = CamlinternalOO.create_object_opt(0, $$class$2); 257 | self[speed] = { 258 | contents: 0 259 | }; 260 | return self; 261 | } 262 | 263 | CamlinternalOO.init_class($$class$2); 264 | 265 | var toyotaSupra = obj_init$2(0); 266 | 267 | Random.self_init(/* () */0); 268 | 269 | var isThingHere = Random.bool(/* () */0); 270 | 271 | var userIds = /* :: */[ 272 | 1, 273 | /* :: */[ 274 | 4, 275 | /* :: */[ 276 | 8, 277 | /* [] */0 278 | ] 279 | ] 280 | ]; 281 | 282 | var newUserIds1_001 = /* :: */[ 283 | 102, 284 | userIds 285 | ]; 286 | 287 | var newUserIds1 = /* :: */[ 288 | 101, 289 | newUserIds1_001 290 | ]; 291 | 292 | var languages = [ 293 | "Reason", 294 | "JavaScript", 295 | "OCaml" 296 | ]; 297 | 298 | Caml_array.caml_array_get(languages, 1); 299 | 300 | function signUpToNewsletter(email) { 301 | return "Thanks for signing up " + email; 302 | } 303 | 304 | function getEmailPrefs(email) { 305 | var message = "Update settings for " + email; 306 | return /* tuple */[ 307 | message, 308 | /* :: */[ 309 | "Weekly News", 310 | /* :: */[ 311 | "Daily Notifications", 312 | /* [] */0 313 | ] 314 | ] 315 | ]; 316 | } 317 | 318 | function noArgument(param) { 319 | return "I've got nothing"; 320 | } 321 | 322 | function noReturn(input) { 323 | console.log("I just print " + input); 324 | return /* () */0; 325 | } 326 | 327 | function moveTo(x, y) { 328 | return { 329 | posx: x, 330 | posy: y 331 | }; 332 | } 333 | 334 | function getMessage(msg) { 335 | return "Message for you, sir... " + msg; 336 | } 337 | 338 | function logMessage(message) { 339 | console.log(message); 340 | return /* () */0; 341 | } 342 | 343 | function logAnotherMessage(msg) { 344 | console.log(msg); 345 | return /* () */0; 346 | } 347 | 348 | function divide(denom, numr) { 349 | return Caml_int32.div(numr, denom); 350 | } 351 | 352 | function divideBySix(param) { 353 | return param / 6 | 0; 354 | } 355 | 356 | function divideByTwo(param) { 357 | return param / 2 | 0; 358 | } 359 | 360 | function divideSixBy(__x) { 361 | return Caml_int32.div(6, __x); 362 | } 363 | 364 | function divideTenBy(__x) { 365 | return Caml_int32.div(10, __x); 366 | } 367 | 368 | function labeledDiv(denom, numr) { 369 | return Caml_int32.div(numr, denom); 370 | } 371 | 372 | function labeledDivBySix(param) { 373 | return param / 6 | 0; 374 | } 375 | 376 | function labeledDivByTwo(param) { 377 | return param / 2 | 0; 378 | } 379 | 380 | function greetPerson(name, greeting, param) { 381 | if (greeting !== undefined) { 382 | return greeting + name; 383 | } else { 384 | return "Hello, " + name; 385 | } 386 | } 387 | 388 | greetPerson("Kate", undefined, /* () */0); 389 | 390 | function subtract(x, y) { 391 | return x - y | 0; 392 | } 393 | 394 | function subtractTwo(__x) { 395 | return __x - 2 | 0; 396 | } 397 | 398 | function subtractFromThree(param) { 399 | return 3 - param | 0; 400 | } 401 | 402 | function subtractFive(param) { 403 | return 5 - param | 0; 404 | } 405 | 406 | function addOne(a) { 407 | return (a << 0); 408 | } 409 | 410 | function divByTwo(a) { 411 | return a / 2 | 0; 412 | } 413 | 414 | function multByThree(a) { 415 | return Caml_int32.imul(a, 3); 416 | } 417 | 418 | var newGreeting = "Good morning!"; 419 | 420 | console.log("Logged!"); 421 | 422 | for(var x = 1; x <= 42; ++x){ 423 | Pervasives.print_int(x); 424 | Pervasives.print_string(" "); 425 | } 426 | 427 | for(var x$1 = 42; x$1 >= 1; --x$1){ 428 | Pervasives.print_int(x$1); 429 | Pervasives.print_string(" "); 430 | } 431 | 432 | var testArray = [ 433 | 1, 434 | 2, 435 | 3, 436 | 42 437 | ]; 438 | 439 | var testArrayLength = testArray.length; 440 | 441 | for(var x$2 = 0; x$2 <= testArrayLength; ++x$2){ 442 | Pervasives.print_int(Caml_array.caml_array_get(testArray, x$2)); 443 | } 444 | 445 | var testVariable = { 446 | contents: true 447 | }; 448 | 449 | while(testVariable.contents) { 450 | console.log("It's true."); 451 | testVariable.contents = false; 452 | }; 453 | 454 | console.log("It's now false."); 455 | 456 | var loginMessage = "May your enemies crumble before your might."; 457 | 458 | var userId = 23; 459 | 460 | var alertMessage = userId !== undefined ? "Welcome, your ID is" + String(userId) : "You don't have an account!"; 461 | 462 | var firstNames = /* :: */[ 463 | "James", 464 | /* :: */[ 465 | "Jean", 466 | /* :: */[ 467 | "Geoff", 468 | /* [] */0 469 | ] 470 | ] 471 | ]; 472 | 473 | var importantNumbers = /* :: */[ 474 | 42, 475 | /* :: */[ 476 | 2001, 477 | /* :: */[ 478 | 31459, 479 | /* [] */0 480 | ] 481 | ] 482 | ]; 483 | 484 | var match = importantNumbers ? /* tuple */[ 485 | 42, 486 | 2001, 487 | 31459 488 | ] : /* tuple */[ 489 | 0, 490 | 0, 491 | 0 492 | ]; 493 | 494 | function isJohn(a) { 495 | return a === "John"; 496 | } 497 | 498 | var maybeName = "John"; 499 | 500 | var aGreeting; 501 | 502 | if (maybeName !== undefined) { 503 | var name$1 = maybeName; 504 | aGreeting = name$1 === "John" ? "Hi John! How's it going?" : "Hi " + (name$1 + ", welcome."); 505 | } else { 506 | aGreeting = "No one to greet."; 507 | } 508 | 509 | var Impossible_Age = Caml_exceptions.create("Guide-ReasonMLQuickGuide.Impossible_Age"); 510 | 511 | function validatePatientAge(patient) { 512 | if (patient.age < 122 && patient.age > 0) { 513 | return "Now seeing " + (patient.name + "."); 514 | } else { 515 | throw Impossible_Age; 516 | } 517 | } 518 | 519 | var newPatient = { 520 | name: "Jeanne Calment", 521 | age: 122, 522 | height: 150, 523 | weight: 55.0 524 | }; 525 | 526 | var exit = 0; 527 | 528 | var status; 529 | 530 | try { 531 | status = validatePatientAge(newPatient); 532 | exit = 1; 533 | } 534 | catch (exn){ 535 | if (exn === Impossible_Age) { 536 | console.log("Jeanne Calment - Invalid Age : " + String(122)); 537 | } else { 538 | throw exn; 539 | } 540 | } 541 | 542 | if (exit === 1) { 543 | console.log(status); 544 | } 545 | 546 | var messageToEvan; 547 | 548 | try { 549 | messageToEvan = validatePatientAge(newPatient); 550 | } 551 | catch (exn$1){ 552 | if (exn$1 === Impossible_Age) { 553 | messageToEvan = "Jeanne Calment - Invalid Age : " + String(122); 554 | } else { 555 | throw exn$1; 556 | } 557 | } 558 | 559 | function getRoleDirectionMessage(staff) { 560 | var match = staff.role; 561 | switch (match) { 562 | case /* Delivery */0 : 563 | return "Deliver it like you mean it!"; 564 | case /* Sales */1 : 565 | return "Sell it like only you can!"; 566 | case /* Other */2 : 567 | return "You're an important part of the team!"; 568 | 569 | } 570 | } 571 | 572 | var Staff = { 573 | defaultRole: /* Other */2, 574 | getRoleDirectionMessage: getRoleDirectionMessage 575 | }; 576 | 577 | var NewStaff_newEmployee = { 578 | employeeName: "Fred", 579 | role: /* Other */2 580 | }; 581 | 582 | var NewStaff = { 583 | newRole: /* Delivery */0, 584 | newEmployee: NewStaff_newEmployee 585 | }; 586 | 587 | function getMeetingTime(staff) { 588 | if (staff >= 2) { 589 | return 1115; 590 | } else { 591 | return 930; 592 | } 593 | } 594 | 595 | var SpecializedStaff_ceo = { 596 | employeeName: "Barnie", 597 | role: /* Other */2 598 | }; 599 | 600 | var SpecializedStaff = { 601 | getRoleDirectionMessage: getRoleDirectionMessage, 602 | ceo: SpecializedStaff_ceo, 603 | defaultRole: /* Delivery */0, 604 | getMeetingTime: getMeetingTime 605 | }; 606 | 607 | var Module1 = { }; 608 | 609 | var Module2 = { 610 | newMusician: /* Classical */0 611 | }; 612 | 613 | var Module3 = { 614 | newMusician: /* Classical */0 615 | }; 616 | 617 | var Module4 = { 618 | externalRef: /* Classical */0 619 | }; 620 | 621 | var Module5 = { 622 | newMusician: /* Classical */0, 623 | externalRef: /* Classical */0 624 | }; 625 | 626 | var Module6 = { 627 | externalRef: /* Classical */0, 628 | newMusician2: /* Classical */0 629 | }; 630 | 631 | var Module7 = { 632 | newMusician: /* Classical */0, 633 | externalRef: /* Classical */0, 634 | newMusician2: /* Classical */0 635 | }; 636 | 637 | var Module_in_external_file = { }; 638 | 639 | var Current_working_file = { 640 | newThing: /* Thing1 */0 641 | }; 642 | 643 | var visibleFunction = Caml_int32.imul; 644 | 645 | var Module8 = { 646 | visibleThing: 2001, 647 | visibleFunction: visibleFunction 648 | }; 649 | 650 | Pervasives.print_int(6); 651 | 652 | var Module9 = { 653 | visibleThing: 2001, 654 | visibleFunction: visibleFunction 655 | }; 656 | 657 | var func1 = Caml_int32.imul; 658 | 659 | function func2(a, b) { 660 | return a + b; 661 | } 662 | 663 | var Module10 = { 664 | func1: func1, 665 | func2: func2 666 | }; 667 | 668 | console.log("Thing!"); 669 | 670 | console.log("This will log to the console."); 671 | 672 | console.log("Log", "an", "array"); 673 | 674 | var getAccountID = new Promise((function (resolve, reject) { 675 | return resolve(1337); 676 | })); 677 | 678 | getAccountID.then((function (result) { 679 | return Promise.resolve(result); 680 | })).catch((function (err) { 681 | console.log("Failure!!", err); 682 | return Promise.resolve(-1); 683 | })); 684 | 685 | var jsReduce = (function (numbers) { 686 | var result = 0; 687 | numbers.forEach( (number) => { 688 | result += number; 689 | }); 690 | return result; 691 | }); 692 | 693 | function calculate(numbers) { 694 | return jsReduce($$Array.of_list(numbers)); 695 | } 696 | 697 | var testArray$1 = [ 698 | "1", 699 | "2", 700 | "3" 701 | ]; 702 | 703 | Belt_Array.forEach(testArray$1, (function (element) { 704 | console.log(element); 705 | return /* () */0; 706 | })); 707 | 708 | Belt_Array.forEach(testArray$1, (function (element) { 709 | console.log(element); 710 | return /* () */0; 711 | })); 712 | 713 | function Guide(Props) { 714 | var food = Props.food; 715 | var match = React.useState((function () { 716 | return 0; 717 | })); 718 | var eat = match[1]; 719 | var amount = match[0]; 720 | return React.createElement("div", undefined, React.createElement("p", undefined, " " + (String(food) + (" monster has eaten " + (String(amount) + (" " + (String(food) + "s ")))))), React.createElement("button", { 721 | onClick: (function (_ev) { 722 | return Curry._1(eat, (function (param) { 723 | return (amount << 0); 724 | })); 725 | }) 726 | }, " Eat " + (String(food) + " "))); 727 | } 728 | 729 | var x$3 = 5; 730 | 731 | var y = 42.0; 732 | 733 | var z = "Dinner for one"; 734 | 735 | var myBlock = /* () */0; 736 | 737 | var aTuple = /* tuple */[ 738 | "Teacher", 739 | 101 740 | ]; 741 | 742 | var classNumber = 101; 743 | 744 | var bjorn = { 745 | firstName: "Bjorn", 746 | age: 28 747 | }; 748 | 749 | var firstName = "Bjorn"; 750 | 751 | var age = 28; 752 | 753 | var bName = "Bjorn"; 754 | 755 | var bAge = 28; 756 | 757 | var cName = "Bjorn"; 758 | 759 | var dName = "Bjorn"; 760 | 761 | var a = 5; 762 | 763 | var myId = 101; 764 | 765 | var isLearning = true; 766 | 767 | var myString1 = "A world without string is chaos."; 768 | 769 | var myString2 = "A world without string is chaos."; 770 | 771 | var greeting = "Hello world!"; 772 | 773 | var aLongerGreeting = "Look at me,\nI'm a\nmulti-line string"; 774 | 775 | var quotedString = "It was the best of times, it was the worst of times."; 776 | 777 | var multiLineQuotedString = "To love another person\nis to see\nthe face of God."; 778 | 779 | var specialCharacters = "This is fine. !@#'\"`$%^&*()"; 780 | 781 | var accountBalance = "You have \$500.00"; 782 | 783 | var lastLetter = /* "z" */122; 784 | 785 | var formattedInt = 123456; 786 | 787 | var formattedFloat = 123456.0; 788 | 789 | var teamMember = /* tuple */[ 790 | "John", 791 | 25 792 | ]; 793 | 794 | var position2d = /* tuple */[ 795 | 9.0, 796 | 12.0 797 | ]; 798 | 799 | var city1 = /* tuple */[ 800 | "London", 801 | 51.507222, 802 | -0.1275 803 | ]; 804 | 805 | var memberName = "John"; 806 | 807 | var memberAge = 25; 808 | 809 | var threeTuple = /* tuple */[ 810 | 42, 811 | 2001, 812 | "A113" 813 | ]; 814 | 815 | var theAnswer = 42; 816 | 817 | var firstTrainJourney = { 818 | destination: "London", 819 | capacity: 45, 820 | averageSpeed: 120.0 821 | }; 822 | 823 | var maxPassengers = 45; 824 | 825 | var amount = 250; 826 | 827 | var userPreferredAuth = /* GitHub */0; 828 | 829 | var gitUser1 = /* GitHub */0; 830 | 831 | var gitUser2 = /* GitHub */0; 832 | 833 | var newUser = /* Moderator */Block.__(1, [ 834 | /* GitHub */0, 835 | /* Europe */1 836 | ]); 837 | 838 | var pipedValue = 3; 839 | 840 | var isMorning = true; 841 | 842 | var logSomething = true; 843 | 844 | var isItReallyTrue = "It's actually true!"; 845 | 846 | var loopStart = 1; 847 | 848 | var loopEnd = 42; 849 | 850 | var dLoopStart = 42; 851 | 852 | var dLoopEnd = 1; 853 | 854 | var newDndPlayer = /* Barbarian */1; 855 | 856 | var $$event = 5; 857 | 858 | var answer = match[0]; 859 | 860 | var yearWeMakeContact = match[1]; 861 | 862 | var pi = match[2]; 863 | 864 | var employee = { 865 | employeeName: "Wilma", 866 | role: /* Delivery */0 867 | }; 868 | 869 | var aThing = /* State2 */1; 870 | 871 | var make = Guide; 872 | 873 | exports.x = x$3; 874 | exports.y = y; 875 | exports.z = z; 876 | exports.addInts = addInts; 877 | exports.intAdder = intAdder; 878 | exports.myBlock = myBlock; 879 | exports.blockScope = blockScope; 880 | exports.lexicalValue = lexicalValue; 881 | exports.aTuple = aTuple; 882 | exports.classNumber = classNumber; 883 | exports.bjorn = bjorn; 884 | exports.firstName = firstName; 885 | exports.age = age; 886 | exports.bName = bName; 887 | exports.bAge = bAge; 888 | exports.cName = cName; 889 | exports.dName = dName; 890 | exports.a = a; 891 | exports.add2 = add2; 892 | exports.myId = myId; 893 | exports.myMutableNumber = myMutableNumber; 894 | exports.copyOfMyMutableNumber = copyOfMyMutableNumber; 895 | exports.isLearning = isLearning; 896 | exports.author1 = author1; 897 | exports.author2 = author2; 898 | exports.author3 = author3; 899 | exports.bigObj = bigObj; 900 | exports.smallObj = smallObj; 901 | exports.myString1 = myString1; 902 | exports.myString2 = myString2; 903 | exports.polishAdder = polishAdder; 904 | exports.$plus = $plus; 905 | exports.$ = $; 906 | exports.greeting = greeting; 907 | exports.aLongerGreeting = aLongerGreeting; 908 | exports.quotedString = quotedString; 909 | exports.multiLineQuotedString = multiLineQuotedString; 910 | exports.specialCharacters = specialCharacters; 911 | exports.world = world; 912 | exports.helloWorld = helloWorld; 913 | exports.accountBalance = accountBalance; 914 | exports.emailSubject = emailSubject; 915 | exports.lastLetter = lastLetter; 916 | exports.formattedInt = formattedInt; 917 | exports.formattedFloat = formattedFloat; 918 | exports.teamMember = teamMember; 919 | exports.position2d = position2d; 920 | exports.city1 = city1; 921 | exports.memberName = memberName; 922 | exports.memberAge = memberAge; 923 | exports.threeTuple = threeTuple; 924 | exports.theAnswer = theAnswer; 925 | exports.firstTrainJourney = firstTrainJourney; 926 | exports.maxPassengers = maxPassengers; 927 | exports.secondTrainJourney = secondTrainJourney; 928 | exports.frostedFlakes = frostedFlakes; 929 | exports.name = name; 930 | exports.amount = amount; 931 | exports.chex = chex; 932 | exports.newObject = newObject; 933 | exports.newTruck = newTruck; 934 | exports.toyotaTundra = toyotaTundra; 935 | exports.toyotaSupra = toyotaSupra; 936 | exports.userPreferredAuth = userPreferredAuth; 937 | exports.gitUser1 = gitUser1; 938 | exports.gitUser2 = gitUser2; 939 | exports.newUser = newUser; 940 | exports.isThingHere = isThingHere; 941 | exports.userIds = userIds; 942 | exports.newUserIds1 = newUserIds1; 943 | exports.languages = languages; 944 | exports.signUpToNewsletter = signUpToNewsletter; 945 | exports.getEmailPrefs = getEmailPrefs; 946 | exports.noArgument = noArgument; 947 | exports.noReturn = noReturn; 948 | exports.moveTo = moveTo; 949 | exports.getMessage = getMessage; 950 | exports.logMessage = logMessage; 951 | exports.logAnotherMessage = logAnotherMessage; 952 | exports.divide = divide; 953 | exports.divideBySix = divideBySix; 954 | exports.divideByTwo = divideByTwo; 955 | exports.divideSixBy = divideSixBy; 956 | exports.divideTenBy = divideTenBy; 957 | exports.labeledDiv = labeledDiv; 958 | exports.labeledDivBySix = labeledDivBySix; 959 | exports.labeledDivByTwo = labeledDivByTwo; 960 | exports.greetPerson = greetPerson; 961 | exports.subtract = subtract; 962 | exports.subtractTwo = subtractTwo; 963 | exports.subtractFromThree = subtractFromThree; 964 | exports.subtractFive = subtractFive; 965 | exports.addOne = addOne; 966 | exports.divByTwo = divByTwo; 967 | exports.multByThree = multByThree; 968 | exports.pipedValue = pipedValue; 969 | exports.isMorning = isMorning; 970 | exports.newGreeting = newGreeting; 971 | exports.logSomething = logSomething; 972 | exports.isItReallyTrue = isItReallyTrue; 973 | exports.loopStart = loopStart; 974 | exports.loopEnd = loopEnd; 975 | exports.dLoopStart = dLoopStart; 976 | exports.dLoopEnd = dLoopEnd; 977 | exports.testArrayLength = testArrayLength; 978 | exports.testVariable = testVariable; 979 | exports.newDndPlayer = newDndPlayer; 980 | exports.loginMessage = loginMessage; 981 | exports.userId = userId; 982 | exports.alertMessage = alertMessage; 983 | exports.firstNames = firstNames; 984 | exports.$$event = $$event; 985 | exports.importantNumbers = importantNumbers; 986 | exports.answer = answer; 987 | exports.yearWeMakeContact = yearWeMakeContact; 988 | exports.pi = pi; 989 | exports.isJohn = isJohn; 990 | exports.maybeName = maybeName; 991 | exports.aGreeting = aGreeting; 992 | exports.Impossible_Age = Impossible_Age; 993 | exports.validatePatientAge = validatePatientAge; 994 | exports.newPatient = newPatient; 995 | exports.messageToEvan = messageToEvan; 996 | exports.Staff = Staff; 997 | exports.employee = employee; 998 | exports.NewStaff = NewStaff; 999 | exports.SpecializedStaff = SpecializedStaff; 1000 | exports.Module1 = Module1; 1001 | exports.Module2 = Module2; 1002 | exports.Module3 = Module3; 1003 | exports.Module4 = Module4; 1004 | exports.Module5 = Module5; 1005 | exports.Module6 = Module6; 1006 | exports.Module7 = Module7; 1007 | exports.Module_in_external_file = Module_in_external_file; 1008 | exports.Current_working_file = Current_working_file; 1009 | exports.Module8 = Module8; 1010 | exports.Module9 = Module9; 1011 | exports.Module10 = Module10; 1012 | exports.aThing = aThing; 1013 | exports.getAccountID = getAccountID; 1014 | exports.jsReduce = jsReduce; 1015 | exports.calculate = calculate; 1016 | exports.testArray = testArray$1; 1017 | exports.make = make; 1018 | /* Not a pure module */ 1019 | -------------------------------------------------------------------------------- /src/Guide.re: -------------------------------------------------------------------------------- 1 | /* Comment blocks start with slash-star, 2 | and end with star-slash. */ 3 | // Line comments begin with a double slash. 4 | // Expressions need not be terminated with a semicolon, but it is best practice; 5 | 6 | /*** A note on types ***/ 7 | 8 | /* ReasonML makes little distinction between primitive types and what could be 9 | called user-defined types or structs. They are simply "things" represented 10 | by symbols. 11 | 12 | The below type is an abstract type, meaning that the symbol has no structure 13 | attached to it. Any program using this type does not need to know its 14 | structure just so long as usage of the type is consistent. Basically, 15 | programs can use symbols without knowing what they mean. */ 16 | 17 | type kwyjibo; 18 | 19 | /* The below type is a concrete type with a defined structure. These will appear 20 | frequently in this guide since any record must have an associated concrete 21 | type. */ 22 | 23 | type thing = { 24 | value1 : string, 25 | value2 : int, 26 | }; 27 | 28 | 29 | /*---------------------------------------------- 30 | * Variables, functions, and bindings 31 | *---------------------------------------------- 32 | */ 33 | 34 | /* In ReasonML, the word "variable" is used as convention but is inaccurate. 35 | Variables do not vary, i.e. they are immutable. As with many functional 36 | languages, variables are more accurately referred to as bindings, since what 37 | is happening is that a value is being irrevocably bound to a symbol, not 38 | merely assigned. 39 | 40 | Symbols and values, whether they be functions, primitives, or more complex 41 | structures, are usually bound with the `let` keyword. Names must begin with 42 | a lowercase letter or underscore. 43 | 44 | All values must have a static type, but these types often do not need to be 45 | declared in use. In most cases, they can be inferred by the compiler via 46 | Hindley-Milner classification. */ 47 | 48 | let x = 5; // : int 49 | let y = 42.0; // : float 50 | let z = "Dinner for one"; // : string 51 | 52 | // Functions will likewise infer parameter and return types. 53 | let addInts = (a, b) => { a + b }; // inferred int for all values via `+` operator. 54 | 55 | // Types can be functions 56 | type intFunction = (int, int) => int; 57 | let intAdder : intFunction = (x, y) => { x + y }; 58 | 59 | // Let bindings are block scoped with braces `{}`. 60 | if (true) { 61 | let val1 = "best of times"; 62 | print_endline(val1) 63 | }; 64 | 65 | let myBlock = { 66 | let val1 = "worst of times"; 67 | print_endline(val1) 68 | }; 69 | 70 | /* The list of reserved words is broadly similar to OCaml, the list for which is 71 | available here: http://caml.inria.fr/pub/docs/manual-ocaml-312/manual044.html. 72 | The Reason-specific list is under development, with the current words 73 | available in the ReasonML source code, on Github. 74 | 75 | https://github.com/facebook/reason/blob/master/src/reason-parser/reason_declarative_lexer.mll#L85-L144 76 | 77 | Prefixing a variable name with an underscore creates a casual variable. 78 | These variables, if unused, will not trigger a compiler warning. 79 | Unused variable warnings only apply to block scopes and will not be 80 | raised on variables declared at the global or module level. 81 | 82 | There are two kinds of unused variable. A suspicious unused variable is one 83 | that has been bound with `let` or `as`. An innocuous variable is one that 84 | has not been been bound with one of the primary binding verbs. */ 85 | 86 | let blockScope = () => { 87 | let variable = 42; // This triggers a warning. 88 | let _casualVariable = 2001; // This does not trigger a warning. 89 | "Return string" 90 | } 91 | 92 | /* `let` bindings are also lexically scoped. If a `let` binding is declared again, 93 | from that point forward the symbol will reference the later value. */ 94 | 95 | let lexicalValue = "42"; 96 | print_endline(lexicalValue); // "42" 97 | let lexicalValue = "To be or not to be."; 98 | print_endline(lexicalValue); // "To be or not to be." 99 | 100 | 101 | /*** Destructuring ***/ 102 | 103 | // Data structures can be "destructured" for assignment to individual variables. 104 | let aTuple = ("Teacher", 101); 105 | let (name, classNumber) = aTuple; 106 | 107 | print_endline(name); 108 | print_int(classNumber); 109 | 110 | type person = { 111 | firstName : string, 112 | age : int, 113 | }; 114 | 115 | // Variable extractions from records must match field names. 116 | let bjorn = {firstName : "Bjorn", age : 28}; 117 | let {firstName, age} = bjorn; 118 | 119 | // Extractions can be re-named before being bound. 120 | let {firstName : bName, age : bAge} = bjorn; 121 | 122 | // Fields can be ignored either through omission or the anonymous variable `_`. 123 | let {firstName : cName} = bjorn; 124 | let {firstName : dName, age : _} = bjorn; 125 | 126 | 127 | /*** Type annotation ***/ 128 | 129 | // Type annotations can be added when inference does not suffice. 130 | let a : int = 5; 131 | 132 | // Function parameters and returns can be annotated. 133 | let add2 = (a : int, b : int): int => a + b; 134 | 135 | // A type can be aliased using the `type` keyword. 136 | type companyId = int; 137 | let myId: companyId = 101; 138 | 139 | // Mutation, while discouraged, is possible with a `ref()` wrapper. 140 | let myMutableNumber = ref(120); 141 | 142 | // To assign a new value, use the `:=` operator. 143 | myMutableNumber := 240; 144 | 145 | // To access the value in a `ref()`, use the `^` suffix. 146 | let copyOfMyMutableNumber = myMutableNumber^; 147 | 148 | 149 | /*** as ***/ 150 | 151 | /* The second way to perform a symbol/value binding is with `as`. Where a `let` 152 | binding binds a right-hand value to a left-hand symbol, `as` binds left to 153 | right. This syntax exists because of the common practice of "piping" 154 | something from one segment of code into the following segment. This stands 155 | in contrast to the procedural practice of variable assignment and then, 156 | later, variable use. 157 | 158 | Unlike `let`, which must be declared procedurally just like variable declarations 159 | in most other common languages, the `as` binding can only be declared functionally, 160 | since it must be passed through to a succeeding block. 161 | 162 | An important difference between `let` binding and `as` is when patterns are evaluated. 163 | 164 | This is an esoteric concept, so do not be dissuaded if it is at first strange and 165 | incomprehensible. At its root, `as` is an excellent example of the pattern matching 166 | at the root of modern functional languages. */ 167 | 168 | // Since `as` is a binding, it has paramount precedence. 169 | 170 | // https://stackoverflow.com/questions/49840954/keyword-as-in-sml-nj?noredirect=1&lq=1 171 | // https://stackoverflow.com/questions/26769403/as-keyword-in-ocaml 172 | // https://reasonml.chat/t/an-explanation-of-as/1912 173 | 174 | 175 | /* `as` is also used in object assignment in a somewhat similar fashion. See the below 176 | section on objects to see an explanation. */ 177 | 178 | 179 | /*---------------------------------------------- 180 | * Basic operators 181 | *---------------------------------------------- 182 | */ 183 | 184 | /*** > Boolean ***/ 185 | 186 | // A boolean can be either true or false. 187 | let isLearning = true; 188 | 189 | true && false; // - : bool = false; Logical and 190 | true || false; // - : bool = true; Logical or 191 | !true; // - : bool = false; Logical not 192 | 193 | 'a' > 'b'; // - bool : false 194 | 5 < 42; // - bool : true 195 | 196 | 197 | /* Equality */ 198 | 199 | /* Although Reason has `==` and `===`, they are different from JavaScript. 200 | Structural Equality, using the double-glyph operator, does a deep 201 | comparison of each entity's structure. Use this judiciously since 202 | comparing large structures is expensive. */ 203 | 204 | type author = { 205 | name : string, 206 | age : int 207 | }; 208 | 209 | let author1 = { 210 | name : "Charles Dickens", 211 | age : 58 212 | }; 213 | 214 | let author2 = { 215 | name : "Charles Dickens", 216 | age : 58 217 | }; 218 | 219 | let author3 = { 220 | name : "Victor Hugo", 221 | age : 83 222 | }; 223 | 224 | author1 == author2; // - : bool = true 225 | author1 == author3; // - : bool = false 226 | author1 != author3; // - : bool = true 227 | 228 | /* Any attempt at using greater-than or less-than vis-à-vis structures will 229 | trigger a structural comparison. The comparison will return a boolean as 230 | soon as a difference is discovered and will not continue to do a complete 231 | comparison. */ 232 | 233 | let bigObj = [10, 10000000, 10000000]; 234 | let smallObj = [11, 1, 1]; 235 | 236 | bigObj > smallObj; // - : bool = false 237 | 238 | /* Because 10 and 11 are different, and 10 is smaller than 11, false is returned 239 | even though the next two values are much larger. */ 240 | 241 | /* Referential, or physical, equality, using the triple-glyph operator, compares 242 | the identity of each entity. */ 243 | 244 | author1 === author2; // - : bool = false 245 | author1 === author1; // - : bool = true 246 | 247 | 248 | /* Comparing Values */ 249 | 250 | // The equality operators work differently for values instead of structures. 251 | // Both `==` and `===` will become strict equality `===` when compiled to JavaScript. 252 | // Attempting to compare two different types will cause a compile error. 253 | 254 | let myString1 = "A world without string is chaos."; 255 | let myString2 = "A world without string is chaos."; 256 | 257 | "A string" === "A string"; // - : bool = true 258 | "A string" == "A string"; // - : bool = true 259 | 42 === 42; // - : bool = true 260 | 42 == 42; // - : bool = true 261 | // 42 === "A string" // Error 262 | 263 | 264 | /*** Operator Assignment ***/ 265 | 266 | /* Reason is a descendant of Lisp, which means that operations in the language are 267 | conceptually similar to lists of operations and data. As such, while standard 268 | infix operator notation is acceptable, so is prefix, or Polish, notation. */ 269 | 270 | let polishAdder = (a, b) => (+) (a, b); 271 | 272 | // Operators are just functions that can be reassigned. 273 | let (+) = (a, b) => a * b; 274 | 275 | // Custom operators can use any of the reserved characters. 276 | let ($) = (a, b) => a - b + 3; 277 | 4 $ 3 // - : int = 4 278 | 279 | 280 | /*---------------------------------------------- 281 | * Basic types 282 | *---------------------------------------------- 283 | */ 284 | 285 | /*** A note on the standard library (StdLib) ***/ 286 | 287 | /* All of Reason's types and structures have modules in the standard library. In general, 288 | it is best to interface with your data via these modules. The abstraction helps 289 | enable cross-compatibility with native and JavaScript while also providing the usual 290 | security and guarantees that come with using a standard library. 291 | 292 | While a complete discussion of the StdLib is outside the scope of this tutorial, 293 | overviews will be given when pertinent. 294 | 295 | The list of modules can be found here: https://reasonml.github.io/api/index_modules.html */ 296 | 297 | 298 | /*** > String ***/ 299 | 300 | /* Strings in Reason map over to strings in JavaScript well. Strings in JavaScript are 301 | actually immutable under the covers, with the act of changing a string actually creating 302 | an entirely new string and associating it with a variable name. 303 | 304 | In OCaml, strings can be directly changed, although this unsafe behavior is highly 305 | discouraged. Further, since JavaScript cannot do this, attempting to do so runs the 306 | risk of cross-compatibility problems. */ 307 | 308 | // Use double quotes for strings. 309 | let greeting = "Hello world!"; 310 | 311 | // Strings can span multiple lines. When compiled, new line characters are generated. 312 | let aLongerGreeting = "Look at me, 313 | I'm a 314 | multi-line string"; 315 | 316 | // The `{||}` syntax provides functionality akin to backtick strings in JavaScript. 317 | let quotedString = {|It was the best of times, it was the worst of times.|}; 318 | 319 | let multiLineQuotedString = {|To love another person 320 | is to see 321 | the face of God.|} 322 | 323 | // No character escaping is necessary. 324 | let specialCharacters = {|This is fine. !@#'"`$%^&*()|} 325 | 326 | // The `js` marker enables unicode. 327 | let world = {js|🌍|js}; 328 | 329 | // The `j` marker enables variable interpolation. 330 | // The parenthesis are optional but required if characters directly follow the variable 331 | let helloWorld = {j|hello, $(world)|j}; 332 | 333 | // The `$` is reserved to prefix variables and can be escaped with `\`. 334 | let accountBalance = {j|You have \$500.00|j}; 335 | 336 | // Concatenate strings with `++`. 337 | let name = "John " ++ "Wayne"; 338 | let emailSubject = "Hi " ++ name ++ ", you're a valued customer"; 339 | 340 | 341 | /*** > Char ***/ 342 | 343 | /* Use single quotes for a char. Chars compile to an integer between 0 and 255 344 | and thus do not support Unicode or UTF-8. */ 345 | 346 | let lastLetter = 'z'; 347 | 348 | /*** > Integer ***/ 349 | 350 | 1 + 1; // int = 2 351 | 25 - 11; // int = 11 352 | 5 * 2 * 3; // int = 30 353 | 8 / 2; // int = 4 354 | 355 | // Integer division will round results 356 | 8 / 3; // int = 2 357 | 8 / 5; // int = 1 358 | 359 | /* The StdLib provides modules for int32 and int64 operations. Unless these exact 360 | precisions are necessary, the standard int type provides the best 361 | cross-compatibility and performance. */ 362 | 363 | 364 | /*** > Float ***/ 365 | 366 | 1.1 +. 1.5; // float = 2.6 367 | 18.0 -. 24.5; // float = -6.5 368 | 2.5 *. 2.0; // float = 5.0 369 | 16.0 /. 4.0; // float = 4.0 370 | 371 | // Floats and integers can be formatted with underscores 372 | let formattedInt : int = 12_34_56; // int = 123456 373 | let formattedFloat : float = 12_34_56.0; // float = 123456.0 374 | 375 | 376 | /*** > Tuple ***/ 377 | 378 | /* Tuples are a common data structure found in many languages. For JavaScript developers, 379 | the argument list passed into a function is the closest analog to a true tuple. 380 | 381 | Tuples are named from the suffix for counting groups, such as double, quintuple, 382 | or dectuple. For programming purposes, an actual number is used instead of a 383 | latin-based prefix. So instead of triple, 3-tuple is used. 384 | 385 | Tuples can contain two or more values, are immutable and of fixed size, ordered, and 386 | importantly are heterogenous. It is the last point that most distinguishes tuples from 387 | arrays or lists. */ 388 | 389 | let teamMember = ("John", 25); 390 | 391 | // Values can be explicitly typed. 392 | let position2d : (float, float) = (9.0, 12.0); 393 | type namedPoint = (string, float, float); 394 | let city1 = ("London", 51.507222, -0.1275); 395 | 396 | // An individual value in a tuple cannot be used independently. 397 | // To extract a value from a 2-tuple, use the standard library functions `fst()` and `snd()`. 398 | let memberName = fst(teamMember) /* - : string = "John" */ 399 | let memberAge = snd(teamMember) /* - : int = 25 */ 400 | 401 | // Values can also be ignored or extracted via destructuring and the anonymous variable `_`. 402 | let threeTuple = (42, 2001, "A113"); 403 | let (theAnswer, _, _) = threeTuple; // theAnswer = 42 404 | 405 | 406 | /*** > Record ***/ 407 | 408 | /* Records play part of the role that object literals play in JavaScript and appear most like 409 | them syntactically. They are collections of key:value pairs that are by default, but not 410 | necessarily, immutable. The shape of a record -- to wit its key names and value types, but 411 | not the order in which they appear -- is fixed. This shape must be declared with a type. */ 412 | 413 | // Like variable names, a type must begin with an underscore or lowercase letter. 414 | type trainJourney = { 415 | destination : string, 416 | capacity : int, 417 | averageSpeed : float, 418 | }; 419 | 420 | // Once declared, types can be inferred by the compiler. 421 | // The below is inferred as type trainJourney 422 | let firstTrainJourney = { 423 | destination : "London", 424 | capacity : 45, 425 | averageSpeed : 120.0, 426 | }; 427 | 428 | // Use dot notation to access properties. 429 | let maxPassengers = firstTrainJourney.capacity; 430 | 431 | /* If used immutably, records are not directly changed. Instead, copies of previous records 432 | are created with new values assigned to particular keys. Creating a new record from a 433 | previous record uses the spread operator. */ 434 | 435 | let secondTrainJourney = {...firstTrainJourney, averageSpeed: 105.0}; 436 | 437 | /* Using records mutably is not generally recommended. That said, it is a powerful tool in 438 | certain situations. A record cannot be made mutable in its entirety. Individual keys can 439 | be made mutable with the `mutable` keyword. */ 440 | 441 | type breakfastCereal = { 442 | name : string, 443 | mutable amount : int, 444 | }; 445 | 446 | let frostedFlakes = { 447 | name : "Kellog's Frosted Flakes", 448 | amount : 500, 449 | }; 450 | 451 | frostedFlakes.amount = 200; 452 | 453 | /* Match a variable's name & type to a record's key:value to allow "punning", or assigning 454 | via inferred structure. */ 455 | let name = "General Mills Chex"; 456 | let amount = 250; 457 | let chex = { // Note how the type did not need to be explicitly declared 458 | name, 459 | amount, 460 | }; 461 | 462 | 463 | /*** Object ***/ 464 | 465 | /* Objects are similar to records but are more class-like in their semantics. 466 | Notably, they are more like the original conception of objects in that they 467 | are defined BY THEIR BEHAVIORS, to wit they only expose methods; all values 468 | are private. 469 | 470 | Like classes, objects have private and public methods, identified with the 471 | `pub` and `pri` keywords. All values are labeled with the `val` keyword. 472 | Objects have `this` implicitly attached as a binding to all instances of objects. 473 | 474 | While objects in Reason are superficially similar to JavaScript objects, they 475 | do not directly transpile to them. */ 476 | 477 | // Objects are accessed with hash notation `#`. 478 | let newObject = { 479 | val aNumber = ref(42); 480 | pri increment = (num: int) => aNumber := aNumber^ + num; 481 | pub addNumber = (inputValue: int) => { 482 | this#increment(inputValue); 483 | aNumber^ 484 | }; 485 | }; 486 | 487 | /* Objects do not need types but can have them. There are two categories of type: 488 | open and closed. They are identified with either one or two dots as the leading 489 | token of the type's structure. The single dot in the type indicates that this 490 | is a closed type, meaning that all instances of this type must have a public 491 | interface of exactly this shape. */ 492 | 493 | type truck = { 494 | . 495 | buildTruck : (~make : string, ~model : string) => string 496 | }; 497 | 498 | let newTruck : truck = { 499 | val drive = 4; 500 | pub buildTruck = (~make, ~model) => {j|You built a new $make $model with $drive-wheel drive.|j}; 501 | 502 | // This method is private and will not violate the public shape. 503 | pri buildEngine = (~cylinders) => {j|You installed a $(cylinders)-cylinder engine.|j} 504 | 505 | // This public method would throw an error because it violates the object shape. 506 | // pub intruder = "I'm not supposed to be here!"; 507 | }; 508 | 509 | let toyotaTundra = newTruck#buildTruck(~make="Toyota", ~model="Tundra"); 510 | 511 | /* The double dot, called an elision, indicates that the below is an open type. 512 | This means that instances of this type do not need to have this exact shape. 513 | This flexibility means that object types, unlike records, are NOT inferred. 514 | They must be explicitly declared. */ 515 | 516 | type car('a) = { 517 | .. 518 | accelerate: unit => unit, 519 | brake: unit => unit 520 | } as 'a; 521 | 522 | /* Since open types do not mandate a shape, they are necessarily polymorphic, 523 | meaning that a closed type must be provided when this object is instantiated. 524 | The object that is created is classified as the supplied type with the `as` keyword. 525 | 526 | The undetermined type is represented by the `'a`, commonly referred to as 527 | "alpha." Alpha's sibling is `'b`, known as "beta." Alpha commonly represents 528 | a function's input type while beta represents its return type. This is only 529 | convention, as the ' simply identifies a type label and the succeeding string 530 | can be anything. 'steve is equally valid to 'a. 531 | 532 | Below, since `car` is an open type, a closed type is supplied in-line. This in-line 533 | type is anonymous and only available to the toyotaSupra that has been created. A 534 | named type could be created and passed in as well. */ 535 | 536 | let toyotaSupra: car({. accelerate: unit => unit, brake: unit => unit, checkSpeed: int}) = { 537 | // as _this; 538 | val speed = ref(0); 539 | pub accelerate = () => { 540 | if (speed^ < 155) {speed := speed^ + 1}; 541 | }; 542 | pub brake = () => { 543 | if (speed^ > 0) {speed := speed^ - 1}; 544 | }; 545 | pub checkSpeed = speed^; 546 | }; 547 | 548 | /* The above example is illustrative of the earlier point about `as` vis-a-vis objects. 549 | A warning underlines the speed val because objects implicitly contain `this`. 550 | `this` represents the object itself and not simply its behaviors or values. The 551 | warning will go away either through the use of a private method requiring `this`, 552 | such as `this#privateMethod`, or through assigning `this` as a casual variable. 553 | The latter can be achieved with the `as` keyword. 554 | 555 | Uncomment the `as _this;` after toyotaSupra's opening bracket to clear the warning. This 556 | syntax captures the `this` that is implicitly being passed into the object 557 | construction and reassigns it as the casual `_this`. The usage of `_this` is purely 558 | for illustrative purposes. The captured `this` can be assigned any casual value to 559 | prevent the warning. `_t` or `_Steve` are equally valid. 560 | 561 | As of this writing, April 4th, 2020, IDEs seem to have problems with correctly 562 | underlining object warnings of this sort and will incorrectly underline parts of code. 563 | Ignore them. The ultimate arbiter of correct code is the Bucklescript compiler and its 564 | console output. 565 | 566 | *** A note on Objects *** 567 | 568 | The reader perhaps has discerned conflict inherent to objects as described above and 569 | has forseen a project's future where massive numbers of objects, both open and closed, 570 | have been created to better match patterns found in languages like Java or C#. These 571 | objects would rapidly become brittle, fragile, and difficult to extend, even with type 572 | safety. A full dissection of these concerns is beyond the scope of this tutorial, but 573 | suffice it to say that while these abilities are in the language, using them to mimic 574 | OOP patterns from other languages is widely seen as bad practice. They have their place, 575 | but only within the context of broader functional concepts. */ 576 | 577 | 578 | /*** > Variant ***/ 579 | 580 | /* A variant is a type declaration with multiple states. The possible states are called 581 | constructors because they are not types themselves; they are symbolic identifiers. Further, 582 | since they are symbols, they should be unique within the current scope, just as any other 583 | declaration. */ 584 | 585 | type authType = 586 | | GitHub 587 | | Facebook 588 | | Google 589 | | Password; 590 | 591 | let userPreferredAuth = GitHub; 592 | 593 | /* The compiler now knows that `userPreferredAuth` is of type `authType`. and 594 | that it must necessarily be in one of four states. */ 595 | 596 | /* Just like any other declaration, how variant constructors are inferred by the type system 597 | can be changed as the lexical scope progresses. Below, the `authTypeGit` also contains a 598 | constructor called `GitHub`. As such, any later uses of the `GitHub` constructor will cause 599 | the compiler to infer a type of `authTypeGit`. For `gitUser1`, this inference is prevented 600 | through explicit type declaration. 601 | 602 | This situation highlights an interesting aspect of ReasonML's type system: requiring explicit 603 | type declarations becomes a code smell. */ 604 | 605 | type authTypeGit = 606 | | GitHub 607 | | Gitlab 608 | | Gitter; 609 | 610 | let gitUser1 : authType = GitHub; 611 | let gitUser2 = GitHub; 612 | 613 | /* Constructors "construct" because they accept types as parameters. These types can 614 | themselves be variants. */ 615 | 616 | type userRegion = 617 | | Asia 618 | | Europe 619 | | South_america 620 | | North_america; 621 | 622 | type userType = 623 | | Admin(authType) 624 | | Guest 625 | | Moderator(authType, userRegion); 626 | 627 | let newUser = Moderator(GitHub, Europe); 628 | 629 | 630 | /*** > Option ***/ 631 | 632 | /* At the root of Reason's safety is the concept of possible existence. This is 633 | not like `null` or `undefined` but an explicit state of nothing. Possible 634 | existence is handled with the `Option` type. Options are like a variant with 635 | two states, Some() and None. 636 | 637 | Those coming from other functional languages will possibly recognize Some() 638 | as a monad. For the purposes of this section, a monad is a wrapper of known 639 | structure that can contain other data. The benefits of monads are as a 640 | high-level abstraction of computation and structure. They will be further 641 | discussed in a later section. */ 642 | 643 | Random.self_init(); 644 | let isThingHere = Random.bool(); 645 | 646 | // Work in progress. 647 | 648 | // sayToThing("Thank you, Thing."); 649 | 650 | /* isThingHere is of type `option` and either returns a Some() that contains a 651 | string or None which contains nothing. The compiler will require code that references 652 | `isThingHere` to account for both states. */ 653 | 654 | 655 | /*** > List ***/ 656 | 657 | /* A singly-linked list is a language-level structure in ReasonML. They are standard 658 | singly-linked lists in that they are identified by their heads, they are ordered, their 659 | consituents are immutable and homogenous, prepending and splitting are constant-time 660 | operations, while access & updates are linear time operations. */ 661 | 662 | // A list is declared with `[ ]` with comma-separated values. 663 | let userIds = [1, 4, 8]; 664 | 665 | // The type can be explicitly set with list('a) where 'a is the type 666 | type idList = list(int); 667 | type attendanceList = list(string); 668 | 669 | /* Just like records, lists are immutable but the contents of a list can be copied using 670 | the spread operator. The spread does not work exactly like JavaScript in that this is 671 | syntactic sugar for OCaml's `::` operator. As such, you cannot spread a list for 672 | appending, only prepending. */ 673 | 674 | let newUserIds1 = [101, 102, ...userIds]; 675 | // let newUserIds1 = [...userIds, 101, 102]; // Invalid use 676 | 677 | 678 | /*** > Array ***/ 679 | 680 | /* Arrays are similar to arrays in other languages. Because ReasonML straddles two worlds, 681 | native and JavaScript, the characteristics of arrays differ slightly depending on which 682 | world is being targeted. When transpiling to JavaScript, arrays are variable size and 683 | can grow at runtime. When targeting native applications, arrays are fixed size. This 684 | behavior is abstracted from the developer and usage of the Js.Array module is acceptable 685 | for native work. */ 686 | 687 | // An array is declared with `[| |]` with comma-separated values. 688 | let languages = [|"Reason", "JavaScript", "OCaml"|]; 689 | 690 | // Access indices with the `[i]` syntax. 691 | languages[1] // "JavaScript" 692 | 693 | 694 | /*---------------------------------------------- 695 | * Function 696 | *---------------------------------------------- 697 | */ 698 | 699 | /* Since ReasonML is a functional language, functions are distinctly different from many 700 | other languages. Since they are treated as evaluations, to wit evaluations of 701 | mathematical functions, there is no such thing as a `return` command. Whatever value 702 | exists at the end of the evaluation block is implicitly returned. Further, all 703 | functions MUST return something. If no final value is present, the function will return 704 | the special value `unit`, which will be discussed shortly. */ 705 | 706 | // Fat arrow syntax declares a function. 707 | // Parenthesis are optional on single-parameter functions. 708 | let signUpToNewsletter = (email) => "Thanks for signing up " ++ email; 709 | 710 | let getEmailPrefs = (email) => { 711 | let message = "Update settings for " ++ email; 712 | let prefs = ["Weekly News", "Daily Notifications"]; 713 | 714 | (message, prefs); // Implicitly returned 715 | }; 716 | 717 | // Call a function with the standard syntax. 718 | signUpToNewsletter("hello@reason.org"); 719 | 720 | 721 | /*** Unit ***/ 722 | 723 | /* Reason has a special type called `unit`. It is the concept of a "thing" 724 | that is "nothing." It is different from `None` in that `None` is the state 725 | of nothing being where `Some()` could also have been. `Unit` is the state of 726 | expected nothing. It is similar to `void` in other languages. Unit can be 727 | declared with the `unit` type keyword or the `()` syntax. */ 728 | 729 | // Unit's first use is in declaring functions that take no arguments. 730 | let noArgument = () => { "I've got nothing" }; 731 | 732 | /* All functions necessarily return something, so if there is no expression 733 | intended for return, such as in functions that only handle side-effects, then 734 | that function will return `unit`. Functions that return `unit` can be 735 | explicitly typed. */ 736 | 737 | let noReturn = (input) : unit => { print_endline("I just print " ++ input) }; 738 | 739 | /* The above function expects to return nothing and will throw a compile error 740 | if anything is returned. */ 741 | 742 | 743 | /*** > Labeled Paramters/Arguments ***/ 744 | 745 | type position = { 746 | posx : float, 747 | posy : float 748 | } 749 | 750 | // Parameters can be labeled with the `~` symbol. 751 | let moveTo = (~x, ~y) => {posx : x, posy : y}; 752 | 753 | // Any order of labeled arguments is acceptable. 754 | moveTo(~x=7.0, ~y=3.5); 755 | moveTo(~y=3.5, ~x=7.0); 756 | 757 | // Labeled parameters can also have an alias used within the function. 758 | let getMessage = (~message as msg) => "Message for you, sir... " ++ msg; 759 | 760 | // Labeled parameters support explicit typing. 761 | let logMessage = (~message: string) => { 762 | print_endline(message); 763 | }; 764 | 765 | let logAnotherMessage = (~message as msg: string) => { 766 | print_endline(msg); 767 | }; 768 | 769 | 770 | /*** > Currying ***/ 771 | 772 | /* Curring is the act of decomposing a function that takes multiple 773 | arguments into a series of functions that each take one argument. 774 | 775 | In practice, this is done automatically by calling a function with 776 | fewer arguments than it has parameters. For example, if a function 777 | has `n` parameters, and it is called with `x` arguments, a function 778 | with `n - x` parameters is returned. 779 | 780 | All functions are curriable by default in ReasonML. */ 781 | 782 | let divide = (denom, numr) => numr / denom; 783 | let divideBySix = divide(6); 784 | let divideByTwo = divide(2); 785 | 786 | divide(3, 24); /* int = 8 */ 787 | divideBySix(12); /* int = 2 */ 788 | divideByTwo(10); /* int = 5 */ 789 | 790 | // To partially call with deeper arguments, the anonymous variable `_` is used. 791 | 792 | let divideSixBy = divide(_, 6); 793 | let divideTenBy = divide(_, 10); 794 | 795 | divideSixBy(2); /* int = 3 */ 796 | divideTenBy(2); /* int = 5 */ 797 | 798 | // Labeled parameters allow currying without the need for the anonymous variable. 799 | 800 | let labeledDiv = (~denom, ~numr) => numr / denom; 801 | let labeledDivBySix = labeledDiv(~denom = 6); 802 | let labeledDivByTwo = labeledDiv(~denom = 2); 803 | 804 | labeledDivBySix(~numr=36); /* int = 6 */ 805 | labeledDivByTwo(~numr=4); /* int = 2 */ 806 | 807 | 808 | /*** > Optional Labeled Parameters/Arguments ***/ 809 | 810 | /* Using the `=?` qualifier makes a parameter optional, meaning that it is turned 811 | into an Option type. This means that the parameter cannot simply be used in the 812 | function. In the below example, a switch statement is used. This functionality 813 | will be discussed shortly, but is mostly self-explanatory here. 814 | 815 | The final parameter of a function with optional arguments must be unit `()`. 816 | This syntax prevent currying. */ 817 | 818 | let greetPerson = (~name, ~greeting=?, ()) => { 819 | switch(greeting) { 820 | | Some(greet) => greet ++ name 821 | | None => "Hello, " ++ name 822 | }; 823 | }; 824 | 825 | // Calling a function without optional arguments requires unit as the final argument. 826 | greetPerson(~name="Kate", ()); 827 | 828 | 829 | /*** > Pipe ***/ 830 | 831 | /* ReasonML has two ways to call functions: the standard syntax and the pipeline 832 | operator. 833 | 834 | Conceptually, it is simple. Where `myFunction(value)` says "perform myFunction 835 | with this value", `(value) -> myFunction` says "send myValue to myFunction." 836 | 837 | There is actually a subtle semantic difference between the two. Where the first 838 | calls the function, the second does not. Instead, it takes a value and applies 839 | a function to it. From a semantic perspective, this means that calling a 840 | function either with incorrect data or no data is impossible, since the function 841 | is not what is being called. 842 | 843 | From a syntactic standpoint, this facilitates the easy passing of one function's 844 | output to another's input. Users of command line interfaces such as Bash will be 845 | accustomed to this pattern. 846 | 847 | Since pipes, either first or last, require positional information to work, they 848 | are incompatible with functions with labeled parameters. 849 | 850 | People who are staying abreast of JavaScript development may notice that this 851 | syntax is currently in the experimental stage in the language specification. 852 | JavaScript is adopting many ideas from the ML line of languages. */ 853 | 854 | let subtract = (x, y) => { x - y }; 855 | let subtractTwo = subtract(_, 2); 856 | 857 | 3 -> subtractTwo; // int = 1 858 | 859 | /* The thin arrow syntax is called "pipe-first" since the value passed in 860 | is used as the first argument in the receiving function. */ 861 | 862 | let subtractFromThree = 3 -> subtract; 863 | 864 | /* The `|>` operator is called pipe-last, or confusingly, the 865 | reverse-application operator, which passes in the final argument. 866 | 867 | For single-parameter functions, the pipe operators behave identically. */ 868 | 869 | let subtractFive = 5 |> subtract; 870 | 871 | let addOne = a => a + 1; 872 | let divByTwo = a => a / 2; 873 | let multByThree = a => a * 3; 874 | 875 | let pipedValue = 3 -> addOne -> divByTwo -> multByThree; // int = 6 876 | 877 | 878 | /*---------------------------------------------- 879 | * Control Flow & Pattern Matching 880 | *---------------------------------------------- 881 | */ 882 | 883 | /*** > If-else ***/ 884 | 885 | /* Like functions, `if` and `else` are evaluation blocks that will 886 | return the final expression within the block. Just as with functions, 887 | there is no early return. */ 888 | 889 | let isMorning = true; 890 | let newGreeting = if (isMorning) { 891 | let goodMorning = "Good morning!"; 892 | goodMorning; 893 | } else { 894 | let genericGreeting = "Hello!"; 895 | genericGreeting; 896 | }; 897 | 898 | /* For blocks that do not return anything, such as side effects, `unit` 899 | is implicitly returned even if there is nothing to accept it. */ 900 | 901 | let logSomething = true; 902 | if (logSomething) { 903 | print_endline("Logged!"); 904 | // `unit` is returned. 905 | }; 906 | 907 | /* 'if` does not exist independently. It is always paired with `else`, 908 | either implicitly or explicitly. */ 909 | 910 | // let isItTrue = if (false) { 911 | // "It's not true!" 912 | // }; 913 | 914 | /* The above code will produce a compile error. The `else` block is 915 | unwritten, and thus implicitly returns `unit`, where the if block 916 | returns a string. Both branches must return the same type. The 917 | below illustrates the correct form. 918 | */ 919 | 920 | let isItReallyTrue = if (false) { 921 | "It's really not true!"; 922 | } else { 923 | "It's actually true!"; 924 | }; 925 | 926 | 927 | /*** > Loops ***/ 928 | 929 | /* Loops are similar to other languages except that Reason has no `break` or 930 | `continue`. This is consistent with the idea that any procedure must have 931 | a single entrance and a single exit, just as a mathematical algorithm 932 | would have. */ 933 | 934 | /* For Loop */ 935 | 936 | let loopStart = 1; 937 | let loopEnd = 42; 938 | 939 | for (x in loopStart to loopEnd) { 940 | print_int(x); 941 | print_string(" "); 942 | }; 943 | 944 | // Reason allows decrementing in loops with the `downto` statement. 945 | 946 | let dLoopStart = 42; 947 | let dLoopEnd = 1; 948 | 949 | for (x in dLoopStart downto dLoopEnd) { 950 | print_int(x); 951 | print_string(" "); 952 | }; 953 | 954 | /* Imperative loops are seen as bad practice because even in a secure, highly 955 | symbolic language like Reason, fence post errors are possible. In the below, 956 | the code will compile but result in an 'index out of bounds' error when 957 | run because the final index called is 4. */ 958 | 959 | let testArray = [|1, 2, 3, 42|]; 960 | let testArrayLength = Belt.Array.length(testArray); 961 | 962 | for (x in 0 to testArrayLength) { 963 | print_int(testArray[x]); 964 | }; 965 | 966 | /* While Loop */ 967 | 968 | /* While Loops are more or less similar to other languages, but since a value 969 | is immutable, a `ref()` wrapper must be used 970 | 971 | Just as with For Loops, While Loops should be avoided since the constraints 972 | are not being set by the data under calculation. For Loops risk fence post 973 | errors and While Loops risk infinite loops. In the below, if `testVariable` 974 | were never set to false, the loop would not stop. */ 975 | 976 | let testVariable = ref(true); 977 | while (testVariable^) { 978 | print_endline("It's true."); 979 | testVariable := false; 980 | } 981 | print_endline("It's now false."); 982 | 983 | 984 | /*** > Pattern Matching ***/ 985 | 986 | /* Pattern matching is ReasonML's crown jewel. It helps prevent all manner 987 | of errors and unintended behaviors and offers a profound competitive 988 | advantage in comparison to other languages and tools. It's power is so 989 | great, that almost all other major languages either have implemented, 990 | or are in the process of implementing, pattern matching. 991 | 992 | Pattern matching uses decomposition of input to analyze the relationship 993 | of tokens to find set patterns. This stands in contrast to a direct 994 | comparison of two values sitting in memory. That sounds fighteningly 995 | complex but it is actually rather straightforward. A complete discussion 996 | of pattern matching on a theoretical level is beyond the scope of this 997 | tour, but it is encouraged to read the Wikipedia page on the subject. 998 | 999 | https://en.wikipedia.org/wiki/Pattern_matching 1000 | 1001 | In ReasonML, as with many functional languages, pattern matching is used 1002 | for all comparisons. Sometimes this acts like common value comparisons in 1003 | other languages, like `x === y` in JavaScript. But unlike simple comparisons, 1004 | a pattern has a finite number of states, meaning that the compiler can warn 1005 | the programmer if all possible patterns are not accounted for. This power 1006 | becomes apparent with `switch` statements. */ 1007 | 1008 | 1009 | /*** > Switch ***/ 1010 | 1011 | /* The `switch` statement is similar to JavaScript's `switch` but enhanced by 1012 | the power of pattern matching. Indeed, in OCaml, it is called `Match`. In 1013 | this example, the previous `authType` variant is used. */ 1014 | 1015 | type dndPlayer = 1016 | | DungeonMaster 1017 | | Barbarian 1018 | | Thief 1019 | | Wizard; 1020 | 1021 | let newDndPlayer = Barbarian; 1022 | 1023 | let loginMessage = 1024 | switch (newDndPlayer) { 1025 | | DungeonMaster => "May adventurers speak of your tales for generations." 1026 | | Barbarian => "May your enemies crumble before your might." 1027 | | Thief => "May your enemies see nothing before their deaths." 1028 | | Wizard => "May your enemies burn from the power in your hands." 1029 | }; 1030 | 1031 | /* All four possible states of the dndPlayer type must be accounted for. If the 1032 | switch case for `Wizard` were deleted, the error "this pattern-matching 1033 | is not exhaustive." would be raised by the compiler. Similarly, whenever 1034 | an `option` is used, any `switch` statement must take into account both 1035 | possible states, `some()` and `none`. */ 1036 | 1037 | let userId = Some(23); 1038 | let alertMessage = 1039 | switch (userId) { 1040 | | Some(id) => "Welcome, your ID is" ++ string_of_int(id) 1041 | | None => "You don't have an account!" 1042 | }; 1043 | 1044 | /* As stated, pattern matching is not simply a comparison of values. Complex 1045 | structures can be tested and matched. In this case, the possible analysis 1046 | space is infinite, as opposed to a finite variant, so the final case is the 1047 | anonymous variable, indicating a catch-all that captures everything not 1048 | caught by the previous cases. Just as with `authType`, if the catch-all is 1049 | deleted, a non-exhaustive pattern match error will be raised by the compiler. */ 1050 | 1051 | let firstNames = ["James", "Jean", "Geoff"]; 1052 | switch (firstNames) { 1053 | | [] => "No names" 1054 | | [first] => "Only " ++ first 1055 | | [first, second] => "A couple of names " ++ first ++ "," ++ second 1056 | | [first, second, third] => "Three names, " ++ first ++ ", " ++ second ++ ", " ++ third 1057 | | _ => "Lots of names" // This cannot be deleted. 1058 | }; 1059 | 1060 | let event = 5; 1061 | switch (event) { 1062 | | 1 => "Green" 1063 | | 0 | 5 => "Red" 1064 | | 2 | 3 | 4 => "Black" 1065 | | _ => "Gray"; 1066 | }; 1067 | 1068 | let importantNumbers = [42, 2001, 31459]; 1069 | // let [answer, yearWeMakeContact, pi] = importantNumbers; 1070 | 1071 | /* The above code triggers a non-exhaustive error because lists and arrays 1072 | are of potentially variable size. In this example, the list is a known fixed size, 1073 | so the error is unfounded. This is another example of enforced protection against 1074 | null and undefined errors. 1075 | 1076 | This should only throw a warning, but there is a bug in Bucklescript at the moment 1077 | that causes a full compile failure. This bug exists as of April 26th, 2020. 1078 | 1079 | The idiomatically correct way to destructure entities of unknown size is through 1080 | pattern matching into a fixed-size tuple. While this may seem like unecessary 1081 | boilerplate, it is another example of Reason's rigid enforcement of type security. */ 1082 | 1083 | let (answer, yearWeMakeContact, pi) = switch (importantNumbers) { 1084 | | [a, b, c] => (a, b, c) 1085 | | _ => (0, 0, 0) 1086 | }; 1087 | 1088 | /*** > When clause ***/ 1089 | 1090 | let isJohn = a => a == "John"; 1091 | let maybeName = Some("John"); 1092 | 1093 | /* When can add more complex logic to a simple switch */ 1094 | let aGreeting = 1095 | switch (maybeName) { 1096 | | Some(name) when isJohn(name) => "Hi John! How's it going?" 1097 | | Some(name) => "Hi " ++ name ++ ", welcome." 1098 | | None => "No one to greet." 1099 | }; 1100 | 1101 | 1102 | /*** > Exception ***/ 1103 | 1104 | /* Unlike many other languages, exceptions in Reason and OCaml are truly intended 1105 | to represent exceptional circumstances in your code. Exceptions are a special 1106 | variant, just as `option` is. But unlike `option`, exception can be extended 1107 | with custom constructors. */ 1108 | 1109 | // Define custom exceptions with the `exception` statement. 1110 | exception Impossible_Age; 1111 | 1112 | type patientDetails = { 1113 | name : string, 1114 | age : int, 1115 | height : int, 1116 | weight : float 1117 | } 1118 | 1119 | // Trigger an exception with the `raise` call. 1120 | let validatePatientAge = (patient : patientDetails) => 1121 | if (patient.age < 122 && patient.age > 0) { 1122 | "Now seeing " ++ patient.name ++ "." 1123 | } else { 1124 | raise(Impossible_Age); 1125 | }; 1126 | 1127 | let newPatient = {name : "Jeanne Calment", age : 122, height : 150, weight : 55.0}; 1128 | 1129 | // Pattern match on the exception Under_Age. 1130 | switch (validatePatientAge(newPatient)) { 1131 | | status => print_endline(status) 1132 | | exception Impossible_Age => 1133 | print_endline(newPatient.name ++ " - Invalid Age : " ++ string_of_int(newPatient.age) ) 1134 | }; 1135 | 1136 | /* Try */ 1137 | 1138 | /* Try blocks are a special switch specifically intended to handle exceptions. 1139 | As such, they do not need the exception label. */ 1140 | 1141 | let messageToEvan = 1142 | try (validatePatientAge(newPatient)) { 1143 | | Impossible_Age => newPatient.name ++ " - Invalid Age : " ++ string_of_int(newPatient.age) 1144 | }; 1145 | 1146 | /* It should be noted that in the above examples, exceptions were not needed. 1147 | Ordinary variants and options would have been sufficient. Exceptions exist 1148 | primarily for performance reasons in OCaml, where an exception is much 1149 | faster than the more usual forms of control flow and handling. In JavaScript, 1150 | this is not the case. As such, if you are targeting JavaScript, there is 1151 | genuinely no reason to use exceptions. */ 1152 | 1153 | 1154 | /*---------------------------------------------- 1155 | * Modules & Interfaces 1156 | *---------------------------------------------- 1157 | */ 1158 | 1159 | /* A module is essentially a namespace. A reasonable analog from other languages 1160 | is the concept of a class. Basically, it is a hunk of isolated code. */ 1161 | 1162 | // Create a new module with the `module` statement. 1163 | module Staff = { 1164 | type role = 1165 | | Delivery 1166 | | Sales 1167 | | Other; 1168 | 1169 | type staffMember = { 1170 | employeeName : string, 1171 | role, 1172 | }; 1173 | 1174 | let defaultRole = Other; 1175 | 1176 | let getRoleDirectionMessage = staff => 1177 | switch (staff.role) { 1178 | | Delivery => "Deliver it like you mean it!" 1179 | | Sales => "Sell it like only you can!" 1180 | | Other => "You're an important part of the team!" 1181 | }; 1182 | }; 1183 | 1184 | // A module can be accessed with dot notation. 1185 | let employee : Staff.staffMember = { employeeName : "Wilma", role : Staff.Delivery }; 1186 | 1187 | /* As stated, modules provide namespacing, meaning that what is within the scope 1188 | of a module is not within the current working scope. To bring a module's 1189 | contents into the working scope, use the `open` command. This process is not 1190 | a copy, but a reference. The contents of a module is made visible to 1191 | another. */ 1192 | 1193 | module NewStaff = { 1194 | open Staff; 1195 | let newRole = Delivery; 1196 | let newEmployee: staffMember = {employeeName: "Fred", role: Other}; 1197 | } 1198 | 1199 | /* Continuing the analogy of a module as a class, a module can be extended using 1200 | the `include` command. Using `include` brings the contents of one module into 1201 | the scope of another module. While this may superficially seem similar to 1202 | `open`, `include` actually copies the content of one module into another while 1203 | still allowing for value overrides. */ 1204 | 1205 | module SpecializedStaff = { 1206 | include Staff; 1207 | 1208 | let ceo: staffMember = {employeeName: "Barnie", role: Other}; 1209 | 1210 | let defaultRole = Delivery; 1211 | 1212 | let getMeetingTime = staff => 1213 | switch (staff) { 1214 | | Other => 11_15 1215 | | _ => 9_30 1216 | }; 1217 | }; 1218 | 1219 | /* The power of include comes from levels beyond two. Since include copies the 1220 | contents, when a third module opens or includes the second, it still has 1221 | access to the contents of the first module. */ 1222 | 1223 | module Module1 = { 1224 | type musician = 1225 | | Classical 1226 | | Jazz 1227 | | Rock 1228 | | Hiphop 1229 | | Electronic; 1230 | } 1231 | 1232 | module Module2 = { 1233 | open Module1; 1234 | let newMusician = Classical; 1235 | } 1236 | 1237 | module Module3 = { 1238 | include Module1; 1239 | let newMusician = Classical; 1240 | } 1241 | 1242 | /* In both Module2 and Module3, the type of `newMusician` does not need to be 1243 | explicitly stated. But since Module2 only opened Module1, while Module3 1244 | included it, later modules must treat them differently. */ 1245 | 1246 | module Module4 { 1247 | open Module2; 1248 | let externalRef = newMusician; 1249 | // let newMusician2 = Classical; // Error : Unbound constructor Classical 1250 | } 1251 | 1252 | module Module5 { 1253 | include Module2; 1254 | let externalRef = newMusician; 1255 | // let newMusician2 = Classical; // Error : Unbound constructor Classical 1256 | } 1257 | 1258 | /* In Module4 and Module5, both will produce errors regardless of whether `open` 1259 | or `include` is used since module2 only opened Module1. It is important to 1260 | note that the log of `newMusician` remains valid since the static value is 1261 | still passed even though the constructor is not. */ 1262 | 1263 | module Module6 { 1264 | open Module3; 1265 | let externalRef = newMusician; 1266 | let newMusician2 = Classical; 1267 | } 1268 | 1269 | module Module7 { 1270 | include Module3; 1271 | let externalRef = newMusician; 1272 | let newMusician2 = Classical; 1273 | } 1274 | 1275 | /* Both Module6 and Module7 will compile correctly since they reference Module3, 1276 | which included Module1. Thus, `include` allows module chaining, whereas `open` 1277 | only allows a parent-child relationship. */ 1278 | 1279 | 1280 | /*** Records, Variants, and Other Files ***/ 1281 | 1282 | /* As stated, all files are by default modules. Because of this, unlike in other 1283 | languages, Reason does not require explicit imports of files. Instead, a 1284 | module name need simply be referenced in the code itself. The compiler will 1285 | recursively search the file tree for definitions, using the current file as 1286 | the root. */ 1287 | 1288 | // Since module names must be capitalized, so must filenames. 1289 | 1290 | module Module_in_external_file = { // From Module_in_external_file.re 1291 | type externalVariant = 1292 | | Thing1 1293 | | Thing2; 1294 | 1295 | type externalType = { 1296 | key1: string, 1297 | key2: int, 1298 | }; 1299 | } 1300 | 1301 | /* The above module was explicitly declared only for the sake of allowing this 1302 | tutorial to compile. Since all files are modules by default, the `externalVariant` 1303 | and `externalType` would usually be the only thing in the file. */ 1304 | 1305 | module Current_working_file = { 1306 | let newThing : Module_in_external_file.externalVariant = Thing1; 1307 | } 1308 | 1309 | /* All files intended for import must have unique names to ensure that the recursive 1310 | search does not suffer from conflicts. This encourages semantic file naming instead 1311 | of semantic directory structuring and thus a flat file structure. This flies in 1312 | the face of conventions in many other languages but provides some security. A file 1313 | named `card.js`, when removed from its semantic directory, is meaningless. As such, 1314 | the information is fragile. But a filename such as `Left_hand_menu_account_card.js`, 1315 | while long, is robustly semantic. This file can sit anywhere and still communicate 1316 | its nature. */ 1317 | 1318 | 1319 | /*** Interfaces ***/ 1320 | 1321 | /* By default, everything in a module is exported and made available to code that 1322 | references it. To customize what pieces of a module are made visible, an interface must 1323 | be created. This is also called a signature because every module has and projects a 1324 | signature, but is by default implicit. 1325 | 1326 | Interfaces are a specification. They dictate what a module must provide to be of a type. 1327 | As such, module types must be explicitly declared on modules that fulfill all requirements 1328 | of that type. If Module8 did not provide `visibleThing` and `visibleFunction`, an error 1329 | would be produced. 1330 | 1331 | Interfaces are only inclusive, not exclusive in their requirements. Below, Module8 has 1332 | a second value/function pair which does not cause a failure, it only causes an unused 1333 | value warning since, while the interface does not exclude their existence, it does not expose 1334 | them to the outside. 1335 | 1336 | If an interface is contained in a separate file, it must be inside an interface file 1337 | with an identical name as the module file but with the extension `.rei`. In this 1338 | scenario, the type of the module does not need to be explicitly declared. The 1339 | compiler knows to bind the signature to the module because of the matching file names. 1340 | 1341 | If contained within the same file as the module, the `type` statement is used. */ 1342 | 1343 | module type Module8Interface = { 1344 | let visibleThing : int; 1345 | let visibleFunction : (int, int) => int; 1346 | }; 1347 | 1348 | module Module8 : Module8Interface = { 1349 | let visibleThing = 2001; 1350 | let visibleFunction = (x,y) => { 1351 | x * y 1352 | }; 1353 | 1354 | let invisibleThing = 42; 1355 | let invisibleFunction = (a,b) => { 1356 | a ++ b 1357 | }; 1358 | }; 1359 | 1360 | module Module9 = { 1361 | include Module8; 1362 | print_int(visibleFunction(2,3)); 1363 | // print_endline(invisibleFunction("Hello,", "Goodbye")); // Unbound value error. 1364 | } 1365 | 1366 | // Module interfaces can be in-lined. 1367 | 1368 | module Module10 : { 1369 | let func1: (int,int) => int; 1370 | let func2: (string,string) => string; 1371 | } = { 1372 | let func1 = (x,y) => { 1373 | x + y 1374 | }; 1375 | let func2 = (a,b) => { 1376 | a ++ b 1377 | }; 1378 | }; 1379 | 1380 | type myVariant = 1381 | | State1 1382 | | State2 1383 | | State3(string); 1384 | 1385 | let aThing = State2; 1386 | 1387 | if (aThing == State2) { 1388 | print_endline("Thing!") 1389 | } 1390 | 1391 | 1392 | 1393 | /*---------------------------------------------- 1394 | * JavaScript Interoperation 1395 | *---------------------------------------------- 1396 | */ 1397 | 1398 | /* Even though Reason is a language unto itself, for the time being, its primary compile 1399 | target is JavaScript. As such, a great many JavaScript-specific tools are available 1400 | to developers. This section is not intended to be exhaustive. For greater information, 1401 | see the Bucklescript docs at https://bucklescript.github.io/. */ 1402 | 1403 | // Most abilities are attached to the `Js` module. 1404 | Js.log("This will log to the console."); 1405 | Js.logMany([|"Log", "an", "array"|]); 1406 | 1407 | /* Promises are currently supported with expected behaviors. Resolve and reject are 1408 | uncurried callbacks. This is a feature of Bucklescript and requires the leading `.` in 1409 | the list of arguments. The pipe-last operator is used for chaining. */ 1410 | 1411 | let getAccountID = Js.Promise.make( (~resolve, ~reject) => resolve(. 1337)); 1412 | 1413 | getAccountID 1414 | |> Js.Promise.then_(result => { 1415 | Js.Promise.resolve(result); 1416 | }) 1417 | |> Js.Promise.catch(err => { 1418 | Js.log2("Failure!!", err); 1419 | Js.Promise.resolve(-1); 1420 | }); 1421 | 1422 | // Async/Await support is under development. 1423 | 1424 | 1425 | /*---------------------------------------------- 1426 | * Bucklescript 1427 | *---------------------------------------------- 1428 | */ 1429 | 1430 | /* A note on BuckleScript */ 1431 | 1432 | /* The ReasonML community has been thrown into a degree of chaos with the 1433 | release of a new Bucklescript-specific ReasonML syntax. Basically, if you 1434 | are targeting JavaScript with your ReasonML code, it will become more 1435 | specifically targeted to that JavaScript and less well-suited for native 1436 | work. The extent to which this will affect current projects and endeavors 1437 | is, regardless of what many are saying, unknown. 1438 | 1439 | What can be said for sure is that everything below is officially deprecated 1440 | and will slowly fade into that great git repo in the sky. 1441 | 1442 | The changes can be found here: 1443 | https://reasonml.org/blog/bucklescript-8-1-new-syntax 1444 | 1445 | Docs are here: 1446 | https://reasonml.org/docs/reason-compiler/latest/new-bucklescript-syntax 1447 | 1448 | A listing of many concerns can be found here: 1449 | https://reasonml.chat/t/bucklescript-8-1-new-syntax-option/2379 */ 1450 | 1451 | 1452 | /* As earlier stated, Bucklescript is the transpiler for turning an OCaml syntax tree into 1453 | JavaScript. When ReasonML compiles, it is turned into an OCaml syntax tree and can then 1454 | be pulled into all existing OCaml toolchains. While ReasonML contains many of its own 1455 | JavaScript abilities, accessing the broader JavaScript world requires the use of 1456 | Bucklescript-specific tools. This comes into play most commonly when writing bindings 1457 | between ReasonML and existing JavaScript that cannot be easily converted to ReasonML. 1458 | 1459 | Bindings are code that take external JavaScript and represent it in ReasonML's symbolic 1460 | system. When transpiled to JavaScript, Bucklescript will generate functions that check 1461 | the consistency of the JavaScript according to the provided bindings. This means that, 1462 | unlike in TypeScript, transpiled ReasonML code is type safe. It will perform run-time 1463 | checks, injecting stability and debuggability into the application in case of unexpected 1464 | external input, as from the response from an API. 1465 | 1466 | In the below example, the %bs.raw decorator allows direct injection of JavaScript. What 1467 | the JavaScript accepts from ReasonML and what it returns to ReasonML must still be typed, 1468 | but there are no guarantees that the JavaScript will actually accept or return what is 1469 | declared. */ 1470 | 1471 | let jsReduce: (array(int)) => int = [%bs.raw 1472 | {| 1473 | function (numbers) { 1474 | var result = 0; 1475 | numbers.forEach( (number) => { 1476 | result += number; 1477 | }); 1478 | return result; 1479 | } 1480 | |} 1481 | ]; 1482 | 1483 | let calculate = (numbers) => jsReduce(Array.of_list(numbers)); 1484 | 1485 | /*---------------------------------------------- 1486 | * Belt 1487 | *---------------------------------------------- 1488 | */ 1489 | 1490 | /* Belt is an implementation/extension of OCaml's standard library that provides additional tools 1491 | specifically to facilitate transpilation to JavaScript. From the perpsective of JavaScript 1492 | developers, the best analogy is Lodash. Unlike Lodash, Belt comes along as a native part of 1493 | Bucklescript. As of February 2020, Belt is officially in beta, with breaking changes 1494 | periodically occuring. That said, it is mostly stable and widely used by developers in the 1495 | community. 1496 | 1497 | Belt possesses much of the same functionality as the Js module. It is recommended to use Belt 1498 | instead of Js since it prevents application logic from being tied to JavaScript. 1499 | 1500 | There are two parts to the Belt library: the primary module and the "flattened" modules. The 1501 | primary module has sub-modules, each of which contain functions for manipulating data. The 1502 | flattened modules are prefixed with Belt_, such as Belt_Map, and are the legacy tools. The 1503 | primary module is intended to ultimately provide all of the same functionality of the flattened 1504 | modules, but it is a work in progress. Currently, the flattened modules provide some 1505 | additional functionality, such as tools for manipulating AVL trees. 1506 | 1507 | When given the choice, do not use the flattened modules unless a particular piece of functionality 1508 | is utterly necessary. The flattened modules will disappear at some point in a future version of 1509 | Bucklescript. */ 1510 | 1511 | let testArray = [|"1", "2", "3"|]; 1512 | 1513 | Belt.Array.forEach(testArray, (element) => { 1514 | Js.log(element); 1515 | }); 1516 | 1517 | // The flattened module does the same thing. It is simply deprecated syntax. 1518 | 1519 | Belt_Array.forEach(testArray, (element) => { 1520 | Js.log(element); 1521 | }); 1522 | 1523 | 1524 | /*---------------------------------------------- 1525 | * ReasonReact 1526 | *---------------------------------------------- 1527 | */ 1528 | 1529 | /* ReasonML was created by the same person, Jordan Walke, that created React. It could be seen 1530 | as a language created specifically to enable faster, more reliable production of React apps. 1531 | React and ReasonML have walked hand-in-hand because of this, meaning that much of the 1532 | discussion surrounding the language has focused on React. ReasonReact's home page is even 1533 | hosted on the same domain as ReasonML. As such, it is sensible to include an overview of the 1534 | React library's bindings. */ 1535 | 1536 | [@react.component] 1537 | let make = (~food) => { 1538 | let (amount, eat) = React.useState( () => 0 ); 1539 | let eatMore = () => { eat( (_) => { amount + 1 } ) }; 1540 |
1541 |

{ React.string( {j| $food monster has eaten $amount $(food)s |j} ) }

1542 | 1543 |
1544 | }; 1545 | 1546 | /* The above abstracts away much of React's boilerplate. All that must be written are the 1547 | decorator and the render function, which is called `make`. The example is a variation 1548 | on the standard React Hooks example available on React's website, and the ReasonML version 1549 | available on the ReasonReact site. 1550 | 1551 | The labeled arguments constitute the component's props. The next line is the state Hook. 1552 | `useState` returns a 2-tuple, with the first element being the value to store and the second 1553 | being the function that can change that value. With destructuring, the names `amount` 1554 | and `eat` are bound to the values. `useState` takes a single argument, a function that 1555 | initializes the state value. In the above case, an inital value of `int : 0` is bound to 1556 | `amount`. 1557 | 1558 | The returned function, `eat`, is a wrapper that adds a signature to, and then returns, a 1559 | provided function. In this example, the anonymous function passed into `eat` is signed as 1560 | accepting an integer and returning an integer. Any function passed into `eat` must follow the 1561 | same signature. 1562 | 1563 | `eatMore` is a wrapper function that calls `eat`. `eat` cannot be directly called because 1564 | the function must accept a React Event, while functions that can change the state will only 1565 | accept the same type that they return, in this case an integer. `eatMore` is a single-use 1566 | function with no special signature. The function passed into `eat` must accept an integer, 1567 | but there is no integer to provide. An anonymous variable is provided since it fulfills any 1568 | type requirement. 1569 | 1570 | The final piece of JSX is the implicitly returned value. All strings must be placed in the 1571 | typed wrapper React.string(). The `onClick` event automatically captures the React event and 1572 | passes it into the provided function. In this example, an underscore is used to create a 1573 | casual variable. Since the event is not being used, a warning would otherwise be displayed. 1574 | 1575 | The above steps and nested functions may seem overly complex, but it is the nature of 1576 | functional programming that steps are not broken down by passing arguments or mutating values. 1577 | Instead, steps are taken with the progressive application of signed and typed functions. 1578 | 1579 | ReasonReact is built around Hooks and supports all of them. The older API is deprecated. 1580 | */ 1581 | 1582 | 1583 | /*---------------------------------------------- 1584 | * Monads 1585 | *---------------------------------------------- 1586 | */ 1587 | 1588 | /* ReasonML is a functional language, meaning that processes are based on the evaluations of 1589 | mathematical functions. This means that for the same inputs, the program will always return 1590 | the same output. This results in the problem of how a program is to have state or handle effects. 1591 | Common programs have a great many effects, such as logs, timers, I/O, etc. Pure functional 1592 | languages like Haskell solve these problems through the use of monads. 1593 | 1594 | ReasonML, and OCaml before it, made practical concessions to the needs of developers by allowing, 1595 | and having language structures for, effects and state. It did not forget the lessons of monads, 1596 | though, and has a few on the language level. Most notable, and most striking to developers coming 1597 | from more procedural languages, is Option. 1598 | 1599 | A monad is simply a structure that follows certain rules. Monads are not peculiar to functional 1600 | languages. They simply arose in usage in that context because they were necessary for solving 1601 | certain problems. Their utility has become apparent, with other languages adopting common monads 1602 | like Option. Scala, Kotlin, C#, and Java all now have optional values as part of the language. 1603 | Indeed, monads generally are becoming common. JavaScript has perhaps the most widely used monad in 1604 | computer history in the form of the Promise. 1605 | 1606 | The purpose of this section therefore is to bring focus to their genesis and hopefully explain 1607 | why knowledge of monads, and category theory more broadly, is an extremely fruitful course of 1608 | investigation. */ 1609 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ReasonML Logo](reason_logo.png) 2 | 3 | ## 2023 Update 4 | 5 | Sadly, the fracture of ReasonML and ReScript has basically killed the language and ecosystem. There is still a fair amount of geeky interest as demonstrated in the official Discord channel, but this has translated to little industry uptake, nor will it experience any. PureScript is even geekier, Elm has better adoption, and TypeScript is good enough for most use cases while offering incalculably better support and tooling. But the real nail in the coffin, though, is the rise of Rust. 6 | 7 | Rust was heavily inspired by the ML line of languages, and anyone skilled in OCaml or ReasonML will likely find it familiar, thus obviating the syntactic and semantic benefits of ReasonML. Further, Rust offers the original promise of ReasonML, to wit the ability to write code that can target either the browser or native code. This was ReasonML's real party piece, but once ReScript abandoned this pipeline to focus entirely on targeting JavaScript, its one advantage became simply being a better language. As anyone will tell you, better languages rarely win the language war. It is simply not enough of a benefit to make people abandon more popular languages. 8 | 9 | Rust brings everything. Its native performance is on par with C and C++, its syntax is approachable for those coming from JavaScript and TypeScript, and its semantics are vastly superior. JavaScript tools are increasingly switching to Rust for the massive performance increase it brings. And it has become the language of choice for WebAssembly development. 10 | 11 | That said, it is not yet a perfect replacement. ReasonML and ReScript easily transpile directly to JavaScript. Indeed, that is their purpose. The semantic overlap of OCaml and JavaScript is easier to reconcile than Rust and JavaScript. Because of this, there have been no entirely successful attempts at creating a Rust->JavaScript transpiler. 12 | 13 | But if one sets their horizon further out, it becomes apparent that WebAssembly is either the outright future of web development, or a major part of it. Combined with its growing server-side ecosystem, if one is going to expand outside of the usual suspects of front-end engineering, I think that Rust is the only choice. Indeed, I think that Rust is such an excellent language with such glorious prospects that anyone working in n-tier, client-server development _must_ learn Rust. I have in fact moved my non-TypeScript learning and work to Rust for basically everything and, since I have abandonded this repo along with ReasonML generally, have created a new [Quick Guide for Rust](https://github.com/amartincolby/Rust-Quick-Guide). 14 | 15 | The tldr of this is that I no longer think ReasonML or ReScript is worth learning. Pure OCaml is certainly worth learning and will likely stay that way for a long time, but its geeky side projects are of little value. If you are a web developer, I instead recommend enhancing TypeScript skills while growing into Rust. The future is good. 16 | 17 | # ReasonML Quick Guide 18 | 19 | ReasonML is a fantastic language. It is, in my eyes, a refinment of OCaml that eliminates many of the syntactic peculiarities of that language that are the result, I assume, of being French. 20 | 21 | Sadly, the online documentation for both OCaml and Reason is poor. The community is passionate but small. I started this document as part of a contribution to [Learn X in Y](https://learnxinyminutes.com/), but as I dug deeper into the language and discovered bits that I didn't fully understand, and for which I could not find sufficient elucidation, my work stalled. I did not want to contribute half-finished work, but at the same time, wanted my work to be public in the hope of furthering the ReasonML gospel and helping others. Basically, wisdom is useless if it can't be scraped by Google. 22 | 23 | My motivation is also partly that of self edification. As Richard Feynman discussed, the best way to learn is to try to teach. By writing this, I become better at the language. And since I subscribe, in as un-self-important a way as possible, to the idea of "Junior Developer For Life," this repo will forever be a work in progress as I better learn the language. Even now, there are some parts that are incomplete. 24 | 25 | As such, I present to you my guide to ReasonML. My general goal is to be as terse as possible, where I clearly communicate what is happening but avoid any unnecessary blather. Below is the Markdown version of the main Reason file at the repo's root. I would be happy to provide further information and also accept criticism and pull requests to this repo. The more content, and the deeper the content, on the web, the better. Let's bring ReasonML to the masses! 26 | 27 | # Shout-outs 28 | 29 | This guide started as an extension of a more lightweight guide written by ["Seth Corker"](https://sethcorker.com). I was working on my own version at the time and integrated his initial work. 30 | 31 | I also want to bring note to the small but dedicated group over at the [ReasonML forums](https://reasonml.chat/). Go there. Just say HI for Pete's sake. Engage! Life springs from activity, and we need more activity, because the technology itself is solid and ready for production use. 32 | 33 | # Further Information 34 | - [ReasonML Community Docs](https://reasonml.org/) *An attempt to unify all documentation in a single, manageable place.* 35 | - [ReasonML Twitter](https://twitter.com/reasonml) *Best stream for constant updates* 36 | - [ReasonConf Twitter](https://twitter.com/reasonconf) 37 | - [Reason Package Index, aka Redex](https://redex.github.io/) 38 | - [Official Reason Docs](https://reasonml.github.io/docs/en/what-and-why) 39 | - [ReScript docs](https://rescript-lang.org) 40 | - [Exploring ReasonML](http://reasonmlhub.com/exploring-reasonml/toc.html) *Perhaps the best docs currently available* 41 | - [Try Reason](https://reasonml.github.io/en/try) 42 | - [Real World OCaml](http://dev.realworldocaml.org/toc.html) 43 | - [Revery](https://www.outrunlabs.com/revery/) *Akin to Electron, but produces true native binaries* 44 | 45 | # The Guide 46 | 47 | ReasonML is a syntax layer over OCaml. Under the covers, ReasonML *is* OCaml. It is broadly similar to OCaml but makes changes that bring its syntax better in alignment with C and thus JavaScript. During compilation, Reason is translated into an OCaml abstract syntax tree. The ReScript compiler is then used to turn that AST into optimized and easy-to-read JavaScript. 48 | 49 | Reason's *raison d'être* is to give JavaScript developers a language that is familiar but cleaner, does not require explicit type declarations, and provides algebraically guaranteed type safety.Further, as a syntax layer, code written in Reason can be readily compiled to OCaml and run as a native executable, paving the way for easy Web Assembly and cross-platform development. 50 | 51 | Unfortunately, a cultural fracture has lately occured in which the community has been split between those who want ReasonML to remain primarily its own language and those who want ReasonML to exist primarily as a replacement for JavaScript. The conflict was such that the lead developer of Bucklescript actually left Twitter. I mention this to help alleviate any confusion for those who may be finding coflicting information in the scant documentation online. The result is that Bucklescript, which was the tool that turned ReasonML/OCaml into JavaScript, is now ReScript, which is something of its own language. To say that it is _inspired_ by ReasonML is not inaccurate, although the bulk of the differences are mostly syntactic. The details of how to use ReScript will be discussed later in the tutorial. For my part, I am disappointed since a major attraction for me was a single language for multiple places, but I also appreciate their desire to more directly target the JavaScript world. 52 | 53 | While idiomatic ReasonML is written in a functional manner, I want to stress that understanding functional patterns is not necessary. Simply writing most existing JavaScript patterns in ReasonML will provide significant benefits. 54 | 55 | The below text is valid ReasonML code. It is a copy of the code in the src directory. 56 | 57 | ``` reason 58 | /* Comment blocks start with slash-star, 59 | and end with star-slash. */ 60 | // Line comments begin with a double slash. 61 | // Expressions need not be terminated with a semicolon, but it is best practice; 62 | 63 | /*** A note on types ***/ 64 | 65 | /* ReasonML makes little distinction between primitive types and what could be 66 | called user-defined types or structs. They are simply "things" represented 67 | by symbols. 68 | 69 | The below type is an abstract type, meaning that the symbol has no structure 70 | attached to it. Any program using this type does not need to know its 71 | structure just so long as usage of the type is consistent. Basically, 72 | programs can use symbols without knowing what they mean. */ 73 | 74 | type kwyjibo; 75 | 76 | /* The below type is a concrete type with a defined structure. These will appear 77 | frequently in this guide since any record must have an associated concrete 78 | type. */ 79 | 80 | type thing = { 81 | value1 : string, 82 | value2 : int, 83 | }; 84 | 85 | 86 | /*---------------------------------------------- 87 | * Variables, functions, and bindings 88 | *---------------------------------------------- 89 | */ 90 | 91 | /* In ReasonML, the word "variable" is used as convention but is inaccurate. 92 | Variables do not vary, i.e. they are immutable. As with many functional 93 | languages, variables are more accurately referred to as bindings, since what 94 | is happening is that a value is being irrevocably bound to a symbol, not 95 | merely assigned. 96 | 97 | Symbols and values, whether they be functions, primitives, or more complex 98 | structures, are usually bound with the `let` keyword. Names must begin with 99 | a lowercase letter or underscore. 100 | 101 | All values must have a static type, but these types often do not need to be 102 | declared in use. In most cases, they can be inferred by the compiler via 103 | Hindley-Milner classification. */ 104 | 105 | let x = 5; // : int 106 | let y = 42.0; // : float 107 | let z = "Dinner for one"; // : string 108 | 109 | // Functions will likewise infer parameter and return types. 110 | let addInts = (a, b) => { a + b }; // inferred int for all values via `+` operator. 111 | 112 | // Types can be functions 113 | type intFunction = (int, int) => int; 114 | let intAdder : intFunction = (x, y) => { x + y }; 115 | 116 | // Let bindings are block scoped with braces `{}`. 117 | if (true) { 118 | let val1 = "best of times"; 119 | print_endline(val1) 120 | }; 121 | 122 | let myBlock = { 123 | let val1 = "worst of times"; 124 | print_endline(val1) 125 | }; 126 | 127 | /* The list of reserved words is broadly similar to OCaml, the list for which is 128 | available here: http://caml.inria.fr/pub/docs/manual-ocaml-312/manual044.html. 129 | The Reason-specific list is under development, with the current words 130 | available in the ReasonML source code, on Github. 131 | 132 | https://github.com/facebook/reason/blob/master/src/reason-parser/reason_declarative_lexer.mll#L85-L144 133 | 134 | Prefixing a variable name with an underscore creates a casual variable. 135 | These variables, if unused, will not trigger a compiler warning. 136 | Unused variable warnings only apply to block scopes and will not be 137 | raised on variables declared at the global or module level. 138 | 139 | There are two kinds of unused variable. A suspicious unused variable is one 140 | that has been bound with `let` or `as`. An innocuous variable is one that 141 | has not been been bound with one of the primary binding verbs. */ 142 | 143 | let blockScope = () => { 144 | let variable = 42; // This triggers a warning. 145 | let _casualVariable = 2001; // This does not trigger a warning. 146 | "Return string" 147 | } 148 | 149 | /* `let` bindings are also lexically scoped. If a `let` binding is declared again, 150 | from that point forward the symbol will reference the later value. */ 151 | 152 | let lexicalValue = "42"; 153 | print_endline(lexicalValue); // "42" 154 | let lexicalValue = "To be or not to be."; 155 | print_endline(lexicalValue); // "To be or not to be." 156 | 157 | 158 | /*** Destructuring ***/ 159 | 160 | // Data structures can be "destructured" for assignment to individual variables. 161 | let aTuple = ("Teacher", 101); 162 | let (name, classNumber) = aTuple; 163 | 164 | print_endline(name); 165 | print_int(classNumber); 166 | 167 | type person = { 168 | firstName : string, 169 | age : int, 170 | }; 171 | 172 | // Variable extractions from records must match field names. 173 | let bjorn = {firstName : "Bjorn", age : 28}; 174 | let {firstName, age} = bjorn; 175 | 176 | // Extractions can be re-named before being bound. 177 | let {firstName : bName, age : bAge} = bjorn; 178 | 179 | // Fields can be ignored either through omission or the anonymous variable `_`. 180 | let {firstName : cName} = bjorn; 181 | let {firstName : dName, age : _} = bjorn; 182 | 183 | 184 | /*** Type annotation ***/ 185 | 186 | // Type annotations can be added when inference does not suffice. 187 | let a : int = 5; 188 | 189 | // Function parameters and returns can be annotated. 190 | let add2 = (a : int, b : int): int => a + b; 191 | 192 | // A type can be aliased using the `type` keyword. 193 | type companyId = int; 194 | let myId: companyId = 101; 195 | 196 | // Mutation, while discouraged, is possible with a `ref()` wrapper. 197 | let myMutableNumber = ref(120); 198 | 199 | // To assign a new value, use the `:=` operator. 200 | myMutableNumber := 240; 201 | 202 | // To access the value in a `ref()`, use the `^` suffix. 203 | let copyOfMyMutableNumber = myMutableNumber^; 204 | 205 | 206 | /*** as ***/ 207 | 208 | /* The second way to perform a symbol/value binding is with `as`. Where a `let` 209 | binding binds a right-hand value to a left-hand symbol, `as` binds left to 210 | right. This syntax exists because of the common practice of "piping" 211 | something from one segment of code into the following segment. This stands 212 | in contrast to the procedural practice of variable assignment and then, 213 | later, variable use. 214 | 215 | Unlike `let`, which must be declared procedurally just like variable declarations 216 | in most other common languages, the `as` binding can only be declared functionally, 217 | since it must be passed through to a succeeding block. 218 | 219 | An important difference between `let` binding and `as` is when patterns are evaluated. 220 | 221 | This is an esoteric concept, so do not be dissuaded if it is at first strange and 222 | incomprehensible. At its root, `as` is an excellent example of the pattern matching 223 | at the root of modern functional languages. */ 224 | 225 | // Since `as` is a binding, it has paramount precedence. 226 | 227 | // https://stackoverflow.com/questions/49840954/keyword-as-in-sml-nj?noredirect=1&lq=1 228 | // https://stackoverflow.com/questions/26769403/as-keyword-in-ocaml 229 | // https://reasonml.chat/t/an-explanation-of-as/1912 230 | 231 | 232 | /* `as` is also used in object assignment in a somewhat similar fashion. See the below 233 | section on objects to see an explanation. */ 234 | 235 | 236 | /*---------------------------------------------- 237 | * Basic operators 238 | *---------------------------------------------- 239 | */ 240 | 241 | /*** > Boolean ***/ 242 | 243 | // A boolean can be either true or false. 244 | let isLearning = true; 245 | 246 | true && false; // - : bool = false; Logical and 247 | true || false; // - : bool = true; Logical or 248 | !true; // - : bool = false; Logical not 249 | 250 | 'a' > 'b'; // - bool : false 251 | 5 < 42; // - bool : true 252 | 253 | 254 | /* Equality */ 255 | 256 | /* Although Reason has `==` and `===`, they are different from JavaScript. 257 | Structural Equality, using the double-glyph operator, does a deep 258 | comparison of each entity's structure. Use this judiciously since 259 | comparing large structures is expensive. */ 260 | 261 | type author = { 262 | name : string, 263 | age : int 264 | }; 265 | 266 | let author1 = { 267 | name : "Charles Dickens", 268 | age : 58 269 | }; 270 | 271 | let author2 = { 272 | name : "Charles Dickens", 273 | age : 58 274 | }; 275 | 276 | let author3 = { 277 | name : "Victor Hugo", 278 | age : 83 279 | }; 280 | 281 | author1 == author2; // - : bool = true 282 | author1 == author3; // - : bool = false 283 | author1 != author3; // - : bool = true 284 | 285 | /* Any attempt at using greater-than or less-than vis-à-vis structures will 286 | trigger a structural comparison. The comparison will return a boolean as 287 | soon as a difference is discovered and will not continue to do a complete 288 | comparison. */ 289 | 290 | let bigObj = [10, 10000000, 10000000]; 291 | let smallObj = [11, 1, 1]; 292 | 293 | bigObj > smallObj; // - : bool = false 294 | 295 | /* Because 10 and 11 are different, and 10 is smaller than 11, false is returned 296 | even though the next two values are much larger. */ 297 | 298 | /* Referential, or physical, equality, using the triple-glyph operator, compares 299 | the identity of each entity. */ 300 | 301 | author1 === author2; // - : bool = false 302 | author1 === author1; // - : bool = true 303 | 304 | 305 | /* Comparing Values */ 306 | 307 | // The equality operators work differently for values instead of structures. 308 | // Both `==` and `===` will become strict equality `===` when compiled to JavaScript. 309 | // Attempting to compare two different types will cause a compile error. 310 | 311 | let myString1 = "A world without string is chaos."; 312 | let myString2 = "A world without string is chaos."; 313 | 314 | "A string" === "A string"; // - : bool = true 315 | "A string" == "A string"; // - : bool = true 316 | 42 === 42; // - : bool = true 317 | 42 == 42; // - : bool = true 318 | // 42 === "A string" // Error 319 | 320 | 321 | /*** Operator Assignment ***/ 322 | 323 | /* Reason is a descendant of Lisp, which means that operations in the language are 324 | conceptually similar to lists of operations and data. As such, while standard 325 | infix operator notation is acceptable, so is prefix, or Polish, notation. */ 326 | 327 | let polishAdder = (a, b) => (+) (a, b); 328 | 329 | // Operators are just functions that can be reassigned. 330 | let (+) = (a, b) => a * b; 331 | 332 | // Custom operators can use any of the reserved characters. 333 | let ($) = (a, b) => a - b + 3; 334 | 4 $ 3 // - : int = 4 335 | 336 | 337 | /*---------------------------------------------- 338 | * Basic types 339 | *---------------------------------------------- 340 | */ 341 | 342 | /*** A note on the standard library (StdLib) ***/ 343 | 344 | /* All of Reason's types and structures have modules in the standard library. In general, 345 | it is best to interface with your data via these modules. The abstraction helps 346 | enable cross-compatibility with native and JavaScript while also providing the usual 347 | security and guarantees that come with using a standard library. 348 | 349 | While a complete discussion of the StdLib is outside the scope of this tutorial, 350 | overviews will be given when pertinent. 351 | 352 | The list of modules can be found here: https://reasonml.github.io/api/index_modules.html */ 353 | 354 | 355 | /*** > String ***/ 356 | 357 | /* Strings in Reason map over to strings in JavaScript well. Strings in JavaScript are 358 | actually immutable under the covers, with the act of changing a string actually creating 359 | an entirely new string and associating it with a variable name. 360 | 361 | In OCaml, strings can be directly changed, although this unsafe behavior is highly 362 | discouraged. Further, since JavaScript cannot do this, attempting to do so runs the 363 | risk of cross-compatibility problems. */ 364 | 365 | // Use double quotes for strings. 366 | let greeting = "Hello world!"; 367 | 368 | // Strings can span multiple lines. When compiled, new line characters are generated. 369 | let aLongerGreeting = "Look at me, 370 | I'm a 371 | multi-line string"; 372 | 373 | // The `{||}` syntax provides functionality akin to backtick strings in JavaScript. 374 | let quotedString = {|It was the best of times, it was the worst of times.|}; 375 | 376 | let multiLineQuotedString = {|To love another person 377 | is to see 378 | the face of God.|} 379 | 380 | // No character escaping is necessary. 381 | let specialCharacters = {|This is fine. !@#'"`$%^&*()|} 382 | 383 | // The `js` marker enables unicode. 384 | let world = {js|🌍|js}; 385 | 386 | // The `j` marker enables variable interpolation. 387 | // The parenthesis are optional but required if characters directly follow the variable 388 | let helloWorld = {j|hello, $(world)|j}; 389 | 390 | // The `$` is reserved to prefix variables and can be escaped with `\`. 391 | let accountBalance = {j|You have \$500.00|j}; 392 | 393 | // Concatenate strings with `++`. 394 | let name = "John " ++ "Wayne"; 395 | let emailSubject = "Hi " ++ name ++ ", you're a valued customer"; 396 | 397 | 398 | /*** > Char ***/ 399 | 400 | /* Use single quotes for a char. Chars compile to an integer between 0 and 255 401 | and thus do not support Unicode or UTF-8. */ 402 | 403 | let lastLetter = 'z'; 404 | 405 | /*** > Integer ***/ 406 | 407 | 1 + 1; // int = 2 408 | 25 - 11; // int = 11 409 | 5 * 2 * 3; // int = 30 410 | 8 / 2; // int = 4 411 | 412 | // Integer division will round results 413 | 8 / 3; // int = 2 414 | 8 / 5; // int = 1 415 | 416 | /* The StdLib provides modules for int32 and int64 operations. Unless these exact 417 | precisions are necessary, the standard int type provides the best 418 | cross-compatibility and performance. */ 419 | 420 | 421 | /*** > Float ***/ 422 | 423 | 1.1 +. 1.5; // float = 2.6 424 | 18.0 -. 24.5; // float = -6.5 425 | 2.5 *. 2.0; // float = 5.0 426 | 16.0 /. 4.0; // float = 4.0 427 | 428 | // Floats and integers can be formatted with underscores 429 | let formattedInt : int = 12_34_56; // int = 123456 430 | let formattedFloat : float = 12_34_56.0; // float = 123456.0 431 | 432 | 433 | /*** > Tuple ***/ 434 | 435 | /* Tuples are a common data structure found in many languages. For JavaScript developers, 436 | the argument list passed into a function is the closest analog to a true tuple. 437 | 438 | Tuples are named from the suffix for counting groups, such as double, quintuple, 439 | or dectuple. For programming purposes, an actual number is used instead of a 440 | latin-based prefix. So instead of triple, 3-tuple is used. 441 | 442 | Tuples can contain two or more values, are immutable and of fixed size, ordered, and 443 | importantly are heterogenous. It is the last point that most distinguishes tuples from 444 | arrays or lists. */ 445 | 446 | let teamMember = ("John", 25); 447 | 448 | // Values can be explicitly typed. 449 | let position2d : (float, float) = (9.0, 12.0); 450 | type namedPoint = (string, float, float); 451 | let city1 = ("London", 51.507222, -0.1275); 452 | 453 | // An individual value in a tuple cannot be used independently. 454 | // To extract a value from a 2-tuple, use the standard library functions `fst()` and `snd()`. 455 | let memberName = fst(teamMember) /* - : string = "John" */ 456 | let memberAge = snd(teamMember) /* - : int = 25 */ 457 | 458 | // Values can also be ignored or extracted via destructuring and the anonymous variable `_`. 459 | let threeTuple = (42, 2001, "A113"); 460 | let (theAnswer, _, _) = threeTuple; // theAnswer = 42 461 | 462 | 463 | /*** > Record ***/ 464 | 465 | /* Records play part of the role that object literals play in JavaScript and appear most like 466 | them syntactically. They are collections of key:value pairs that are by default, but not 467 | necessarily, immutable. The shape of a record -- to wit its key names and value types, but 468 | not the order in which they appear -- is fixed. This shape must be declared with a type. */ 469 | 470 | // Like variable names, a type must begin with an underscore or lowercase letter. 471 | type trainJourney = { 472 | destination : string, 473 | capacity : int, 474 | averageSpeed : float, 475 | }; 476 | 477 | // Once declared, types can be inferred by the compiler. 478 | // The below is inferred as type trainJourney 479 | let firstTrainJourney = { 480 | destination : "London", 481 | capacity : 45, 482 | averageSpeed : 120.0, 483 | }; 484 | 485 | // Use dot notation to access properties. 486 | let maxPassengers = firstTrainJourney.capacity; 487 | 488 | /* If used immutably, records are not directly changed. Instead, copies of previous records 489 | are created with new values assigned to particular keys. Creating a new record from a 490 | previous record uses the spread operator. */ 491 | 492 | let secondTrainJourney = {...firstTrainJourney, averageSpeed: 105.0}; 493 | 494 | /* Using records mutably is not generally recommended. That said, it is a powerful tool in 495 | certain situations. A record cannot be made mutable in its entirety. Individual keys can 496 | be made mutable with the `mutable` keyword. */ 497 | 498 | type breakfastCereal = { 499 | name : string, 500 | mutable amount : int, 501 | }; 502 | 503 | let frostedFlakes = { 504 | name : "Kellog's Frosted Flakes", 505 | amount : 500, 506 | }; 507 | 508 | frostedFlakes.amount = 200; 509 | 510 | /* Match a variable's name & type to a record's key:value to allow "punning", or assigning 511 | via inferred structure. */ 512 | let name = "General Mills Chex"; 513 | let amount = 250; 514 | let chex = { // Note how the type did not need to be explicitly declared 515 | name, 516 | amount, 517 | }; 518 | 519 | 520 | /*** Object ***/ 521 | 522 | /* Objects are similar to records but are more class-like in their semantics. 523 | Notably, they are more like the original conception of objects in that they 524 | are defined BY THEIR BEHAVIORS, to wit they only expose methods; all values 525 | are private. 526 | 527 | Like classes, objects have private and public methods, identified with the 528 | `pub` and `pri` keywords. All values are labeled with the `val` keyword. 529 | Objects have `this` implicitly attached as a binding to all instances of objects. 530 | 531 | While objects in Reason are superficially similar to JavaScript objects, they 532 | do not directly transpile to them. */ 533 | 534 | // Objects are accessed with hash notation `#`. 535 | let newObject = { 536 | val aNumber = ref(42); 537 | pri increment = (num: int) => aNumber := aNumber^ + num; 538 | pub addNumber = (inputValue: int) => { 539 | this#increment(inputValue); 540 | aNumber^ 541 | }; 542 | }; 543 | 544 | /* Objects do not need types but can have them. There are two categories of type: 545 | open and closed. They are identified with either one or two dots as the leading 546 | token of the type's structure. The single dot in the type indicates that this 547 | is a closed type, meaning that all instances of this type must have a public 548 | interface of exactly this shape. */ 549 | 550 | type truck = { 551 | . 552 | buildTruck : (~make : string, ~model : string) => string 553 | }; 554 | 555 | let newTruck : truck = { 556 | val drive = 4; 557 | pub buildTruck = (~make, ~model) => {j|You built a new $make $model with $drive-wheel drive.|j}; 558 | 559 | // This method is private and will not violate the public shape. 560 | pri buildEngine = (~cylinders) => {j|You installed a $(cylinders)-cylinder engine.|j} 561 | 562 | // This public method would throw an error because it violates the object shape. 563 | // pub intruder = "I'm not supposed to be here!"; 564 | }; 565 | 566 | let toyotaTundra = newTruck#buildTruck(~make="Toyota", ~model="Tundra"); 567 | 568 | /* The double dot, called an elision, indicates that the below is an open type. 569 | This means that instances of this type do not need to have this exact shape. 570 | This flexibility means that object types, unlike records, are NOT inferred. 571 | They must be explicitly declared. */ 572 | 573 | type car('a) = { 574 | .. 575 | accelerate: unit => unit, 576 | brake: unit => unit 577 | } as 'a; 578 | 579 | /* Since open types do not mandate a shape, they are necessarily polymorphic, 580 | meaning that a closed type must be provided when this object is instantiated. 581 | The object that is created is classified as the supplied type with the `as` keyword. 582 | 583 | The undetermined type is represented by the `'a`, commonly referred to as 584 | "alpha." Alpha's sibling is `'b`, known as "beta." Alpha commonly represents 585 | a function's input type while beta represents its return type. This is only 586 | convention, as the ' simply identifies a type label and the succeeding string 587 | can be anything. 'steve is equally valid to 'a. 588 | 589 | Below, since `car` is an open type, a closed type is supplied in-line. This in-line 590 | type is anonymous and only available to the toyotaSupra that has been created. A 591 | named type could be created and passed in as well. */ 592 | 593 | let toyotaSupra: car({. accelerate: unit => unit, brake: unit => unit, checkSpeed: int}) = { 594 | // as _this; 595 | val speed = ref(0); 596 | pub accelerate = () => { 597 | if (speed^ < 155) {speed := speed^ + 1}; 598 | }; 599 | pub brake = () => { 600 | if (speed^ > 0) {speed := speed^ - 1}; 601 | }; 602 | pub checkSpeed = speed^; 603 | }; 604 | 605 | /* The above example is illustrative of the earlier point about `as` vis-a-vis objects. 606 | A warning underlines the speed val because objects implicitly contain `this`. 607 | `this` represents the object itself and not simply its behaviors or values. The 608 | warning will go away either through the use of a private method requiring `this`, 609 | such as `this#privateMethod`, or through assigning `this` as a casual variable. 610 | The latter can be achieved with the `as` keyword. 611 | 612 | Uncomment the `as _this;` after toyotaSupra's opening bracket to clear the warning. This 613 | syntax captures the `this` that is implicitly being passed into the object 614 | construction and reassigns it as the casual `_this`. The usage of `_this` is purely 615 | for illustrative purposes. The captured `this` can be assigned any casual value to 616 | prevent the warning. `_t` or `_Steve` are equally valid. 617 | 618 | As of this writing, April 4th, 2020, IDEs seem to have problems with correctly 619 | underlining object warnings of this sort and will incorrectly underline parts of code. 620 | Ignore them. The ultimate arbiter of correct code is the Bucklescript compiler and its 621 | console output. 622 | 623 | *** A note on Objects *** 624 | 625 | The reader perhaps has discerned conflict inherent to objects as described above and 626 | has forseen a project's future where massive numbers of objects, both open and closed, 627 | have been created to better match patterns found in languages like Java or C#. These 628 | objects would rapidly become brittle, fragile, and difficult to extend, even with type 629 | safety. A full dissection of these concerns is beyond the scope of this tutorial, but 630 | suffice it to say that while these abilities are in the language, using them to mimic 631 | OOP patterns from other languages is widely seen as bad practice. They have their place, 632 | but only within the context of broader functional concepts. */ 633 | 634 | 635 | /*** > Variant ***/ 636 | 637 | /* A variant is a type declaration with multiple states. The possible states are called 638 | constructors because they are not types themselves; they are symbolic identifiers. Further, 639 | since they are symbols, they should be unique within the current scope, just as any other 640 | declaration. */ 641 | 642 | type authType = 643 | | GitHub 644 | | Facebook 645 | | Google 646 | | Password; 647 | 648 | let userPreferredAuth = GitHub; 649 | 650 | /* The compiler now knows that `userPreferredAuth` is of type `authType`. and 651 | that it must necessarily be in one of four states. */ 652 | 653 | /* Just like any other declaration, how variant constructors are inferred by the type system 654 | can be changed as the lexical scope progresses. Below, the `authTypeGit` also contains a 655 | constructor called `GitHub`. As such, any later uses of the `GitHub` constructor will cause 656 | the compiler to infer a type of `authTypeGit`. For `gitUser1`, this inference is prevented 657 | through explicit type declaration. 658 | 659 | This situation highlights an interesting aspect of ReasonML's type system: requiring explicit 660 | type declarations becomes a code smell. */ 661 | 662 | type authTypeGit = 663 | | GitHub 664 | | Gitlab 665 | | Gitter; 666 | 667 | let gitUser1 : authType = GitHub; 668 | let gitUser2 = GitHub; 669 | 670 | /* Constructors "construct" because they accept types as parameters. These types can 671 | themselves be variants. */ 672 | 673 | type userRegion = 674 | | Asia 675 | | Europe 676 | | South_america 677 | | North_america; 678 | 679 | type userType = 680 | | Admin(authType) 681 | | Guest 682 | | Moderator(authType, userRegion); 683 | 684 | let newUser = Moderator(GitHub, Europe); 685 | 686 | 687 | /*** > Option ***/ 688 | 689 | /* At the root of Reason's safety is the concept of possible existence. This is 690 | not like `null` or `undefined` but an explicit state of nothing. Possible 691 | existence is handled with the `Option` type. Options are like a variant with 692 | two states, Some() and None. 693 | 694 | Those coming from other functional languages will possibly recognize Some() 695 | as a monad. For the purposes of this section, a monad is a wrapper of known 696 | structure that can contain other data. The benefits of monads are as a 697 | high-level abstraction of computation and structure. They will be further 698 | discussed in a later section. */ 699 | 700 | Random.self_init(); 701 | let isThingHere = Random.bool(); 702 | 703 | // Work in progress. 704 | 705 | // sayToThing("Thank you, Thing."); 706 | 707 | /* isThingHere is of type `option` and either returns a Some() that contains a 708 | string or None which contains nothing. The compiler will require code that references 709 | `isThingHere` to account for both states. */ 710 | 711 | 712 | /*** > List ***/ 713 | 714 | /* A singly-linked list is a language-level structure in ReasonML. They are standard 715 | singly-linked lists in that they are identified by their heads, they are ordered, their 716 | consituents are immutable and homogenous, prepending and splitting are constant-time 717 | operations, while access & updates are linear time operations. */ 718 | 719 | // A list is declared with `[ ]` with comma-separated values. 720 | let userIds = [1, 4, 8]; 721 | 722 | // The type can be explicitly set with list('a) where 'a is the type 723 | type idList = list(int); 724 | type attendanceList = list(string); 725 | 726 | /* Just like records, lists are immutable but the contents of a list can be copied using 727 | the spread operator. The spread does not work exactly like JavaScript in that this is 728 | syntactic sugar for OCaml's `::` operator. As such, you cannot spread a list for 729 | appending, only prepending. */ 730 | 731 | let newUserIds1 = [101, 102, ...userIds]; 732 | // let newUserIds1 = [...userIds, 101, 102]; // Invalid use 733 | 734 | 735 | /*** > Array ***/ 736 | 737 | /* Arrays are similar to arrays in other languages. Because ReasonML straddles two worlds, 738 | native and JavaScript, the characteristics of arrays differ slightly depending on which 739 | world is being targeted. When transpiling to JavaScript, arrays are variable size and 740 | can grow at runtime. When targeting native applications, arrays are fixed size. This 741 | behavior is abstracted from the developer and usage of the Js.Array module is acceptable 742 | for native work. */ 743 | 744 | // An array is declared with `[| |]` with comma-separated values. 745 | let languages = [|"Reason", "JavaScript", "OCaml"|]; 746 | 747 | // Access indices with the `[i]` syntax. 748 | languages[1] // "JavaScript" 749 | 750 | 751 | /*---------------------------------------------- 752 | * Function 753 | *---------------------------------------------- 754 | */ 755 | 756 | /* Since ReasonML is a functional language, functions are distinctly different from many 757 | other languages. Since they are treated as evaluations, to wit evaluations of 758 | mathematical functions, there is no such thing as a `return` command. Whatever value 759 | exists at the end of the evaluation block is implicitly returned. Further, all 760 | functions MUST return something. If no final value is present, the function will return 761 | the special value `unit`, which will be discussed shortly. */ 762 | 763 | // Fat arrow syntax declares a function. 764 | // Parenthesis are optional on single-parameter functions. 765 | let signUpToNewsletter = (email) => "Thanks for signing up " ++ email; 766 | 767 | let getEmailPrefs = (email) => { 768 | let message = "Update settings for " ++ email; 769 | let prefs = ["Weekly News", "Daily Notifications"]; 770 | 771 | (message, prefs); // Implicitly returned 772 | }; 773 | 774 | // Call a function with the standard syntax. 775 | signUpToNewsletter("hello@reason.org"); 776 | 777 | 778 | /*** Unit ***/ 779 | 780 | /* Reason has a special type called `unit`. It is the concept of a "thing" 781 | that is "nothing." It is different from `None` in that `None` is the state 782 | of nothing being where `Some()` could also have been. `Unit` is the state of 783 | expected nothing. It is similar to `void` in other languages. Unit can be 784 | declared with the `unit` type keyword or the `()` syntax. */ 785 | 786 | // Unit's first use is in declaring functions that take no arguments. 787 | let noArgument = () => { "I've got nothing" }; 788 | 789 | /* All functions necessarily return something, so if there is no expression 790 | intended for return, such as in functions that only handle side-effects, then 791 | that function will return `unit`. Functions that return `unit` can be 792 | explicitly typed. */ 793 | 794 | let noReturn = (input) : unit => { print_endline("I just print " ++ input) }; 795 | 796 | /* The above function expects to return nothing and will throw a compile error 797 | if anything is returned. */ 798 | 799 | 800 | /*** > Labeled Paramters/Arguments ***/ 801 | 802 | type position = { 803 | posx : float, 804 | posy : float 805 | } 806 | 807 | // Parameters can be labeled with the `~` symbol. 808 | let moveTo = (~x, ~y) => {posx : x, posy : y}; 809 | 810 | // Any order of labeled arguments is acceptable. 811 | moveTo(~x=7.0, ~y=3.5); 812 | moveTo(~y=3.5, ~x=7.0); 813 | 814 | // Labeled parameters can also have an alias used within the function. 815 | let getMessage = (~message as msg) => "Message for you, sir... " ++ msg; 816 | 817 | // Labeled parameters support explicit typing. 818 | let logMessage = (~message: string) => { 819 | print_endline(message); 820 | }; 821 | 822 | let logAnotherMessage = (~message as msg: string) => { 823 | print_endline(msg); 824 | }; 825 | 826 | 827 | /*** > Currying ***/ 828 | 829 | /* Curring is the act of decomposing a function that takes multiple 830 | arguments into a series of functions that each take one argument. 831 | 832 | In practice, this is done automatically by calling a function with 833 | fewer arguments than it has parameters. For example, if a function 834 | has `n` parameters, and it is called with `x` arguments, a function 835 | with `n - x` parameters is returned. 836 | 837 | All functions are curriable by default in ReasonML. */ 838 | 839 | let divide = (denom, numr) => numr / denom; 840 | let divideBySix = divide(6); 841 | let divideByTwo = divide(2); 842 | 843 | divide(3, 24); /* int = 8 */ 844 | divideBySix(12); /* int = 2 */ 845 | divideByTwo(10); /* int = 5 */ 846 | 847 | // To partially call with deeper arguments, the anonymous variable `_` is used. 848 | 849 | let divideSixBy = divide(_, 6); 850 | let divideTenBy = divide(_, 10); 851 | 852 | divideSixBy(2); /* int = 3 */ 853 | divideTenBy(2); /* int = 5 */ 854 | 855 | // Labeled parameters allow currying without the need for the anonymous variable. 856 | 857 | let labeledDiv = (~denom, ~numr) => numr / denom; 858 | let labeledDivBySix = labeledDiv(~denom = 6); 859 | let labeledDivByTwo = labeledDiv(~denom = 2); 860 | 861 | labeledDivBySix(~numr=36); /* int = 6 */ 862 | labeledDivByTwo(~numr=4); /* int = 2 */ 863 | 864 | 865 | /*** > Optional Labeled Parameters/Arguments ***/ 866 | 867 | /* Using the `=?` qualifier makes a parameter optional, meaning that it is turned 868 | into an Option type. This means that the parameter cannot simply be used in the 869 | function. In the below example, a switch statement is used. This functionality 870 | will be discussed shortly, but is mostly self-explanatory here. 871 | 872 | The final parameter of a function with optional arguments must be unit `()`. 873 | This syntax prevent currying. */ 874 | 875 | let greetPerson = (~name, ~greeting=?, ()) => { 876 | switch(greeting) { 877 | | Some(greet) => greet ++ name 878 | | None => "Hello, " ++ name 879 | }; 880 | }; 881 | 882 | // Calling a function without optional arguments requires unit as the final argument. 883 | greetPerson(~name="Kate", ()); 884 | 885 | 886 | /*** > Pipe ***/ 887 | 888 | /* ReasonML has two ways to call functions: the standard syntax and the pipeline 889 | operator. 890 | 891 | Conceptually, it is simple. Where `myFunction(value)` says "perform myFunction 892 | with this value", `(value) -> myFunction` says "send myValue to myFunction." 893 | 894 | There is actually a subtle semantic difference between the two. Where the first 895 | calls the function, the second does not. Instead, it takes a value and applies 896 | a function to it. From a semantic perspective, this means that calling a 897 | function either with incorrect data or no data is impossible, since the function 898 | is not what is being called. 899 | 900 | From a syntactic standpoint, this facilitates the easy passing of one function's 901 | output to another's input. Users of command line interfaces such as Bash will be 902 | accustomed to this pattern. 903 | 904 | Since pipes, either first or last, require positional information to work, they 905 | are incompatible with functions with labeled parameters. 906 | 907 | People who are staying abreast of JavaScript development may notice that this 908 | syntax is currently in the experimental stage in the language specification. 909 | JavaScript is adopting many ideas from the ML line of languages. */ 910 | 911 | let subtract = (x, y) => { x - y }; 912 | let subtractTwo = subtract(_, 2); 913 | 914 | 3 -> subtractTwo; // int = 1 915 | 916 | /* The thin arrow syntax is called "pipe-first" since the value passed in 917 | is used as the first argument in the receiving function. */ 918 | 919 | let subtractFromThree = 3 -> subtract; 920 | 921 | /* The `|>` operator is called pipe-last, or confusingly, the 922 | reverse-application operator, which passes in the final argument. 923 | 924 | For single-parameter functions, the pipe operators behave identically. */ 925 | 926 | let subtractFive = 5 |> subtract; 927 | 928 | let addOne = a => a + 1; 929 | let divByTwo = a => a / 2; 930 | let multByThree = a => a * 3; 931 | 932 | let pipedValue = 3 -> addOne -> divByTwo -> multByThree; // int = 6 933 | 934 | 935 | /*---------------------------------------------- 936 | * Control Flow & Pattern Matching 937 | *---------------------------------------------- 938 | */ 939 | 940 | /*** > If-else ***/ 941 | 942 | /* Like functions, `if` and `else` are evaluation blocks that will 943 | return the final expression within the block. Just as with functions, 944 | there is no early return. */ 945 | 946 | let isMorning = true; 947 | let newGreeting = if (isMorning) { 948 | let goodMorning = "Good morning!"; 949 | goodMorning; 950 | } else { 951 | let genericGreeting = "Hello!"; 952 | genericGreeting; 953 | }; 954 | 955 | /* For blocks that do not return anything, such as side effects, `unit` 956 | is implicitly returned even if there is nothing to accept it. */ 957 | 958 | let logSomething = true; 959 | if (logSomething) { 960 | print_endline("Logged!"); 961 | // `unit` is returned. 962 | }; 963 | 964 | /* 'if` does not exist independently. It is always paired with `else`, 965 | either implicitly or explicitly. */ 966 | 967 | // let isItTrue = if (false) { 968 | // "It's not true!" 969 | // }; 970 | 971 | /* The above code will produce a compile error. The `else` block is 972 | unwritten, and thus implicitly returns `unit`, where the if block 973 | returns a string. Both branches must return the same type. The 974 | below illustrates the correct form. 975 | */ 976 | 977 | let isItReallyTrue = if (false) { 978 | "It's really not true!"; 979 | } else { 980 | "It's actually true!"; 981 | }; 982 | 983 | 984 | /*** > Loops ***/ 985 | 986 | /* Loops are similar to other languages except that Reason has no `break` or 987 | `continue`. This is consistent with the idea that any procedure must have 988 | a single entrance and a single exit, just as a mathematical algorithm 989 | would have. */ 990 | 991 | /* For Loop */ 992 | 993 | let loopStart = 1; 994 | let loopEnd = 42; 995 | 996 | for (x in loopStart to loopEnd) { 997 | print_int(x); 998 | print_string(" "); 999 | }; 1000 | 1001 | // Reason allows decrementing in loops with the `downto` statement. 1002 | 1003 | let dLoopStart = 42; 1004 | let dLoopEnd = 1; 1005 | 1006 | for (x in dLoopStart downto dLoopEnd) { 1007 | print_int(x); 1008 | print_string(" "); 1009 | }; 1010 | 1011 | /* Imperative loops are seen as bad practice because even in a secure, highly 1012 | symbolic language like Reason, fence post errors are possible. In the below, 1013 | the code will compile but result in an 'index out of bounds' error when 1014 | run because the final index called is 4. */ 1015 | 1016 | let testArray = [|1, 2, 3, 42|]; 1017 | let testArrayLength = Belt.Array.length(testArray); 1018 | 1019 | for (x in 0 to testArrayLength) { 1020 | print_int(testArray[x]); 1021 | }; 1022 | 1023 | /* While Loop */ 1024 | 1025 | /* While Loops are more or less similar to other languages, but since a value 1026 | is immutable, a `ref()` wrapper must be used 1027 | 1028 | Just as with For Loops, While Loops should be avoided since the constraints 1029 | are not being set by the data under calculation. For Loops risk fence post 1030 | errors and While Loops risk infinite loops. In the below, if `testVariable` 1031 | were never set to false, the loop would not stop. */ 1032 | 1033 | let testVariable = ref(true); 1034 | while (testVariable^) { 1035 | print_endline("It's true."); 1036 | testVariable := false; 1037 | } 1038 | print_endline("It's now false."); 1039 | 1040 | 1041 | /*** > Pattern Matching ***/ 1042 | 1043 | /* Pattern matching is ReasonML's crown jewel. It helps prevent all manner 1044 | of errors and unintended behaviors and offers a profound competitive 1045 | advantage in comparison to other languages and tools. It's power is so 1046 | great, that almost all other major languages either have implemented, 1047 | or are in the process of implementing, pattern matching. 1048 | 1049 | Pattern matching uses decomposition of input to analyze the relationship 1050 | of tokens to find set patterns. This stands in contrast to a direct 1051 | comparison of two values sitting in memory. That sounds fighteningly 1052 | complex but it is actually rather straightforward. A complete discussion 1053 | of pattern matching on a theoretical level is beyond the scope of this 1054 | tour, but it is encouraged to read the Wikipedia page on the subject. 1055 | 1056 | https://en.wikipedia.org/wiki/Pattern_matching 1057 | 1058 | In ReasonML, as with many functional languages, pattern matching is used 1059 | for all comparisons. Sometimes this acts like common value comparisons in 1060 | other languages, like `x === y` in JavaScript. But unlike simple comparisons, 1061 | a pattern has a finite number of states, meaning that the compiler can warn 1062 | the programmer if all possible patterns are not accounted for. This power 1063 | becomes apparent with `switch` statements. */ 1064 | 1065 | 1066 | /*** > Switch ***/ 1067 | 1068 | /* The `switch` statement is similar to JavaScript's `switch` but enhanced by 1069 | the power of pattern matching. Indeed, in OCaml, it is called `Match`. In 1070 | this example, the previous `authType` variant is used. */ 1071 | 1072 | type dndPlayer = 1073 | | DungeonMaster 1074 | | Barbarian 1075 | | Thief 1076 | | Wizard; 1077 | 1078 | let newDndPlayer = Barbarian; 1079 | 1080 | let loginMessage = 1081 | switch (newDndPlayer) { 1082 | | DungeonMaster => "May adventurers speak of your tales for generations." 1083 | | Barbarian => "May your enemies crumble before your might." 1084 | | Thief => "May your enemies see nothing before their deaths." 1085 | | Wizard => "May your enemies burn from the power in your hands." 1086 | }; 1087 | 1088 | /* All four possible states of the dndPlayer type must be accounted for. If the 1089 | switch case for `Wizard` were deleted, the error "this pattern-matching 1090 | is not exhaustive." would be raised by the compiler. Similarly, whenever 1091 | an `option` is used, any `switch` statement must take into account both 1092 | possible states, `some()` and `none`. */ 1093 | 1094 | let userId = Some(23); 1095 | let alertMessage = 1096 | switch (userId) { 1097 | | Some(id) => "Welcome, your ID is" ++ string_of_int(id) 1098 | | None => "You don't have an account!" 1099 | }; 1100 | 1101 | /* As stated, pattern matching is not simply a comparison of values. Complex 1102 | structures can be tested and matched. In this case, the possible analysis 1103 | space is infinite, as opposed to a finite variant, so the final case is the 1104 | anonymous variable, indicating a catch-all that captures everything not 1105 | caught by the previous cases. Just as with `authType`, if the catch-all is 1106 | deleted, a non-exhaustive pattern match error will be raised by the compiler. */ 1107 | 1108 | let firstNames = ["James", "Jean", "Geoff"]; 1109 | switch (firstNames) { 1110 | | [] => "No names" 1111 | | [first] => "Only " ++ first 1112 | | [first, second] => "A couple of names " ++ first ++ "," ++ second 1113 | | [first, second, third] => "Three names, " ++ first ++ ", " ++ second ++ ", " ++ third 1114 | | _ => "Lots of names" // This cannot be deleted. 1115 | }; 1116 | 1117 | let event = 5; 1118 | switch (event) { 1119 | | 1 => "Green" 1120 | | 0 | 5 => "Red" 1121 | | 2 | 3 | 4 => "Black" 1122 | | _ => "Gray"; 1123 | }; 1124 | 1125 | let importantNumbers = [42, 2001, 31459]; 1126 | // let [answer, yearWeMakeContact, pi] = importantNumbers; 1127 | 1128 | /* The above code triggers a non-exhaustive error because lists and arrays 1129 | are of potentially variable size. In this example, the list is a known fixed size, 1130 | so the error is unfounded. This is another example of enforced protection against 1131 | null and undefined errors. 1132 | 1133 | This should only throw a warning, but there is a bug in Bucklescript at the moment 1134 | that causes a full compile failure. This bug exists as of April 26th, 2020. 1135 | 1136 | The idiomatically correct way to destructure entities of unknown size is through 1137 | pattern matching into a fixed-size tuple. While this may seem like unecessary 1138 | boilerplate, it is another example of Reason's rigid enforcement of type security. */ 1139 | 1140 | let (answer, yearWeMakeContact, pi) = switch (importantNumbers) { 1141 | | [a, b, c] => (a, b, c) 1142 | | _ => (0, 0, 0) 1143 | }; 1144 | 1145 | /*** > When clause ***/ 1146 | 1147 | let isJohn = a => a == "John"; 1148 | let maybeName = Some("John"); 1149 | 1150 | /* When can add more complex logic to a simple switch */ 1151 | let aGreeting = 1152 | switch (maybeName) { 1153 | | Some(name) when isJohn(name) => "Hi John! How's it going?" 1154 | | Some(name) => "Hi " ++ name ++ ", welcome." 1155 | | None => "No one to greet." 1156 | }; 1157 | 1158 | 1159 | /*** > Exception ***/ 1160 | 1161 | /* Unlike many other languages, exceptions in Reason and OCaml are truly intended 1162 | to represent exceptional circumstances in your code. Exceptions are a special 1163 | variant, just as `option` is. But unlike `option`, exception can be extended 1164 | with custom constructors. */ 1165 | 1166 | // Define custom exceptions with the `exception` statement. 1167 | exception Impossible_Age; 1168 | 1169 | type patientDetails = { 1170 | name : string, 1171 | age : int, 1172 | height : int, 1173 | weight : float 1174 | } 1175 | 1176 | // Trigger an exception with the `raise` call. 1177 | let validatePatientAge = (patient : patientDetails) => 1178 | if (patient.age < 122 && patient.age > 0) { 1179 | "Now seeing " ++ patient.name ++ "." 1180 | } else { 1181 | raise(Impossible_Age); 1182 | }; 1183 | 1184 | let newPatient = {name : "Jeanne Calment", age : 122, height : 150, weight : 55.0}; 1185 | 1186 | // Pattern match on the exception Under_Age. 1187 | switch (validatePatientAge(newPatient)) { 1188 | | status => print_endline(status) 1189 | | exception Impossible_Age => 1190 | print_endline(newPatient.name ++ " - Invalid Age : " ++ string_of_int(newPatient.age) ) 1191 | }; 1192 | 1193 | /* Try */ 1194 | 1195 | /* Try blocks are a special switch specifically intended to handle exceptions. 1196 | As such, they do not need the exception label. */ 1197 | 1198 | let messageToEvan = 1199 | try (validatePatientAge(newPatient)) { 1200 | | Impossible_Age => newPatient.name ++ " - Invalid Age : " ++ string_of_int(newPatient.age) 1201 | }; 1202 | 1203 | /* It should be noted that in the above examples, exceptions were not needed. 1204 | Ordinary variants and options would have been sufficient. Exceptions exist 1205 | primarily for performance reasons in OCaml, where an exception is much 1206 | faster than the more usual forms of control flow and handling. In JavaScript, 1207 | this is not the case. As such, if you are targeting JavaScript, there is 1208 | genuinely no reason to use exceptions. */ 1209 | 1210 | 1211 | /*---------------------------------------------- 1212 | * Modules & Interfaces 1213 | *---------------------------------------------- 1214 | */ 1215 | 1216 | /* A module is essentially a namespace. A reasonable analog from other languages 1217 | is the concept of a class. Basically, it is a hunk of isolated code. */ 1218 | 1219 | // Create a new module with the `module` statement. 1220 | module Staff = { 1221 | type role = 1222 | | Delivery 1223 | | Sales 1224 | | Other; 1225 | 1226 | type staffMember = { 1227 | employeeName : string, 1228 | role, 1229 | }; 1230 | 1231 | let defaultRole = Other; 1232 | 1233 | let getRoleDirectionMessage = staff => 1234 | switch (staff.role) { 1235 | | Delivery => "Deliver it like you mean it!" 1236 | | Sales => "Sell it like only you can!" 1237 | | Other => "You're an important part of the team!" 1238 | }; 1239 | }; 1240 | 1241 | // A module can be accessed with dot notation. 1242 | let employee : Staff.staffMember = { employeeName : "Wilma", role : Staff.Delivery }; 1243 | 1244 | /* As stated, modules provide namespacing, meaning that what is within the scope 1245 | of a module is not within the current working scope. To bring a module's 1246 | contents into the working scope, use the `open` command. This process is not 1247 | a copy, but a reference. The contents of a module is made visible to 1248 | another. */ 1249 | 1250 | module NewStaff = { 1251 | open Staff; 1252 | let newRole = Delivery; 1253 | let newEmployee: staffMember = {employeeName: "Fred", role: Other}; 1254 | } 1255 | 1256 | /* Continuing the analogy of a module as a class, a module can be extended using 1257 | the `include` command. Using `include` brings the contents of one module into 1258 | the scope of another module. While this may superficially seem similar to 1259 | `open`, `include` actually copies the content of one module into another while 1260 | still allowing for value overrides. */ 1261 | 1262 | module SpecializedStaff = { 1263 | include Staff; 1264 | 1265 | let ceo: staffMember = {employeeName: "Barnie", role: Other}; 1266 | 1267 | let defaultRole = Delivery; 1268 | 1269 | let getMeetingTime = staff => 1270 | switch (staff) { 1271 | | Other => 11_15 1272 | | _ => 9_30 1273 | }; 1274 | }; 1275 | 1276 | /* The power of include comes from levels beyond two. Since include copies the 1277 | contents, when a third module opens or includes the second, it still has 1278 | access to the contents of the first module. */ 1279 | 1280 | module Module1 = { 1281 | type musician = 1282 | | Classical 1283 | | Jazz 1284 | | Rock 1285 | | Hiphop 1286 | | Electronic; 1287 | } 1288 | 1289 | module Module2 = { 1290 | open Module1; 1291 | let newMusician = Classical; 1292 | } 1293 | 1294 | module Module3 = { 1295 | include Module1; 1296 | let newMusician = Classical; 1297 | } 1298 | 1299 | /* In both Module2 and Module3, the type of `newMusician` does not need to be 1300 | explicitly stated. But since Module2 only opened Module1, while Module3 1301 | included it, later modules must treat them differently. */ 1302 | 1303 | module Module4 { 1304 | open Module2; 1305 | let externalRef = newMusician; 1306 | // let newMusician2 = Classical; // Error : Unbound constructor Classical 1307 | } 1308 | 1309 | module Module5 { 1310 | include Module2; 1311 | let externalRef = newMusician; 1312 | // let newMusician2 = Classical; // Error : Unbound constructor Classical 1313 | } 1314 | 1315 | /* In Module4 and Module5, both will produce errors regardless of whether `open` 1316 | or `include` is used since module2 only opened Module1. It is important to 1317 | note that the log of `newMusician` remains valid since the static value is 1318 | still passed even though the constructor is not. */ 1319 | 1320 | module Module6 { 1321 | open Module3; 1322 | let externalRef = newMusician; 1323 | let newMusician2 = Classical; 1324 | } 1325 | 1326 | module Module7 { 1327 | include Module3; 1328 | let externalRef = newMusician; 1329 | let newMusician2 = Classical; 1330 | } 1331 | 1332 | /* Both Module6 and Module7 will compile correctly since they reference Module3, 1333 | which included Module1. Thus, `include` allows module chaining, whereas `open` 1334 | only allows a parent-child relationship. */ 1335 | 1336 | 1337 | /*** Records, Variants, and Other Files ***/ 1338 | 1339 | /* As stated, all files are by default modules. Because of this, unlike in other 1340 | languages, Reason does not require explicit imports of files. Instead, a 1341 | module name need simply be referenced in the code itself. The compiler will 1342 | recursively search the file tree for definitions, using the current file as 1343 | the root. */ 1344 | 1345 | // Since module names must be capitalized, so must filenames. 1346 | 1347 | module Module_in_external_file = { // From Module_in_external_file.re 1348 | type externalVariant = 1349 | | Thing1 1350 | | Thing2; 1351 | 1352 | type externalType = { 1353 | key1: string, 1354 | key2: int, 1355 | }; 1356 | } 1357 | 1358 | /* The above module was explicitly declared only for the sake of allowing this 1359 | tutorial to compile. Since all files are modules by default, the `externalVariant` 1360 | and `externalType` would usually be the only thing in the file. */ 1361 | 1362 | module Current_working_file = { 1363 | let newThing : Module_in_external_file.externalVariant = Thing1; 1364 | } 1365 | 1366 | /* All files intended for import must have unique names to ensure that the recursive 1367 | search does not suffer from conflicts. This encourages semantic file naming instead 1368 | of semantic directory structuring and thus a flat file structure. This flies in 1369 | the face of conventions in many other languages but provides some security. A file 1370 | named `card.js`, when removed from its semantic directory, is meaningless. As such, 1371 | the information is fragile. But a filename such as `Left_hand_menu_account_card.js`, 1372 | while long, is robustly semantic. This file can sit anywhere and still communicate 1373 | its nature. */ 1374 | 1375 | 1376 | /*** Interfaces ***/ 1377 | 1378 | /* By default, everything in a module is exported and made available to code that 1379 | references it. To customize what pieces of a module are made visible, an interface must 1380 | be created. This is also called a signature because every module has and projects a 1381 | signature, but is by default implicit. 1382 | 1383 | Interfaces are a specification. They dictate what a module must provide to be of a type. 1384 | As such, module types must be explicitly declared on modules that fulfill all requirements 1385 | of that type. If Module8 did not provide `visibleThing` and `visibleFunction`, an error 1386 | would be produced. 1387 | 1388 | Interfaces are only inclusive, not exclusive in their requirements. Below, Module8 has 1389 | a second value/function pair which does not cause a failure, it only causes an unused 1390 | value warning since, while the interface does not exclude their existence, it does not expose 1391 | them to the outside. 1392 | 1393 | If an interface is contained in a separate file, it must be inside an interface file 1394 | with an identical name as the module file but with the extension `.rei`. In this 1395 | scenario, the type of the module does not need to be explicitly declared. The 1396 | compiler knows to bind the signature to the module because of the matching file names. 1397 | 1398 | If contained within the same file as the module, the `type` statement is used. */ 1399 | 1400 | module type Module8Interface = { 1401 | let visibleThing : int; 1402 | let visibleFunction : (int, int) => int; 1403 | }; 1404 | 1405 | module Module8 : Module8Interface = { 1406 | let visibleThing = 2001; 1407 | let visibleFunction = (x,y) => { 1408 | x * y 1409 | }; 1410 | 1411 | let invisibleThing = 42; 1412 | let invisibleFunction = (a,b) => { 1413 | a ++ b 1414 | }; 1415 | }; 1416 | 1417 | module Module9 = { 1418 | include Module8; 1419 | print_int(visibleFunction(2,3)); 1420 | // print_endline(invisibleFunction("Hello,", "Goodbye")); // Unbound value error. 1421 | } 1422 | 1423 | // Module interfaces can be in-lined. 1424 | 1425 | module Module10 : { 1426 | let func1: (int,int) => int; 1427 | let func2: (string,string) => string; 1428 | } = { 1429 | let func1 = (x,y) => { 1430 | x + y 1431 | }; 1432 | let func2 = (a,b) => { 1433 | a ++ b 1434 | }; 1435 | }; 1436 | 1437 | type myVariant = 1438 | | State1 1439 | | State2 1440 | | State3(string); 1441 | 1442 | let aThing = State2; 1443 | 1444 | if (aThing == State2) { 1445 | print_endline("Thing!") 1446 | } 1447 | 1448 | 1449 | 1450 | /*---------------------------------------------- 1451 | * JavaScript Interoperation 1452 | *---------------------------------------------- 1453 | */ 1454 | 1455 | /* Even though Reason is a language unto itself, for the time being, its primary compile 1456 | target is JavaScript. As such, a great many JavaScript-specific tools are available 1457 | to developers. This section is not intended to be exhaustive. For greater information, 1458 | see the Bucklescript docs at https://bucklescript.github.io/. */ 1459 | 1460 | // Most abilities are attached to the `Js` module. 1461 | Js.log("This will log to the console."); 1462 | Js.logMany([|"Log", "an", "array"|]); 1463 | 1464 | /* Promises are currently supported with expected behaviors. Resolve and reject are 1465 | uncurried callbacks. This is a feature of Bucklescript and requires the leading `.` in 1466 | the list of arguments. The pipe-last operator is used for chaining. */ 1467 | 1468 | let getAccountID = Js.Promise.make( (~resolve, ~reject) => resolve(. 1337)); 1469 | 1470 | getAccountID 1471 | |> Js.Promise.then_(result => { 1472 | Js.Promise.resolve(result); 1473 | }) 1474 | |> Js.Promise.catch(err => { 1475 | Js.log2("Failure!!", err); 1476 | Js.Promise.resolve(-1); 1477 | }); 1478 | 1479 | // Async/Await support is under development. 1480 | 1481 | 1482 | /*---------------------------------------------- 1483 | * Bucklescript 1484 | *---------------------------------------------- 1485 | */ 1486 | 1487 | /* A note on BuckleScript */ 1488 | 1489 | /* The ReasonML community has been thrown into a degree of chaos with the 1490 | release of a new Bucklescript-specific ReasonML syntax. Basically, if you 1491 | are targeting JavaScript with your ReasonML code, it will become more 1492 | specifically targeted to that JavaScript and less well-suited for native 1493 | work. The extent to which this will affect current projects and endeavors 1494 | is, regardless of what many are saying, unknown. 1495 | 1496 | What can be said for sure is that everything below is officially deprecated 1497 | and will slowly fade into that great git repo in the sky. 1498 | 1499 | The changes can be found here: 1500 | https://reasonml.org/blog/bucklescript-8-1-new-syntax 1501 | 1502 | Docs are here: 1503 | https://reasonml.org/docs/reason-compiler/latest/new-bucklescript-syntax 1504 | 1505 | A listing of many concerns can be found here: 1506 | https://reasonml.chat/t/bucklescript-8-1-new-syntax-option/2379 */ 1507 | 1508 | 1509 | /* As earlier stated, Bucklescript is the transpiler for turning an OCaml syntax tree into 1510 | JavaScript. When ReasonML compiles, it is turned into an OCaml syntax tree and can then 1511 | be pulled into all existing OCaml toolchains. While ReasonML contains many of its own 1512 | JavaScript abilities, accessing the broader JavaScript world requires the use of 1513 | Bucklescript-specific tools. This comes into play most commonly when writing bindings 1514 | between ReasonML and existing JavaScript that cannot be easily converted to ReasonML. 1515 | 1516 | Bindings are code that take external JavaScript and represent it in ReasonML's symbolic 1517 | system. When transpiled to JavaScript, Bucklescript will generate functions that check 1518 | the consistency of the JavaScript according to the provided bindings. This means that, 1519 | unlike in TypeScript, transpiled ReasonML code is type safe. It will perform run-time 1520 | checks, injecting stability and debuggability into the application in case of unexpected 1521 | external input, as from the response from an API. 1522 | 1523 | In the below example, the %bs.raw decorator allows direct injection of JavaScript. What 1524 | the JavaScript accepts from ReasonML and what it returns to ReasonML must still be typed, 1525 | but there are no guarantees that the JavaScript will actually accept or return what is 1526 | declared. */ 1527 | 1528 | let jsReduce: (array(int)) => int = [%bs.raw 1529 | {| 1530 | function (numbers) { 1531 | var result = 0; 1532 | numbers.forEach( (number) => { 1533 | result += number; 1534 | }); 1535 | return result; 1536 | } 1537 | |} 1538 | ]; 1539 | 1540 | let calculate = (numbers) => jsReduce(Array.of_list(numbers)); 1541 | 1542 | /*---------------------------------------------- 1543 | * Belt 1544 | *---------------------------------------------- 1545 | */ 1546 | 1547 | /* Belt is an implementation/extension of OCaml's standard library that provides additional tools 1548 | specifically to facilitate transpilation to JavaScript. From the perpsective of JavaScript 1549 | developers, the best analogy is Lodash. Unlike Lodash, Belt comes along as a native part of 1550 | Bucklescript. As of February 2020, Belt is officially in beta, with breaking changes 1551 | periodically occuring. That said, it is mostly stable and widely used by developers in the 1552 | community. 1553 | 1554 | Belt possesses much of the same functionality as the Js module. It is recommended to use Belt 1555 | instead of Js since it prevents application logic from being tied to JavaScript. 1556 | 1557 | There are two parts to the Belt library: the primary module and the "flattened" modules. The 1558 | primary module has sub-modules, each of which contain functions for manipulating data. The 1559 | flattened modules are prefixed with Belt_, such as Belt_Map, and are the legacy tools. The 1560 | primary module is intended to ultimately provide all of the same functionality of the flattened 1561 | modules, but it is a work in progress. Currently, the flattened modules provide some 1562 | additional functionality, such as tools for manipulating AVL trees. 1563 | 1564 | When given the choice, do not use the flattened modules unless a particular piece of functionality 1565 | is utterly necessary. The flattened modules will disappear at some point in a future version of 1566 | Bucklescript. */ 1567 | 1568 | let testArray = [|"1", "2", "3"|]; 1569 | 1570 | Belt.Array.forEach(testArray, (element) => { 1571 | Js.log(element); 1572 | }); 1573 | 1574 | // The flattened module does the same thing. It is simply deprecated syntax. 1575 | 1576 | Belt_Array.forEach(testArray, (element) => { 1577 | Js.log(element); 1578 | }); 1579 | 1580 | 1581 | /*---------------------------------------------- 1582 | * ReasonReact 1583 | *---------------------------------------------- 1584 | */ 1585 | 1586 | /* ReasonML was created by the same person, Jordan Walke, that created React. It could be seen 1587 | as a language created specifically to enable faster, more reliable production of React apps. 1588 | React and ReasonML have walked hand-in-hand because of this, meaning that much of the 1589 | discussion surrounding the language has focused on React. ReasonReact's home page is even 1590 | hosted on the same domain as ReasonML. As such, it is sensible to include an overview of the 1591 | React library's bindings. */ 1592 | 1593 | [@react.component] 1594 | let make = (~food) => { 1595 | let (amount, eat) = React.useState( () => 0 ); 1596 | let eatMore = () => { eat( (_) => { amount + 1 } ) }; 1597 |
1598 |

{ React.string( {j| $food monster has eaten $amount $(food)s |j} ) }

1599 | 1600 |
1601 | }; 1602 | 1603 | /* The above abstracts away much of React's boilerplate. All that must be written are the 1604 | decorator and the render function, which is called `make`. The example is a variation 1605 | on the standard React Hooks example available on React's website, and the ReasonML version 1606 | available on the ReasonReact site. 1607 | 1608 | The labeled arguments constitute the component's props. The next line is the state Hook. 1609 | `useState` returns a 2-tuple, with the first element being the value to store and the second 1610 | being the function that can change that value. With destructuring, the names `amount` 1611 | and `eat` are bound to the values. `useState` takes a single argument, a function that 1612 | initializes the state value. In the above case, an inital value of `int : 0` is bound to 1613 | `amount`. 1614 | 1615 | The returned function, `eat`, is a wrapper that adds a signature to, and then returns, a 1616 | provided function. In this example, the anonymous function passed into `eat` is signed as 1617 | accepting an integer and returning an integer. Any function passed into `eat` must follow the 1618 | same signature. 1619 | 1620 | `eatMore` is a wrapper function that calls `eat`. `eat` cannot be directly called because 1621 | the function must accept a React Event, while functions that can change the state will only 1622 | accept the same type that they return, in this case an integer. `eatMore` is a single-use 1623 | function with no special signature. The function passed into `eat` must accept an integer, 1624 | but there is no integer to provide. An anonymous variable is provided since it fulfills any 1625 | type requirement. 1626 | 1627 | The final piece of JSX is the implicitly returned value. All strings must be placed in the 1628 | typed wrapper React.string(). The `onClick` event automatically captures the React event and 1629 | passes it into the provided function. In this example, an underscore is used to create a 1630 | casual variable. Since the event is not being used, a warning would otherwise be displayed. 1631 | 1632 | The above steps and nested functions may seem overly complex, but it is the nature of 1633 | functional programming that steps are not broken down by passing arguments or mutating values. 1634 | Instead, steps are taken with the progressive application of signed and typed functions. 1635 | 1636 | ReasonReact is built around Hooks and supports all of them. The older API is deprecated. 1637 | */ 1638 | 1639 | 1640 | /*---------------------------------------------- 1641 | * Monads 1642 | *---------------------------------------------- 1643 | */ 1644 | 1645 | /* ReasonML is a functional language, meaning that processes are based on the evaluations of 1646 | mathematical functions. This means that for the same inputs, the program will always return 1647 | the same output. This results in the problem of how a program is to have state or handle effects. 1648 | Common programs have a great many effects, such as logs, timers, I/O, etc. Pure functional 1649 | languages like Haskell solve these problems through the use of monads. 1650 | 1651 | ReasonML, and OCaml before it, made practical concessions to the needs of developers by allowing, 1652 | and having language structures for, effects and state. It did not forget the lessons of monads, 1653 | though, and has a few on the language level. Most notable, and most striking to developers coming 1654 | from more procedural languages, is Option. 1655 | 1656 | A monad is simply a structure that follows certain rules. Monads are not peculiar to functional 1657 | languages. They simply arose in usage in that context because they were necessary for solving 1658 | certain problems. Their utility has become apparent, with other languages adopting common monads 1659 | like Option. Scala, Kotlin, C#, and Java all now have optional values as part of the language. 1660 | Indeed, monads generally are becoming common. JavaScript has perhaps the most widely used monad in 1661 | computer history in the form of the Promise. 1662 | 1663 | The purpose of this section therefore is to bring focus to their genesis and hopefully explain 1664 | why knowledge of monads, and category theory more broadly, is an extremely fruitful course of 1665 | investigation. */ 1666 | --------------------------------------------------------------------------------