From fadb281798a95bd87c8a3a1b443272af951103df Mon Sep 17 00:00:00 2001 From: Danny Yang Date: Mon, 13 May 2024 03:37:11 -0400 Subject: [PATCH] [rescript/en] add ReScript (#4394) --- rescript.html.markdown | 536 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 rescript.html.markdown diff --git a/rescript.html.markdown b/rescript.html.markdown new file mode 100644 index 00000000..b633332c --- /dev/null +++ b/rescript.html.markdown @@ -0,0 +1,536 @@ +--- +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)