diff --git a/reason.html.markdown b/reason.html.markdown new file mode 100644 index 00000000..4b909bdf --- /dev/null +++ b/reason.html.markdown @@ -0,0 +1,545 @@ +--- +name: Reason +category: language +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. + +```javascript +/* Comments start with slash-star, and end with slash-star */ + +/*---------------------------------------------- + * 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 it's 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 tp 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 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.firstTrip = { + 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 */ +let messageToEvan = + try (driveToTown(evan)) { + | Under_Age => evan.firstName ++ " is too young to drive!" + }; +/* - With Reason exceptions can be avoided with optionals and are seldom used */ + +/*---------------------------------------------- + * 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) \ No newline at end of file