--- language: ReScript filename: rescript.res contributors: - ["Seth Corker", "https://sethcorker.com"] - ["Danny Yang", "https://yangdanny97.github.io"] --- 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. ```javascript /* Comments start with slash-star, and end with star-slash */ // Single line comments start with double 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, ReScript 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 ReScript 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 `.contents` */ let copyOfMyMutableNumber = myMutableNumber.contents /* 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 " /* Use ` for unicode */ let world = `🌍` /* The ` annotation is also used for string interpolation */ let helloWorld = `hello, ${world}` /* Bindings must be converted to strings */ let age = 10 let ageMsg = `I am ${Js.Int.toString(age)} years old` /* Using `j` annotation in interpolation will implicitly convert bindings to strings */ let ageMsg = j`I am $age years old` /* 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, ReScript 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 ReScript are linked lists */ /* A list is declared with the `list` keyword and initialized with values wrapped in curly braces */ let userIds = list{1, 4, 8} /* The type can be explicitly set with list<'a> where 'a is the type */ type idList = list type attendanceList = list /* Lists are immutable */ /* But you can create a new list with additional prepended elements by using the spread operator on an existing list */ let newUserIds = list{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 = ["ReScript", "JavaScript", "OCaml"] /*---------------------------------------------- * Function *---------------------------------------------- */ /* ReScript functions use the arrow syntax, the expression is returned */ let signUpToNewsletter = email => "Thanks for signing up " ++ email /* Call a function like this */ signUpToNewsletter("hello@ReScript.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) */ /* 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 ReScript, `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 ReScript 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 ReScript exceptions can be avoided with optionals and are seldom used */ let messageToEvan = try { driveToTown(evan) } catch { | Under_Age => evan.firstName ++ " is too young to drive!" } /*---------------------------------------------- * Object *---------------------------------------------- * Objects are similar to Record types, but are less rigid */ /* An object may be typed like a record but the property names are quoted */ type surfaceComputer = { "color": string, "capacity": int, } let surfaceBook: surfaceComputer = { "color": "blue", "capacity": 512 } /* Objects don't require types */ let hamster = { "color": "brown", "age": 2 } /* Object typing is structural, so you can have functions that accept any object with the required fields */ let getAge = animal => animal["age"] getAge(hamster) getAge({ "name": "Fido", "color": "silver", "age": 3 }) getAge({ "age": 5 }) /*---------------------------------------------- * 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 ReScript Docs](https://rescript-lang.org/) - [Try ReScript - Online Playground](https://rescript-lang.org/try)