ReScript is a robustly typed language that compiles to efficient and human-readable JavaScript. It comes with a lightning fast compiler toolchain that scales to any codebase size. ReScript is descended from OCaml and Reason, with nice features like type inference and pattern matching, along with beginner-friendly syntax and a focus on the JavaScript ecosystem.
1/* Comments start with slash-star, and end with star-slash */
2// Single line comments start with double slash
3
4/*----------------------------------------------
5 * Variable and function declaration
6 *----------------------------------------------
7 * Variables and functions use the let keyword and end with a semi-colon
8 * `let` bindings are immutable
9 */
10
11let x = 5
12/* - Notice we didn't add a type, ReScript will infer x is an int */
13
14/* A function like this, take two arguments and add them together */
15let add = (a, b) => a + b
16/* - This doesn't need a type annotation either! */
17
18/*----------------------------------------------
19 * Type annotation
20 *----------------------------------------------
21 * Types don't need to be explicitly annotated in most cases but when you need
22 * to, you can add the type after the name
23 */
24
25/* A type can be explicitly written like so */
26let x: int = 5
27
28/* The add function from before could be explicitly annotated too */
29let add2 = (a: int, b: int): int => a + b
30
31/* A type can be aliased using the type keyword */
32type companyId = int
33let myId: companyId = 101
34
35/* Mutation is not encouraged in ReScript but it's there if you need it
36 If you need to mutate a let binding, the value must be wrapped in a `ref()`*/
37let myMutableNumber = ref(120)
38
39/* To access the value (and not the ref container), use `.contents` */
40let copyOfMyMutableNumber = myMutableNumber.contents
41
42/* To assign a new value, use the `:=` operator */
43myMutableNumber := 240
44
45/*----------------------------------------------
46 * Basic types and operators
47 *----------------------------------------------
48 */
49
50/* > String */
51
52/* Use double quotes for strings */
53let greeting = "Hello world!"
54
55/* A string can span multiple lines */
56let aLongerGreeting = "Look at me,
57I'm a multi-line string
58"
59
60/* Use ` for unicode */
61let world = `🌍`
62
63/* The ` annotation is also used for string interpolation */
64let helloWorld = `hello, ${world}`
65/* Bindings must be converted to strings */
66let age = 10
67let ageMsg = `I am ${Int.toString(age)} years old`
68
69
70/* Concatenate strings with ++ */
71let name = "John " ++ "Wayne"
72let emailSubject = "Hi " ++ name ++ ", you're a valued customer"
73
74/* > Char */
75
76/* Use a single character for the char type */
77let lastLetter = 'z'
78/* - Char doesn't support Unicode or UTF-8 */
79
80/* > Boolean */
81
82/* A boolean can be either true or false */
83let isLearning = true
84
85true && false /* - : bool = false Logical and */
86true || true /* - : bool = true Logical or */
87!true /* - : bool = false Logical not */
88
89/* Greater than `>`, or greater than or equal to `>=` */
90'a' > 'b' /* - bool : false */
91
92/* Less than `<`, or less than or equal to `<=` */
931 < 5 /* - : bool = true */
94
95/* Structural equal */
96"hello" == "hello" /* - : bool = true */
97
98/* Referential equal */
99"hello" === "hello" /* - : bool = false */
100/* - This is false because they are two different "hello" string literals */
101
102/* Structural unequal */
103lastLetter != 'a' /* -: bool = true */
104
105/* Referential unequal */
106lastLetter !== lastLetter /* - : bool = false */
107
108/* > Integer */
109/* Perform math operations on integers */
110
1111 + 1 /* - : int = 2 */
11225 - 11 /* - : int = 11 */
1135 * 2 * 3 /* - : int = 30 */
1148 / 2 /* - : int = 4 */
115
116/* > Float */
117/* Operators on floats have a dot after them */
118
1191.1 +. 1.5 /* - : float = 2.6 */
12018.0 -. 24.5 /* - : float = -6.5 */
1212.5 *. 2.0 /* - : float = 5. */
12216.0 /. 4.0 /* - : float = 4. */
123
124/* > Tuple
125 * Tuples have the following attributes
126 - immutable
127 - ordered
128 - fix-sized at creation time
129 - heterogeneous (can contain different types of values)
130 A tuple is 2 or more values */
131
132let teamMember = ("John", 25)
133
134/* Type annotation matches the values */
135let position2d: (float, float) = (9.0, 12.0)
136
137/* Pattern matching is a great tool to retrieve just the values you care about
138 If we only want the y value, let's use `_` to ignore the value */
139let (_, y) = position2d
140y +. 1.0 /* - : float = 13. */
141
142/* > Record */
143
144/* A record has to have an explicit type */
145type trainJourney = {
146 destination: string,
147 capacity: int,
148 averageSpeed: float,
149}
150
151/* Once the type is declared, ReScript can infer it whenever it comes up */
152let firstTrip = {destination: "London", capacity: 45, averageSpeed: 120.0}
153
154/* Access a property using dot notation */
155let maxPassengers = firstTrip.capacity
156
157/* If you define the record type in a different file, you have to reference the
158 filename, if trainJourney was in a file called Trips.res */
159let secondTrip: Trips.trainJourney = {
160 destination: "Paris",
161 capacity: 50,
162 averageSpeed: 150.0,
163}
164
165/* Records are immutable by default */
166/* But the contents of a record can be copied using the spread operator */
167let newTrip = {...secondTrip, averageSpeed: 120.0}
168
169/* A record property can be mutated explicitly with the `mutable` keyword */
170type breakfastCereal = {
171 name: string,
172 mutable amount: int,
173}
174
175let tastyMuesli = {name: "Tasty Muesli TM", amount: 500}
176
177tastyMuesli.amount = 200
178/* - tastyMuesli now has an amount of 200 */
179
180/* Punning is used to avoid redundant typing */
181let name = "Just As Good Muesli"
182let justAsGoodMuesli = {name, amount: 500}
183/* - justAsGoodMuesli.name is now "Just As Good Muesli", it's equivalent
184 to { name: name, amount: 500 } */
185
186/* > Variant
187 Mutually exclusive states can be expressed with variants */
188
189type authType =
190 | GitHub
191 | Facebook
192 | Google
193 | Password
194/* - The constructors must be capitalized like so */
195/* - Like records, variants should be named if declared in a different file */
196
197let userPreferredAuth = GitHub
198
199/* Variants work great with a switch statement */
200let loginMessage =
201 switch (userPreferredAuth) {
202 | GitHub => "Login with GitHub credentials."
203 | Facebook => "Login with your Facebook account."
204 | Google => "Login with your Google account"
205 | Password => "Login with email and password."
206 }
207
208/* > Option
209 An option can be None or Some('a) where 'a is the type */
210
211let userId = Some(23)
212
213/* A switch handles the two cases */
214let alertMessage =
215 switch (userId) {
216 | Some(id) => "Welcome, your ID is" ++ string_of_int(id)
217 | None => "You don't have an account!"
218 }
219/* - Missing a case, `None` or `Some`, would cause an error */
220
221/* > List
222 * Lists have the following attributes
223 - immutable
224 - ordered
225 - fast at prepending items
226 - fast at splitting
227
228 * Lists in ReScript are linked lists
229 */
230
231/* A list is declared with the `list` keyword and initialized with values wrapped in curly braces */
232let userIds = list{1, 4, 8}
233
234/* The type can be explicitly set with list<'a> where 'a is the type */
235type idList = list<int>
236type attendanceList = list<string>
237
238/* Lists are immutable */
239/* But you can create a new list with additional prepended elements by using the spread operator on an existing list */
240let newUserIds = list{101, 102, ...userIds}
241
242/* > Array
243 * Arrays have the following attributes
244 - mutable
245 - fast at random access & updates */
246
247/* An array is declared with `[` and ends with `]` */
248let languages = ["ReScript", "JavaScript", "OCaml"]
249
250/*----------------------------------------------
251 * Function
252 *----------------------------------------------
253 */
254
255/* ReScript functions use the arrow syntax, the expression is returned */
256let signUpToNewsletter = email => "Thanks for signing up " ++ email
257
258/* Call a function like this */
259signUpToNewsletter("hello@ReScript.org")
260
261/* For longer functions, use a block */
262let getEmailPrefs = email => {
263 let message = "Update settings for " ++ email
264 let prefs = ["Weekly News", "Daily Notifications"]
265
266 (message, prefs)
267}
268/* - the final tuple is implicitly returned */
269
270/* > Labeled Arguments */
271
272/* Arguments can be labeled with the ~ symbol */
273let moveTo = (~x, ~y) => {
274 /* Move to x,y */
275 ()
276}
277
278moveTo(~x=7.0, ~y=3.5)
279
280/* Labeled arguments can also have a name used within the function */
281let getMessage = (~message as msg) => "==" ++ msg ++ "=="
282
283getMessage(~message="You have a message!")
284/* - The caller specifies ~message but internally the function can make use */
285
286/* The following function also has explicit types declared */
287let showDialog = (~message: string): unit => {
288 () /* Show the dialog */
289}
290/* - The return type is `unit`, this is a special type that is equivalent to
291 specifying that this function doesn't return a value
292 the `unit` type can also be represented as `()` */
293
294/* > Currying
295 Functions can be curried and are partially called, allowing for easy reuse
296 The remaining arguments are represented with ... */
297
298let div = (denom, numr) => numr / denom
299let divBySix = div(6, ...)
300let divByTwo = div(2, ...)
301
302div(3, 24) /* - : int = 8 */
303divBySix(128) /* - : int = 21 */
304divByTwo(10) /* - : int = 5 */
305
306/* > Optional Labeled Arguments */
307
308/* Use `=?` syntax for optional labeled arguments */
309let greetPerson = (~name, ~greeting=?) => {
310 switch (greeting) {
311 | Some(greet) => greet ++ " " ++ name
312 | None => "Hi " ++ name
313 }
314}
315/* - The third argument, `unit` or `()` is required because if we omitted it,
316 the function would be curried so greetPerson(~name="Kate") would create
317 a partial function, to fix this we add `unit` when we declare and call it */
318
319/* Call greetPerson without the optional labeled argument */
320greetPerson(~name="Kate")
321
322/* Call greetPerson with all arguments */
323greetPerson(~name="Marco", ~greeting="How are you today,")
324
325/* > Pipe */
326/* Functions can be called with the pipeline operator */
327
328/* Use `->` to pass in the first argument (pipe-first) */
3293->div(24) /* - : int = 8 */
330/* - This is equivalent to div(3, 24) */
331
33236->divBySix /* - : int = 6 */
333/* - This is equivalent to divBySix(36) */
334
335/* Pipes make it easier to chain code together */
336let addOne = a => a + 1
337let divByTwo = a => a / 2
338let multByThree = a => a * 3
339
340let pipedValue = 3->addOne->divByTwo->multByThree /* - : int = 6 */
341
342/*----------------------------------------------
343 * Control Flow & Pattern Matching
344 *----------------------------------------------
345 */
346
347/* > If-else */
348/* In ReScript, `If` is an expression when evaluate will return the result */
349
350/* greeting will be "Good morning!" */
351let greeting = if (true) {"Good morning!"} else {"Hello!"}
352
353/* Without an else branch the expression will return `unit` or `()` */
354if (false) {
355 showDialog(~message="Are you sure you want to leave?")
356}
357/* - Because the result will be of type `unit`, both return types should be of
358 the same type if you want to assign the result. */
359
360/* > Destructuring */
361/* Extract properties from data structures easily */
362
363let aTuple = ("Teacher", 101)
364
365/* We can extract the values of a tuple */
366let (name, classNum) = aTuple
367
368/* The properties of a record can be extracted too */
369type person = {
370 firstName: string,
371 age: int,
372}
373let bjorn = {firstName: "Bjorn", age: 28}
374
375/* The variable names have to match with the record property names */
376let {firstName, age} = bjorn
377
378/* But we can rename them like so */
379let {firstName: bName, age: bAge} = bjorn
380
381let {firstName: cName, age: _} = bjorn
382
383/* > Switch
384 Pattern matching with switches is an important tool in ReScript
385 It can be used in combination with destructuring for an expressive and
386 concise tool */
387
388/* Lets take a simple list */
389let firstNames = ["James", "Jean", "Geoff"]
390
391/* We can pattern match on the names for each case we want to handle */
392switch (firstNames) {
393| [] => "No names"
394| [first] => "Only " ++ first
395| [first, second] => "A couple of names " ++ first ++ "," ++ second
396| [first, second, third] =>
397 "Three names, " ++ first ++ ", " ++ second ++ ", " ++ third
398| _ => "Lots of names"
399}
400/* - The `_` is a catch all at the end, it signifies that we don't care what
401 the value is so it will match every other case */
402
403/* > When clause */
404
405let isJohn = a => a == "John"
406let maybeName = Some("John")
407
408/* When can add more complex logic to a simple switch */
409let aGreeting =
410 switch (maybeName) {
411 | Some(name) when isJohn(name) => "Hi John! How's it going?"
412 | Some(name) => "Hi " ++ name ++ ", welcome."
413 | None => "No one to greet."
414 }
415
416/* > Exception */
417
418/* Define a custom exception */
419exception Under_Age
420
421/* Raise an exception within a function */
422let driveToTown = (driver: person) =>
423 if (driver.age >= 15) {
424 "We're in town"
425 } else {
426 raise(Under_Age)
427 }
428
429let evan = {firstName: "Evan", age: 14}
430
431/* Pattern match on the exception Under_Age */
432switch (driveToTown(evan)) {
433| status => print_endline(status)
434| exception Under_Age =>
435 print_endline(evan.firstName ++ " is too young to drive!")
436}
437
438/* Alternatively, a try block can be used */
439/* - With ReScript exceptions can be avoided with optionals and are seldom used */
440let messageToEvan =
441 try {
442 driveToTown(evan)
443 } catch {
444 | Under_Age => evan.firstName ++ " is too young to drive!"
445 }
446
447/*----------------------------------------------
448 * Object
449 *----------------------------------------------
450 * Objects are similar to Record types, but are less rigid
451 */
452
453/* An object may be typed like a record but the property names are quoted */
454type surfaceComputer = {
455 "color": string,
456 "capacity": int,
457}
458let surfaceBook: surfaceComputer = { "color": "blue", "capacity": 512 }
459
460/* Objects don't require types */
461let hamster = { "color": "brown", "age": 2 }
462
463/* Object typing is structural, so you can have functions that accept any object with the required fields */
464let getAge = animal => animal["age"]
465getAge(hamster)
466getAge({ "name": "Fido", "color": "silver", "age": 3 })
467getAge({ "age": 5 })
468
469/*----------------------------------------------
470 * Module
471 *----------------------------------------------
472 * Modules are used to organize your code and provide namespacing.
473 * Each file is a module by default
474 */
475
476/* Create a module */
477module Staff = {
478 type role =
479 | Delivery
480 | Sales
481 | Other
482 type member = {
483 name: string,
484 role,
485 }
486
487 let getRoleDirectionMessage = staff =>
488 switch (staff.role) {
489 | Delivery => "Deliver it like you mean it!"
490 | Sales => "Sell it like only you can!"
491 | Other => "You're an important part of the team!"
492 }
493}
494
495/* A module can be accessed with dot notation */
496let newEmployee: Staff.member = {name: "Laura", role: Staff.Delivery}
497
498/* Using the module name can be tiresome so the module's contents can be opened
499 into the current scope with `open` */
500open Staff
501
502let otherNewEmployee: member = {name: "Fred", role: Other}
503
504/* A module can be extended using the `include` keyword, include copies
505 the contents of the module into the scope of the new module */
506module SpecializedStaff = {
507 include Staff
508
509 /* `member` is included so there's no need to reference it explicitly */
510 let ceo: member = {name: "Reggie", role: Other}
511
512 let getMeetingTime = staff =>
513 switch (staff) {
514 | Other => 11_15 /* - : int = 1115 Underscores are for formatting only */
515 | _ => 9_30
516 }
517}