ReScript

Личный сайт Go-разработчика из Казани

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}

Further Reading