This tutorial intends to provide a comprehensive, but relatively short introduction to Reason. is a programming language built on top of . It provides functional and object-oriented features with type-safety and focus on performance. It was created at . Its syntax is similar to . The intention is to make interoperation with JavaScript and adoption by JavaScript programmers easier. Reason can access both JavaScript and OCaml ecosystems. OCaml was created in 1996. It is a functional programming language with infered types. Reason OCaml Facebook JavaScript The Reason website contains an . It allows to play with the language and see how the generated JavaScript looks like. It can also convert from OCaml to Reason. online playground Why In JavaScript types annotation, linting or unified formatting is provided as an external depenency such as Flow, TypeScript, ESLint or Prettier. Reason provides these features out-of-the-box. This makes the development process more streamlined and convenient. Reason offers support for React with . It also supports JSX syntax (HTML-like syntax used in React) out-of-the-box. ReasonReact Reason has also the ability to generate native binaries. The generated code is performant. There is no VM overhead. It provides one binary which facilites deployment process. How it works Reason is compiled to OCaml’s abstract syntax tree. This makes Reason a transpiler. OCaml cannot be run directly in the browser. The AST can be converted to various targets. BuckleScript can be used to compile that AST to JavaScript. It also provides the interop between OCaml and JavaScript ecosystems. BuckleScript is extremly fast and generates readable JavaScript. It also provides the Foreign Function Interface (FFI) to allow interoperability with the JavaScript existing libraries. Check . BuckleScript is used at Facebook by the Messanger team and at Google by WebAssembly spec interpreter. Check the here. BuckleScript was created by . BuckleScript benchmarks Bucklescript demo Hongbo Zhang Hello Reason We will use BuckleScript to generate a Reason project. The tool provides ready-to-use project templates known as . themes Let’s start by installing globally: bs-platform npm install -g bs-platform We can now use binary provided by to generate a project scaffold. We will use template to start with the most basic Reason project structure. bsb bs-platform basic-reason bsb -init reason-1 -theme basic-reason Making directory reason-1 Symlink bs-platform in /Users/zaiste/code/reason-1 Here’s the Reason directory structure generated from template via BuckleScript: basic-reason . ├── README.md ├── bsconfig.json ├── lib ├── node_modules ├── package.json └── src └── Demo.re contains BuckleScript configuration for a Reason project. It allows to specify files to compile via , BuckleScript dependencies via , additional flags for the compiler and more. bsconfig.json sources bs-dependencies Next step is to build the project. This will take Reason code and pass it through BuckleScript to generate JavaScript. By default the compiler will target Node.js. npm run build > reason-1@0.1.0 build /Users/zaiste/code/reason-1 > bsb -make-world ninja: Entering directory `lib/bs' [3/3] Building src/Demo.mlast.d [1/1] Building src/Demo-MyFirstReasonml.cmj Finally we can run our application by using on the files generated by BuckleScript. node node src/Demo.bs.js Hello, BuckleScript and Reason! Syntax 101 In this section, I will go over the syntax elements that I found the peculiar, new or just different. Modules In Reason files are modules. There are no or statements as in JavaScript or similar programming languages. The module definitions must be prefixed with the module name to work externally. This feature comes from OCaml. As a result you can freely move the module files in the filesystem without the need to modify the code. require import Functions Functions are defined using and . let => let greet = name => Js.log("Hello, " ++ name "!"); greet("Zaiste"); The operator is used to concatenate strings. ++ Function’s input arguments can be labelled. This makes the function invocation more explicit: passed-in values no longer need to follow the arguments order from the function definition. Prefixing the argument name with makes it labelled. ~ let greet = (~name, ~location) => Js.log("Hello, " ++ name "! You're in " ++ location); greet(~location="Vienna", ~name="Zaiste") Data Structures Variants A is a data structure that holds a value from a fixed set of values. This is also known as tagged or disjoint union or algebraic data types. Each case in a variant must be capitalised. Optionally, it can receive parameters. variant type animal = | Dog | Cat | Bird; Records This is a record let p = { name: "Zaiste", age: 13 } Records need explicit type definition. type person = { name: string, age: int }; In the scope of a module, the type will be inherited: the binding will be recognized as type. Outside of a module, you can reference the type by just prefixing it with file name. p person let p: Person.person = { name: 'Sean', age: 12 }; There is a convention to create a module per type and name the type as to avoid the repetition i.e. instead of . t Person.t Person.person Async Programming & Promise There is a built-in support for Promises via BuckleScript, provided as module. Here's an example of making an API call using : JS.Promise Fetch API Js.Promise.( Fetch.fetch(endpoint) |> then_(Fetch.Response.json) |> then_(json => doSomethingOnResponse(json) |> resolve) ) You need to use as is reserved word in OCaml. then_ then Pattern Matching Pattern matching is a dispatch mechanism based on the shape of the provided value. In Reason, pattern matching is implemented with statement. It can be used with a variant type or as destructuring mechanism. switch switch pet { | Dog => "woof" | Cat => "meow" | Bird => "chirp" }; We can use pattern matching for list destructuring: let numbers = ["1", "2", "3", "4"]; switch numbers { | [] => "Empty" | [n1] => "Only one number: " ++ n1 | [n1, n2] => "Only two numbers" | [n1, _, n3, ...rest] => "At least three numbers" }; Or, we can use it for record destructuring let project = { name: "Huncwot", size: 101101, forks: 42, deps: [{name: "axios"}, {name: "sqlite3"}]} switch project { | {name: "Huncwot", deps} => "Matching by `name`" | {location, years: [{name: "axios"}, ...rest]} => "Matching by one of `deps`" | project => "Any other situation" } Optional values is a built-in variant in Reason describing "nullable" values: option() type option('a) = None | Some('a); Varia means "nothing" unit is a signature of a function that doesn't accept any input parameters and doesn't return any values; mostly used for callback functions unit => unit React in Reason Hello ReactReason is a Reason built-in feature for creating React applications. ReasonReact Let’s create a ReasonReact project using BuckleScript and its template. react bsb -init reasonreact-1 -theme react This method is recommended by Reason team for scaffolding ReasonReact projects. It is also possible to use yarn with template for a more complete starting point. reason-scripts ReasonReact provides two types of components: and . Contrary to stateless components, reducer components are stateful providing Redux-like reducers. statelessComponent reducerComponent let s = ReasonReact.string let component = ReasonReact.statelessComponent("App"); let make = (~message, _children) => { ...component, render: _self => <h1 class="header">(s(message))</h1> }; As described earlier designates a labelled argument to freely order function's input parameters. in the binding name tells the compiler that the argument isn't used in the body of that function. The spread operator ( ) alongside of means that we extend an existing component. In this example we also overwrite the function. ~ _ ... component render JSX in Reason is more strict than in React: we need to explicitly wrap strings with . For convenience, I've created a shorter binding called to use it conveniently inside JSX block. ReasonReact.string() s Building non-trivial ReactReason app Let’s build a ReactReason application that goes beyond displaying predefined data. We will create a GitHub viewer for trending repositories. The intention is to showcase how to integrate with an external API, how to manage state and how to use React’s lifecycle methods methods. For the purpose of this example we will use to bootstrap our Reason project. reason-scripts yarn create react-app reasonreact-github --scripts-version reason-scripts Install dependencies: cd reasonreact-github yarn Start it with: yarn start is the central concept in this application. Let’s start by defining a type to describe that entity. We will put it inside a separate module called . Repository Repo type t = { name: string, size: int, forks: int }; From now on we can refer to this type with from any Reason file in our application without the need of requiring it. Repo.t Managing State We’ve already seen a stateless component. Now let’s create a component that has state. In our context we will be using component managing a list of trending repositories fetched from GitHub's API. RepoList Let’s start by defining the type for the state managed by component. RepoList type state = { repos: list(Repo.t) }; There is, however, a catch. Initially, before the list of trending repositories is fetched from GitHub API, the is undefined. Reason type system doesn't allow us to have undefined value though. We could model that initial state with an empty list, but this is not optimal. Empty list could also mean that our query for fetching trending repositories didn't return any results. repos Let’s use Reason’s optional values to deal with that situation. type state = { repos: option(list(Repo.t)) } Next step is to define possible actions for that component. In ReasonReact, actions are represented as variants. For now we will only have one action called . ReposFetched type action = | ReposFetched(list(Repo.t)); In order to create a stateful component in ReasonReact we need to use function. reducerComponent() let component = ReasonReact.reducerComponent("App"); Such component allows to define a reducer which describes how the state is transformed in response to actions. A reducer takes an action along with the current state as input and returns the new state as output. Reducers must be pure functions. reducer: (action, _prevState) => { switch action { | ReposFetched(repos) => ReasonReact.Update({repos: Some(repos)}) } } We’re pattern matching action, based on the parameter we receive in the reducer() method. Pattern matching must be exhaustive. All variant values must be matched. definition is placed inside component's function. reducer main To finish off component’s definition, let’s define its initial state: initialState: () => { repos: Some([ {name: "Huncwot", size: 11011, forks: 42} ]) } Integrating with API We will use to fetch data from an external API. It is a BuckleScript library that acts as a thin layer on top of the Fetch API. Once the data is fetched, we will use to extract fields we are interested in. [bs-fetch](https://github.com/reasonml-community/bs-fetch) bs-json Start by installing and : bs-fetch bs-json npm i bs-fetch @glennsl/bs-json Add them to in your : bs-dependencies bsconfig.json { "bs-dependencies": [ ..., "bs-fetch", "@glennsl/bs-json" ] } We defined our type as a set of three fields: , and . Once the payload is fetched from GitHub API we parse it to extract those three fields. Repo name size forks let parse = json => Json.Decode.{ name: json |> field("name", string), size: json |> field("size", int), forks: json |> field("forks", int), }; is a method of . The (mind the dot) opens module. Its properties can now be used within these curly brackets without the need of prefixing with . field Json.Decode Json.Decode.{ ... } Json.Decode Json.Decode Since GitHub returns repos under , let's define another function to get that list. items let extract = (fields, json) => Json.Decode.( json |> at(fields, list(parse))); Finally we can make a request and pass the returned data through our parsing functions: let list = () => Js.Promise.( Fetch.fetch(endpoint) |> then_(Fetch.Response.json) |> then_(text => extract(["items"], text) |> resolve) ); React Lifecycle Methods Let’s use lifecycle method to trigger the fetch of repositories from GitHub API. didMount didMount: self => { let handle = repos => self.send(ReposFetched(repos)); Repo.list() |> Js.Promise.then_(repos => { handle(repos); Js.Promise.resolve(); }) |> ignore; } is a method that dispatches action to the reducer. Once the promise resolves, the action will carry fetched repositories to the reducer. This will update our state. handle ReposFetched Rendering Since we distinguish between non initialized state and an empty list of repositories, it is straightforward to handle the initial message. loading in progress render: self => <div> ( switch self.state.repos { | None => s("Loading repositories..."); | Some([]) => s("Emtpy list") | Some(repos) => <ul> ( repos |> List.map((repo: Repo.t) => <li> (s(repo.name)) </li>) |> Array.of_list |> ReasonReact.array ) </ul> } ) </div> }; Error handling TBW Types in CSS Types for CSS with . [bs-css](https://github.com/SentiaAnalytics/bs-css) yarn add bs-css "bs-dependencies": [ ..., "bs-css" ] let style = Css.( { "header": style([backgroundColor(rgba(111, 37, 35, 1.0)), display(Flex)]), "title": style([color(white), fontSize(px(28)), fontWeight(Bold)]), } ); let make = _children => { ...component, render: _self => <header className=style##header> <h1 className=style##title> (s("This is title")) </h1> </header> }; Vocabulary is an interactive command line for Reason. rtop Merlin is an autocompletion service file for OCaml and Reason. Bucklescript annotations for FFI [@bs...] Additional Resources TBD module History = { type h; [@bs.send] external goBack : h => unit = ""; [@bs.send] external goForward : h => unit = ""; [@bs.send] external go : (h, ~jumps: int) => unit = ""; [@bs.get] external length : h => int = ""; }; BuckleScript allows us to mix raw JavaScript with Reason code. [%bs.raw {|require('./app.css')|}]; Originally published at zaiste.net .
Share Your Thoughts