summaryrefslogtreecommitdiffhomepage
path: root/reason.html.markdown
blob: 065dd33b268471c898b4102cbde1a883dad73b55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
---
language: reason
filename: reason.re
contributors:
  - ["Seth Corker", "https://sethcorker.com"]
---

Reason is a syntax over OCaml that is easier to get started for programmers who are familiar with C-style syntax like JavaScript. BuckleScript is part of the toolchain which compiles Reason to JavaScript so you can write statically typed code for anywhere that JavaScript runs.

```reason
/* Comments start with slash-star, and end with star-slash */

/*----------------------------------------------
 * Variable and function declaration
 *----------------------------------------------
 * Variables and functions use the let keyword and end with a semi-colon
 * `let` bindings are immutable
 */

let x = 5;
/* - Notice we didn't add a type, Reason will infer x is an int */

/* A function like this, take two arguments and add them together */
let add = (a, b) => a + b;
/* - This doesn't need a type annotation either! */

/*----------------------------------------------
 * Type annotation
 *----------------------------------------------
 * Types don't need to be explicitly annotated in most cases but when you need
 * to, you can add the type after the name
 */

/* A type can be explicitly written like so */
let x: int = 5;

/* The add function from before could be explicitly annotated too */
let add2 = (a: int, b: int): int => a + b;

/* A type can be aliased using the type keyword */
type companyId = int;
let myId: companyId = 101;

/* Mutation is not encouraged in Reason but it's there if you need it
   If you need to mutate a let binding, the value must be wrapped in a `ref()`*/
let myMutableNumber = ref(120);

/* To access the value (and not the ref container), use `^` */
let copyOfMyMutableNumber = myMutableNumber^;

/* To assign a new value, use the `:=` operator */
myMutableNumber := 240;

/*----------------------------------------------
 * Basic types and operators
 *----------------------------------------------
 */

/* > String */

/* Use double quotes for strings */
let greeting = "Hello world!";

/* A string can span multiple lines */
let aLongerGreeting = "Look at me,
I'm a multi-line string
";

/* A quoted string can be used for string interpolation and special chars
   Use the `js` annotation for unicode */
let world = {js|🌍|js};

/* The `j` annotation is used for string interpolation */
let helloWorld = {j|hello, $world|j};

/* Concatenate strings with ++ */
let name = "John " ++ "Wayne";
let emailSubject = "Hi " ++ name ++ ", you're a valued customer";

/* > Char */

/* Use a single character for the char type */
let lastLetter = 'z';
/* - Char doesn't support Unicode or UTF-8 */

/* > Boolean */

/* A boolean can be either true or false */
let isLearning = true;

true && false;  /* - : bool = false;  Logical and */
true || true;   /* - : bool = true;   Logical or  */
!true;          /* - : bool = false;  Logical not */

/* Greater than `>`, or greater than or equal to `>=` */
'a' > 'b'; /* - bool : false */

/* Less than `<`, or less than or equal to `<=` */
1 < 5; /* - : bool = true */

/* Structural equal */
"hello" == "hello"; /* - : bool = true */

/* Referential equal */
"hello" === "hello"; /* - : bool = false */
/* - This is false because they are two different "hello" string literals */

/* Structural unequal */
lastLetter != 'a'; /* -: bool = true */

/* Referential unequal */
lastLetter !== lastLetter; /* - : bool = false */

/* > Integer */
/* Perform math operations on integers */

1 + 1;          /* - : int = 2  */
25 - 11;        /* - : int = 11 */
5 * 2 * 3;      /* - : int = 30 */
8 / 2;          /* - : int = 4  */

/* > Float */
/* Operators on floats have a dot after them */

1.1 +. 1.5;     /* - : float = 2.6  */
18.0 -. 24.5;   /* - : float = -6.5 */
2.5 *. 2.0;     /* - : float = 5.   */
16.0 /. 4.0;    /* - : float = 4.   */

/* > Tuple
 * Tuples have the following attributes
  - immutable
  - ordered
  - fix-sized at creation time
  - heterogeneous (can contain different types of values)
 A tuple is 2 or more values */

let teamMember = ("John", 25);

/* Type annotation matches the values */
let position2d: (float, float) = (9.0, 12.0);

/* Pattern matching is a great tool to retrieve just the values you care about
   If we only want the y value, let's use `_` to ignore the value */
let (_, y) = position2d;
y +. 1.0; /* - : float = 13. */

/* > Record */

/* A record has to have an explicit type */
type trainJourney = {
  destination: string,
  capacity: int,
  averageSpeed: float,
};

/* Once the type is declared, Reason can infer it whenever it comes up */
let firstTrip = {destination: "London", capacity: 45, averageSpeed: 120.0};

/* Access a property using dot notation */
let maxPassengers = firstTrip.capacity;

/* If you define the record type in a different file, you have to reference the
   filename, if trainJourney was in a file called Trips.re */
let secondTrip: Trips.trainJourney = {
  destination: "Paris",
  capacity: 50,
  averageSpeed: 150.0,
};

/* Records are immutable by default */
/* But the contents of a record can be copied using the spread operator */
let newTrip = {...secondTrip, averageSpeed: 120.0};

/* A record property can be mutated explicitly with the `mutable` keyword */
type breakfastCereal = {
  name: string,
  mutable amount: int,
};

let tastyMuesli = {name: "Tasty Muesli TM", amount: 500};

tastyMuesli.amount = 200;
/* - tastyMuesli now has an amount of 200 */

/* Punning is used to avoid redundant typing */
let name = "Just As Good Muesli";
let justAsGoodMuesli = {name, amount: 500};
/* - justAsGoodMuesli.name is now "Just As Good Muesli", it's equivalent
   to { name: name, amount: 500 } */

/* > Variant
   Mutually exclusive states can be expressed with variants */

type authType =
  | GitHub
  | Facebook
  | Google
  | Password;
/* - The constructors must be capitalized like so */
/* - Like records, variants should be named if declared in a different file */

let userPreferredAuth = GitHub;

/* Variants work great with a switch statement */
let loginMessage =
  switch (userPreferredAuth) {
  | GitHub => "Login with GitHub credentials."
  | Facebook => "Login with your Facebook account."
  | Google => "Login with your Google account"
  | Password => "Login with email and password."
  };

/* > Option
   An option can be None or Some('a) where 'a is the type */

let userId = Some(23);

/* A switch handles the two cases */
let alertMessage =
  switch (userId) {
  | Some(id) => "Welcome, your ID is" ++ string_of_int(id)
  | None => "You don't have an account!"
  };
/* - Missing a case, `None` or `Some`, would cause an error */

/* > List
  * Lists have the following attributes
   - immutable
   - ordered
   - fast at prepending items
   - fast at splitting

  * Lists in Reason are linked lists
 */

/* A list is declared with square brackets */
let userIds = [1, 4, 8];

/* The type can be explicitly set with list('a) where 'a is the type */
type idList = list(int);
type attendanceList = list(string);

/* Lists are immutable */
/* But the contents of a list can be copied using the spread operator */
let newUserIds = [101, 102, ...userIds];

/* > Array
 * Arrays have the following attributes
  - mutable
  - fast at random access & updates */

/* An array is declared with `[|` and ends with `|]` */
let languages = [|"Reason", "JavaScript", "OCaml"|];

/*----------------------------------------------
 * Function
 *----------------------------------------------
 */

/* Reason functions use the arrow syntax, the expression is returned */
let signUpToNewsletter = email => "Thanks for signing up " ++ email;

/* Call a function like this */
signUpToNewsletter("hello@reason.org");

/* For longer functions, use a block */
let getEmailPrefs = email => {
  let message = "Update settings for " ++ email;
  let prefs = ["Weekly News", "Daily Notifications"];

  (message, prefs);
};
/* - the final tuple is implicitly returned */

/* > Labeled Arguments */

/* Arguments can be labeled with the ~ symbol */
let moveTo = (~x, ~y) => {/* Move to x,y */};

moveTo(~x=7.0, ~y=3.5);

/* Labeled arguments can also have a name used within the function */
let getMessage = (~message as msg) => "==" ++ msg ++ "==";

getMessage(~message="You have a message!");
/* - The caller specifies ~message but internally the function can make use */

/* The following function also has explicit types declared */
let showDialog = (~message: string): unit => {
  () /* Show the dialog */;
};
/* - The return type is `unit`, this is a special type that is equivalent to
   specifying that this function doesn't return a value
   the `unit` type can also be represented as `()` */

/* > Currying
   Functions can be curried and are partially called, allowing for easy reuse */

let div = (denom, numr) => numr / denom;
let divBySix = div(6);
let divByTwo = div(2);

div(3, 24);     /* - : int = 8  */
divBySix(128);  /* - : int = 21 */
divByTwo(10);   /* - : int = 5  */

/* > Optional Labeled Arguments */

/* Use `=?` syntax for optional labeled arguments */
let greetPerson = (~name, ~greeting=?, ()) => {
  switch (greeting) {
  | Some(greet) => greet ++ " " ++ name
  | None => "Hi " ++ name
  };
};
/* - The third argument, `unit` or `()` is required because if we omitted it,
   the function would be curried so greetPerson(~name="Kate") would create
   a partial function, to fix this we add `unit` when we declare and call it */

/* Call greetPerson without the optional labeled argument */
greetPerson(~name="Kate", ());

/* Call greetPerson with all arguments */
greetPerson(~name="Marco", ~greeting="How are you today,");

/* > Pipe */
/* Functions can be called with the pipeline operator */

/* Use `->` to pass in the first argument (pipe-first) */
3->div(24);     /* - : int = 8 */
/* - This is equivalent to div(3, 24); */

36->divBySix;   /* - : int = 6 */
/* - This is equivalent to divBySix(36); */

/* Use `|>` to pass in the last argument (pipe-last) */
24 |> div(3);   /* - : int = 8 */
/* - This is equivalent to div(3, 24); */

36 |> divBySix; /* - : int = 6 */
/* - This is equivalent to divBySix(36); */

/* Pipes make it easier to chain code together */
let addOne = a => a + 1;
let divByTwo = a => a / 2;
let multByThree = a => a * 3;

let pipedValue = 3->addOne->divByTwo->multByThree; /* - : int = 6 */

/*----------------------------------------------
 * Control Flow & Pattern Matching
 *----------------------------------------------
 */

/* > If-else */
/* In Reason, `If` is an expression when evaluate will return the result */

/* greeting will be "Good morning!" */
let greeting = if (true) {"Good morning!"} else {"Hello!"};

/* Without an else branch the expression will return `unit` or `()` */
if (false) {
  showDialog(~message="Are you sure you want to leave?");
};
/* - Because the result will be of type `unit`, both return types should be of
   the same type if you want to assign the result. */

/* > Destructuring */
/* Extract properties from data structures easily */

let aTuple = ("Teacher", 101);

/* We can extract the values of a tuple */
let (name, classNum) = aTuple;

/* The properties of a record can be extracted too */
type person = {
  firstName: string,
  age: int,
};
let bjorn = {firstName: "Bjorn", age: 28};

/* The variable names have to match with the record property names */
let {firstName, age} = bjorn;

/* But we can rename them like so */
let {firstName: bName, age: bAge} = bjorn;

let {firstName: cName, age: _} = bjorn;

/* > Switch
   Pattern matching with switches is an important tool in Reason
   It can be used in combination with destructuring for an expressive and
   concise tool */

/* Lets take a simple list */
let firstNames = ["James", "Jean", "Geoff"];

/* We can pattern match on the names for each case we want to handle */
switch (firstNames) {
| [] => "No names"
| [first] => "Only " ++ first
| [first, second] => "A couple of names " ++ first ++ "," ++ second
| [first, second, third] =>
  "Three names, " ++ first ++ ", " ++ second ++ ", " ++ third
| _ => "Lots of names"
};
/* - The `_` is a catch all at the end, it signifies that we don't care what
   the value is so it will match every other case */

/* > When clause */

let isJohn = a => a == "John";
let maybeName = Some("John");

/* When can add more complex logic to a simple switch */
let aGreeting =
  switch (maybeName) {
  | Some(name) when isJohn(name) => "Hi John! How's it going?"
  | Some(name) => "Hi " ++ name ++ ", welcome."
  | None => "No one to greet."
  };

/* > Exception */

/* Define a custom exception */
exception Under_Age;

/* Raise an exception within a function */
let driveToTown = (driver: person) =>
  if (driver.age >= 15) {
    "We're in town";
  } else {
    raise(Under_Age);
  };

let evan = {firstName: "Evan", age: 14};

/* Pattern match on the exception Under_Age */
switch (driveToTown(evan)) {
| status => print_endline(status)
| exception Under_Age =>
  print_endline(evan.firstName ++ " is too young to drive!")
};

/* Alternatively, a try block can be used */
/* - With Reason exceptions can be avoided with optionals and are seldom used */
let messageToEvan =
  try (driveToTown(evan)) {
  | Under_Age => evan.firstName ++ " is too young to drive!"
  };

/*----------------------------------------------
 * Object
 *----------------------------------------------
 * Objects are similar to Record types but aren't as rigid
 * An object resembles a class
 */

/* An object may be typed like a record but contains a dot */
type surfaceComputer = {
  .
  color: string,
  capacity: int,
};
/* - A single dot signifies a closed object, an object that uses this type
   must have the exact shape */

let surfaceBook: surfaceComputer = {pub color = "blue"; pub capacity = 512};

/* But an object doesn't require a type */
let house = {
  /* A private property */
  val temp = ref(18.0);
  /* Public properties */
  pub temperature = temp;
  /* A private method only accessible from within house */
  pri setThermostat = v => temp := v;
  /* A public method that calls the private setThermostat method */
  pub arriveHome = () => this#setThermostat(22.0)
};

house#temperature; /* - : float = 18. */
house#arriveHome();
house#temperature; /* - : float = 22. */

/*----------------------------------------------
 * Module
 *----------------------------------------------
 * Modules are used to organize your code and provide namespacing.
 * Each file is a module by default
 */

/* Create a module */
module Staff = {
  type role =
    | Delivery
    | Sales
    | Other;
  type member = {
    name: string,
    role,
  };

  let getRoleDirectionMessage = staff =>
    switch (staff.role) {
    | Delivery => "Deliver it like you mean it!"
    | Sales => "Sell it like only you can!"
    | Other => "You're an important part of the team!"
    };
};

/* A module can be accessed with dot notation */
let newEmployee: Staff.member = {name: "Laura", role: Staff.Delivery};

/* Using the module name can be tiresome so the module's contents can be opened
   into the current scope with `open` */
open Staff;

let otherNewEmployee: member = {name: "Fred", role: Other};

/* A module can be extended using the `include` keyword, include copies
   the contents of the module into the scope of the new module */
module SpecializedStaff = {
  include Staff;

  /* `member` is included so there's no need to reference it explicitly */
  let ceo: member = {name: "Reggie", role: Other};

  let getMeetingTime = staff =>
    switch (staff) {
    | Other => 11_15 /* - : int = 1115; Underscores are for formatting only  */
    | _ => 9_30
    };
};
```

## Further Reading

- [Official Reason Docs](https://reasonml.github.io/docs/en/what-and-why)
- [Official BuckleScript Docs](https://bucklescript.github.io/docs/en/what-why)
- [Try Reason](https://reasonml.github.io/en/try)
- [Get Started with Reason by Nik Graf](https://egghead.io/courses/get-started-with-reason)