100% found this document useful (3 votes)
13 views

[FREE PDF sample] Introducing ReScript: Functional Programming for Web Applications 1st Edition Danny Yang ebooks

Programming

Uploaded by

zaraijaashi
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (3 votes)
13 views

[FREE PDF sample] Introducing ReScript: Functional Programming for Web Applications 1st Edition Danny Yang ebooks

Programming

Uploaded by

zaraijaashi
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 76

Download Full Version ebookmass - Visit ebookmass.

com

Introducing ReScript: Functional Programming for


Web Applications 1st Edition Danny Yang

https://ebookmass.com/product/introducing-rescript-
functional-programming-for-web-applications-1st-edition-
danny-yang/

OR CLICK HERE

DOWLOAD NOW

Discover More Ebook - Explore Now at ebookmass.com


Instant digital products (PDF, ePub, MOBI) ready for you
Download now and discover formats that fit your needs...

Introducing Functional Programming Using C#: Leveraging a


New Perspective for OOP Developers 1st Edition Vaskaran
Sarcar
https://ebookmass.com/product/introducing-functional-programming-
using-c-leveraging-a-new-perspective-for-oop-developers-1st-edition-
vaskaran-sarcar/
ebookmass.com

Introducing Functional Programming Using C# : Leveraging a


New Perspective for OOP Developers Vaskaran Sarcar

https://ebookmass.com/product/introducing-functional-programming-
using-c-leveraging-a-new-perspective-for-oop-developers-vaskaran-
sarcar/
ebookmass.com

PHP 8 Basics: For Programming and Web Development 1st


Edition Gunnard Engebreth

https://ebookmass.com/product/php-8-basics-for-programming-and-web-
development-1st-edition-gunnard-engebreth/

ebookmass.com

Governing Social Protection in the Long Term : Social


Policy and Employment Relations in Australia and New
Zealand 1st ed. Edition Gaby Ramia
https://ebookmass.com/product/governing-social-protection-in-the-long-
term-social-policy-and-employment-relations-in-australia-and-new-
zealand-1st-ed-edition-gaby-ramia/
ebookmass.com
Social Studies for the Preschool/Primary Child 9th
Edition, (Ebook PDF)

https://ebookmass.com/product/social-studies-for-the-preschool-
primary-child-9th-edition-ebook-pdf/

ebookmass.com

Hydrometallurgy: Theory 1st Edition Michael Nicol

https://ebookmass.com/product/hydrometallurgy-theory-1st-edition-
michael-nicol/

ebookmass.com

Conservation Technology Serge A. Wich

https://ebookmass.com/product/conservation-technology-serge-a-wich/

ebookmass.com

Massage Therapistu2019s Guide to Pathology: Critical


Thinking and Practical Application 6th Edition, (Ebook
PDF)
https://ebookmass.com/product/massage-therapists-guide-to-pathology-
critical-thinking-and-practical-application-6th-edition-ebook-pdf/

ebookmass.com

College Algebra, 11e 11th Edition Ron Larson

https://ebookmass.com/product/college-algebra-11e-11th-edition-ron-
larson/

ebookmass.com
Graph Data Science with Neo4j: Learn how to use Neo4j 5
with Graph Data Science library 2.0 and its Python driver
for your project Scifo
https://ebookmass.com/product/graph-data-science-with-neo4j-learn-how-
to-use-neo4j-5-with-graph-data-science-library-2-0-and-its-python-
driver-for-your-project-scifo/
ebookmass.com
Introducing
ReScript
Functional Programming for
Web Applications

Danny Yang
Introducing ReScript
Functional Programming
for Web Applications

Danny Yang
Introducing ReScript: Functional Programming for Web Applications
Danny Yang
Mountain View, CA, USA

ISBN-13 (pbk): 978-1-4842-8887-0 ISBN-13 (electronic): 978-1-4842-8888-7


https://doi.org/10.1007/978-1-4842-8888-7

Copyright © 2023 by Danny Yang


This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or
part of the material is concerned, specifically the rights of translation, reprinting, reuse of
illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way,
and transmission or information storage and retrieval, electronic adaptation, computer software,
or by similar or dissimilar methodology now known or hereafter developed.
Trademarked names, logos, and images may appear in this book. Rather than use a trademark
symbol with every occurrence of a trademarked name, logo, or image we use the names, logos,
and images only in an editorial fashion and to the benefit of the trademark owner, with no
intention of infringement of the trademark.
The use in this publication of trade names, trademarks, service marks, and similar terms, even if
they are not identified as such, is not to be taken as an expression of opinion as to whether or not
they are subject to proprietary rights.
While the advice and information in this book are believed to be true and accurate at the date of
publication, neither the authors nor the editors nor the publisher can accept any legal
responsibility for any errors or omissions that may be made. The publisher makes no warranty,
express or implied, with respect to the material contained herein.
Managing Director, Apress Media LLC: Welmoed Spahr
Acquisitions Editor: Steve Anglin
Development Editor: James Markham
Coordinating Editor: Jill Balzano
Cover designed by eStudioCalamar
Cover image by Steve Johnson on Unsplash (www.unsplash.com)
Distributed to the book trade worldwide by Apress Media, LLC, 1 New York Plaza, New York, NY
10004, U.S.A. Phone 1-800-SPRINGER, fax (201) 348-4505, e-mail [email protected],
or visit www.springeronline.com. Apress Media, LLC is a California LLC and the sole member
(owner) is Springer Science + Business Media Finance Inc (SSBM Finance Inc). SSBM Finance
Inc is a Delaware corporation.
For information on translations, please e-mail [email protected]; for
reprint, paperback, or audio rights, please e-mail [email protected].
Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook
versions and licenses are also available for most titles. For more information, reference our Print
and eBook Bulk Sales web page at http://www.apress.com/bulk-sales.
Any source code or other supplementary material referenced by the author in this book is
available to readers on GitHub (https://github.com/Apress). For more detailed information,
please visit http://www.apress.com/source-code.
Printed on acid-free paper
Table of Contents
About the Author���������������������������������������������������������������������������������ix

About the Technical Reviewer�������������������������������������������������������������xi

Introduction���������������������������������������������������������������������������������������xiii

Chapter 1: ReScript Basics�������������������������������������������������������������������1


Expressions, Values, and Side Effects�������������������������������������������������������������������1
Compile Time and Runtime�����������������������������������������������������������������������������������2
Types, Typechecking, and Type Inference�������������������������������������������������������������2
Primitive Types and Operators������������������������������������������������������������������������������3
Integer and Float���������������������������������������������������������������������������������������������3
Boolean������������������������������������������������������������������������������������������������������������5
String���������������������������������������������������������������������������������������������������������������6
Unit������������������������������������������������������������������������������������������������������������������7
Printing and Debugging����������������������������������������������������������������������������������������8
Bindings����������������������������������������������������������������������������������������������������������������9
Mutation and Refs�����������������������������������������������������������������������������������������10
Blocks�����������������������������������������������������������������������������������������������������������������12
Block Scoping�����������������������������������������������������������������������������������������������������14
Conditionals��������������������������������������������������������������������������������������������������������14
Switches�������������������������������������������������������������������������������������������������������������16
Loops������������������������������������������������������������������������������������������������������������������21
Putting It All Together������������������������������������������������������������������������������������������24
Final Thoughts�����������������������������������������������������������������������������������������������������27

iii
Table of Contents

Chapter 2: Functions��������������������������������������������������������������������������29
Defining and Using Functions�����������������������������������������������������������������������������29
Type Annotations�������������������������������������������������������������������������������������������31
Using Standard Library Functions and Opening Modules������������������������������32
Higher-Order Functions���������������������������������������������������������������������������������34
Piping������������������������������������������������������������������������������������������������������������35
Labeled and Optional Parameters�����������������������������������������������������������������38
Currying and Partial Application��������������������������������������������������������������������40
Polymorphic Functions����������������������������������������������������������������������������������42
Pure Functions����������������������������������������������������������������������������������������������������43
Ignoring Return Values����������������������������������������������������������������������������������44
Recursion������������������������������������������������������������������������������������������������������������46
Syntax������������������������������������������������������������������������������������������������������������46
How to Use Recursion�����������������������������������������������������������������������������������48
Final Thoughts�����������������������������������������������������������������������������������������������������52

Chapter 3: Composite Data Types�������������������������������������������������������53


Tuples������������������������������������������������������������������������������������������������������������������53
Creating Tuples����������������������������������������������������������������������������������������������54
Accessing Tuples�������������������������������������������������������������������������������������������������55
Pattern Matching with Tuples������������������������������������������������������������������������57
Variants���������������������������������������������������������������������������������������������������������������61
Declaring and Constructing Variants�������������������������������������������������������������61
Pattern Matching with Variants���������������������������������������������������������������������62
Exhaustiveness����������������������������������������������������������������������������������������������62
Complex Patterns������������������������������������������������������������������������������������������63
Variants with Data�����������������������������������������������������������������������������������������64
Recursive Data Types������������������������������������������������������������������������������������69
Options����������������������������������������������������������������������������������������������������������������72

iv
Table of Contents

Pattern Matching Options�����������������������������������������������������������������������������������73


Exceptions and Error Handling����������������������������������������������������������������������������76
Raising Exceptions����������������������������������������������������������������������������������������������77
Catching Exceptions��������������������������������������������������������������������������������������77
Custom Exceptions���������������������������������������������������������������������������������������������78
Another Way to Handle Errors: Result������������������������������������������������������������79
Result in Action����������������������������������������������������������������������������������������������81
Defining Errors for Results����������������������������������������������������������������������������82
Composing Results����������������������������������������������������������������������������������������84
Final Thoughts�����������������������������������������������������������������������������������������������������86

Chapter 4: Records and Objects���������������������������������������������������������87


Records���������������������������������������������������������������������������������������������������������������87
Declaring and Creating Records��������������������������������������������������������������������88
Nominal Typing����������������������������������������������������������������������������������������������88
Accessing Record Fields�������������������������������������������������������������������������������89
Updating Records������������������������������������������������������������������������������������������90
Mutable Fields�����������������������������������������������������������������������������������������������91
Optional Fields�����������������������������������������������������������������������������������������������91
Destructuring Records�����������������������������������������������������������������������������������92
Pattern Matching with Records���������������������������������������������������������������������95
Records and Variants�������������������������������������������������������������������������������������97
Printing Records��������������������������������������������������������������������������������������������99
Records and JSON���������������������������������������������������������������������������������������100
Objects��������������������������������������������������������������������������������������������������������������101
Declaring and Creating Objects�������������������������������������������������������������������102
Accessing Object Fields������������������������������������������������������������������������������103
Structural Typing�����������������������������������������������������������������������������������������103
Mutating Objects�����������������������������������������������������������������������������������������104

v
Table of Contents

Printing Objects�������������������������������������������������������������������������������������������105
Objects and JSON����������������������������������������������������������������������������������������106
Objects vs. Records������������������������������������������������������������������������������������������108

Chapter 5: Lists and Arrays��������������������������������������������������������������109


Arrays����������������������������������������������������������������������������������������������������������������109
Array Standard Library��������������������������������������������������������������������������������������110
Note on Accessing Arrays����������������������������������������������������������������������������111
Higher-Order Functions for Arrays��������������������������������������������������������������������113
Map�������������������������������������������������������������������������������������������������������������113
Filter������������������������������������������������������������������������������������������������������������116
Reduce��������������������������������������������������������������������������������������������������������118
Composing Higher-Order Functions������������������������������������������������������������122
Higher-Order Functions in Action����������������������������������������������������������������122
Generalizing Higher-Order Functions����������������������������������������������������������126
Lists������������������������������������������������������������������������������������������������������������������128
Building a List����������������������������������������������������������������������������������������������128
Immutability and Lists���������������������������������������������������������������������������������129
Pattern Matching with List��������������������������������������������������������������������������130
Higher-Order Functions with Lists��������������������������������������������������������������������133
Drawbacks of Lists��������������������������������������������������������������������������������������136
Use Cases for Immutable Collections����������������������������������������������������������137
Lists vs. Arrays��������������������������������������������������������������������������������������������139
Final Thoughts���������������������������������������������������������������������������������������������139

Chapter 6: Collections����������������������������������������������������������������������141
Immutable Collections��������������������������������������������������������������������������������������142
Immutable Sets�������������������������������������������������������������������������������������������143
Immutable Maps������������������������������������������������������������������������������������������146

vi
Table of Contents

Using Collections: Luggage Example Revisited�������������������������������������������149


Advanced Topic: Generic Collections�����������������������������������������������������������151
Mutable Collections������������������������������������������������������������������������������������������156
Mutable Stack���������������������������������������������������������������������������������������������157
Mutable Queue��������������������������������������������������������������������������������������������159
Mutable Set and Mutable Map��������������������������������������������������������������������161
Hash Set and Hash Map������������������������������������������������������������������������������162
Advanced Topic: Generic Hash Set/Hash Map Keys������������������������������������164
Dict��������������������������������������������������������������������������������������������������������������167
Which Collection Should I Use?������������������������������������������������������������������������171

Chapter 7: Modules��������������������������������������������������������������������������173
Files as Modules�����������������������������������������������������������������������������������������������174
Defining Modules����������������������������������������������������������������������������������������������174
Using Modules��������������������������������������������������������������������������������������������������175
Opening Modules�����������������������������������������������������������������������������������������177
Destructuring a Module�������������������������������������������������������������������������������181
Module Examples����������������������������������������������������������������������������������������������182
Module Signatures��������������������������������������������������������������������������������������������188
Interface Files����������������������������������������������������������������������������������������������189
Defining Module Signatures������������������������������������������������������������������������189
Functors������������������������������������������������������������������������������������������������������194
Defining and Using Functors�����������������������������������������������������������������������195
Extending Modules��������������������������������������������������������������������������������������197
Functors in the Standard Library�����������������������������������������������������������������199
Final Thoughts���������������������������������������������������������������������������������������������������200

vii
Table of Contents

Chapter 8: JavaScript Interoperability���������������������������������������������203


Calling JavaScript from ReScript����������������������������������������������������������������������203
Embedding Raw JavaScript in ReScript Files����������������������������������������������206
Calling ReScript from JavaScript����������������������������������������������������������������������208
Exporting Values from ReScript�������������������������������������������������������������������208
Using ReScript Modules from JavaScript����������������������������������������������������209
Shared Data Types��������������������������������������������������������������������������������������������211
Integers�������������������������������������������������������������������������������������������������������213
Functions�����������������������������������������������������������������������������������������������������214
Options��������������������������������������������������������������������������������������������������������215
Other Data Types�����������������������������������������������������������������������������������������������216
Immutable Data Structures�������������������������������������������������������������������������216
Variants�������������������������������������������������������������������������������������������������������216
Polymorphic Variants�����������������������������������������������������������������������������������219
Working with Null����������������������������������������������������������������������������������������������220
Working with Exceptions����������������������������������������������������������������������������������223
Catching ReScript Exceptions in JavaScript������������������������������������������������223
Catching JavaScript Exceptions in ReScript������������������������������������������������225
Working with JSON�������������������������������������������������������������������������������������������225
Option 1: Binding Without Types������������������������������������������������������������������225
Option 2: Binding With Types�����������������������������������������������������������������������227
Option 3: With Validation������������������������������������������������������������������������������228
Putting It All Together: Simple ReScript Web App����������������������������������������������232
Version 1: Handwritten Bindings�����������������������������������������������������������������233
Version 2: Using Imported Bindings�������������������������������������������������������������247
Final Thoughts���������������������������������������������������������������������������������������������������253

Index�������������������������������������������������������������������������������������������������255

viii
About the Author
Danny Yang is a professional software engineer at Meta working on
infrastructure for WhatsApp. He has previously worked on Facebook
Messenger, including the web interface which was written in ReScript.
His technical interests include functional programming, compilers, and
data visualization, which he writes about on his blog at www.yangdanny97
.github.io.

ix
About the Technical Reviewer
German Gonzalez-Morris is a software architect/engineer working
with C/C++, Java, and different application containers, in particular
with WebLogic Server. He has developed various applications using JEE,
Spring, and Python. His areas of expertise also include OOP, Java/JEE,
Python, design patterns, algorithms, Spring Core/MVC/security, and
microservices. German has worked with performance messaging, RESTful
API, and transactional systems. For more information about him, visit
­www.linkedin.com/in/german-gonzalez-morris.

xi
Introduction
Why Learn ReScript?
JavaScript is vital to the modern web ecosystem. It’s used in the front end
to implement websites and other user interfaces, and used in the back end
to implement servers for websites and APIs.
Part of JavaScript’s ubiquity is due to its ease of use. JavaScript is
dynamic and flexible, making it easy for people to pick up. However, this
strength becomes a weakness when working on large web applications
with multiple developers – the only way to know that JavaScript code works
correctly is to actually run it, and it’s relatively easy to make mistakes when
programming in JavaScript.
What if there was a way to detect bugs in JavaScript before running
the code, or prevent many classes of bugs altogether? What if there was a
language that was concise and elegant that made it easy for programmers
to write complex web applications and hard for programmers to make
mistakes?
Enter ReScript.
ReScript is a language designed for writing web applications. It brings
a lot to the table: static typechecking, a strong type system, and powerful
language features that will change the way you program.
Here’s a glimpse of some of the features that make ReScript a great
language:

Static typechecking – Catch bugs in your code


without having to run it: undefined values, missing
cases, incorrect types, and more.

xiii
Introduction

Sound type system – ReScript programs that pass


typechecking cannot have runtime type errors.

Type inference – ReScript automatically infers types


based on how variables are used, allowing you to
enjoy the benefits of type safety without having to
annotate every variable and function.

Immutability – Peace of mind while you program


with variables and data structures that cannot be
unexpectedly modified under your nose.

Algebraic data types and pattern matching – Cleanly


define and elegantly manipulate complex data.

First-class bindings for React – Write React


elements and JSX directly inside ReScript files.

There are a number of other languages and tools that offer static
typechecking for web applications, but ReScript has several key advantages
over its competitors. As an example, let’s look at the benefits ReScript has
compared with another popular JavaScript alternative, TypeScript:

ReScript is safer – Unlike ReScript’s battle-tested


and sound type system, TypeScript’s type system is
unsound, so it is still possible to have runtime type
errors in a valid TypeScript program.

ReScript is faster – ReScript’s compiler is much


faster than TypeScript’s compiler, allowing for a
smoother development experience when working in
large code bases.

ReScript is more concise – ReScript’s excellent


type inference means that programmers do not
have to write as many type annotations in ReScript
programs compared to TypeScript programs.

xiv
Introduction

Although ReScript is a relative newcomer to the web ecosystem, it’s


actually based on technology that has been battle-tested for years before
ReScript even existed. ReScript itself has proven successful as well. Most
notably, Facebook used it to build the web interface for Messenger – a
product used by hundreds of millions of people – with a code base
containing thousands of files.

History of ReScript
The lineage of ReScript can ultimately be traced back to the ML family of
languages originating from the 1960s. In particular, ReScript is directly
based on OCaml, a general-purpose programming language that was
developed in the 1980s and used today for systems programming in
academia and industry.
In 2015, Jordan Walke, the creator of the React web framework,
developed a toolchain and alternative syntax for OCaml called Reason.
Reason was designed to bridge the gap between the web and OCaml
ecosystems – it could be compiled into both native machine code and
JavaScript, allowing web developers to take advantage of OCaml’s
features. Static typechecking and OCaml’s sound type system eliminated
many common bugs in JavaScript code, and OCaml’s immutability and
functional style was a great fit for React.
Reason was compiled to JavaScript using a compiler called
BuckleScript, which was developed at Bloomberg around the same time
Reason was being created at Facebook.
Around 2020, the BuckleScript project created a new language
based on Reason that could only be compiled to JavaScript using the
BuckleScript compiler, and so ReScript was born.

xv
Introduction

ReScript has the following key differences from its predecessors:

ReScript has different syntax and features. While


it looks and feels more like JavaScript, ReScript is
still based on the battle-tested compiler and type
system as Reason and OCaml, so it has the same
type safety benefits as its predecessors.

ReScript can only be compiled to JavaScript. By


dropping support for native compilation, ReScript
has a simpler toolchain and standard library, along
with a feature set better suited for web development.
This makes ReScript easier for newcomers to learn
and allows for smoother integration with other web
technologies.

ReScript and the Web Ecosystem


Like some other statically typed languages in the web ecosystem, ReScript
code is transpiled to JavaScript. This means that ReScript code doesn’t
directly run in the browser or on the server. Instead, the ReScript compiler
checks that the code is valid and generates JavaScript files, which can then
be imported and used like any handwritten JavaScript file.
Being able to run in any environment that supports JavaScript allows
ReScript to be used for full-stack web development, from client-side code
that runs in the browser to server-side code that runs in Node.js.
Since ReScript code is exactly the same as JavaScript code when it runs,
ReScript programs can easily import and use JavaScript libraries, while
JavaScript programs can call ReScript functions as easily as they can call
other JavaScript functions.

xvi
Introduction

Why Learn Functional Programming?


Functional programming is a paradigm of programming that focuses on
the application and composition of functions.
In functional languages, functions are first class and can be used like
any other value (bound to variables, passed as arguments, or returned
from other functions). Complex programs are written by composing
multiple smaller functions that apply transformations to data. This can be
contrasted with the imperative and object-oriented style of many popular
languages, where programs look more like a series of commands that read
and update memory. It’s important to know that most languages are not
purely in one category or the other; many languages fall somewhere in
the middle of this spectrum, and features from functional languages are
slowly being adopted by other languages. For example, ReScript is more
functional but it also has imperative features like loops, while JavaScript is
more imperative but it also has first-class functions.
Programming in a functional style has many benefits – programs are
cleaner and more concise, and logic is more declarative making it easier to
trace the flow of data through a program. The ability to compose functions
together and write functions that accept other functions as arguments
(higher-order functions) makes functional languages very flexible, and
the greater emphasis on immutability and purity makes it easier to
understand, write, and test programs.
As a disclaimer, I’m not some functional programming purist here to
convince you that functional programming is the best way to solve every
problem. Instead, I view functional programming as a useful tool in a
programmer’s tool belt, albeit a tool that not enough people know about or
know how to use.
Unlike many other functional programming books, the explanations
in this book are designed to be accessible to those without a formal
background in computer science. I do not expect readers to have experience
with statically typed languages or functional programming concepts.

xvii
Introduction

About This Book


This book is written for anyone who is interested in learning ReScript or
wants to learn the basics of functional programming using ReScript.
The book is structured as an overview of ReScript’s features, building
up from the basics to eventually cover complex data types, pattern
matching, modules, and finally writing a minimal web application using
ReScript.
Along the way, you will learn functional programming concepts like
higher-order functions, immutability, and purity, which will help you think
about and write software differently, even when you are not using ReScript.
Here’s what this book will cover:

Chapter 1 – Language basics: expressions, binding,


and control flow

Chapter 2 – Functions: higher-order programming,


recursion, and purity

Chapter 3 – Composite data types, pattern


matching, and error handling

Chapter 4 – Records and objects

Chapter 5 – Lists and arrays: map, filter, and reduce

Chapter 6 – Collections: sets, maps, stacks,


and queues

Chapter 7 – Modular programming: modules and


functors

Chapter 8 – JavaScript integrations: bindings,


dealing with JSON, and more

xviii
Introduction

Installing ReScript
ReScript projects are set up just like modern JavaScript projects, except we
have an extra development dependency on the rescript package.
Before we begin, make sure you have Node.js v10 or higher, and npm.
To check the version of Node.js you have, run node -v in your terminal. If
you don’t have Node.js installed, you can find installation instructions at
https://nodejs.org.
Once you’ve confirmed that you have the right version of Node.js
installed, create a new directory for your project, and run npm install
rescript@10 to install v10 of ReScript.
There will be a package.json file with the following contents inside the
project’s root directory. Add the following scripts to it:

{
  ...

  "scripts": {
      "build": "rescript",
      "start": "rescript build -w"
  }
}

The scripts we added in package.json are used to run the ReScript


compiler to compile our ReScript to JavaScript.

npm run build will compile all the ReScript files in


the project.

npm run start will start a process that watches the


project and automatically recompiles whenever
anything changes.

xix
Introduction

Next, create a bsconfig.json file inside the same directory, with the
following contents:

{
"name": "your-project-name",
"sources": [
   {
     "dir": "src",
     "subdirs": true
   }
],
"package-specs": [
   {
     "module": "commonjs",
     "in-source": true
   }
],
"suffix": ".bs.js",
"bs-dependencies": []
}

The dir field specifies which directory ReScript source files are
located, in this case under the folder src. For every ReScript file Foo.res
under src, the compiler will output a JavaScript file named Foo.bs.js in
the same location as the original source.
Now we’re ready to write some ReScript!

xx
Introduction

First ReScript Program


Source files in ReScript have the extension .res. We can write a simple
Hello World program by creating a file at src/HelloWorld.res with the
following contents:

Js.log("hello, world")

ReScript is a compiled language – this means that the ReScript files


that we write are not being run directly by the browser or Node.js. The
compiler checks to make sure our ReScript code is valid – syntax is correct,
function calls and values are the right types, etc. – and then it compiles the
ReScript files into JavaScript. The browser or Node.js will run the JavaScript
the same way they would for JavaScript we wrote by hand.
Run the compiler using npm run build. You will see the compiled
output in src/HelloWorld.bs.js, which will have the following contents:

console.log("hello, world");

You can run the JavaScript file using node src/HelloWorld.bs.js to


print “hello, world” to the terminal.
Since ReScript compiles to JavaScript, it can be used for both client and
server applications in a variety of environments – your browser, Node.js,
etc. For the purposes of this book, we will be executing ReScript programs
in Node.js.
As you read through the examples in the book, I encourage you to copy
the examples into your code editor or the ReScript playground at https://
rescript-lang.org/try so that you can compile and run the programs
yourself. As you experiment with ReScript for yourself, make sure to
inspect the compiled JavaScript output of your ReScript programs to get a
deeper understanding of how ReScript works under the hood. You’ll find
that the generated JavaScript code is quite readable!

xxi
CHAPTER 1

ReScript Basics
In this chapter, we’ll dive right into the basic concepts of ReScript:
expressions, values, control flow, and binding. At the end of the chapter,
we’ll use those concepts in a simple command-line program.

Expressions, Values, and Side Effects


Expressions are the fundamental building block of ReScript programs. In
this section, we will be going over basic expressions in ReScript and their
semantics – their meaning. Just like we can learn the English language by
studying the meaning of words and sentences, we can learn ReScript by
studying the meaning of expressions.
In general, we will think about semantics like this: Evaluating an
expression performs some computation and yields a single result. The
result is a value, or something that cannot be evaluated any further –
for example, the expression 2 + 3 evaluates to 5. Additionally, the
evaluation might produce any number of side effects, or changes that
can be observed outside of the function – for example, evaluating an
expression can mutate an array, print something to the console, or throw
an exception.
In other languages, computation can be modeled as both expressions
and statements, where expressions yield a value when evaluated and
statements do not. In ReScript, there is no such thing as statements, and
every expression will yield a value if evaluated successfully.

© Danny Yang 2023 1


D. Yang, Introducing ReScript, https://doi.org/10.1007/978-1-4842-8888-7_1
Chapter 1 ReScript Basics

Compile Time and Runtime


To differentiate between things that happen when the ReScript code is
compiled and when the resulting JavaScript code is actually run, we’ll
introduce the terms compile time and runtime.
Again using 2 + 3 as an example:

At compile time, the ReScript compiler checks


that 2 and 3 are valid numbers and can be added
together. Then, it outputs JavaScript code, which in
this simple case is also 2 + 3.

At runtime, the JavaScript code actually runs and 2


+ 3 is evaluated to 5.

The code snippets in this book will sometimes be followed by the


expected output of the snippet. Outputs at compile time will be labeled
as “Compiler output,” and outputs at runtime will be labeled as “Console
output.”

Types, Typechecking, and Type Inference


Just like how values in JavaScript have types like number, object, and
boolean, expressions and values in ReScript also have types. In JavaScript,
types are not very strict, and values may be interpreted as different types
depending on the context they are being used in. In contrast, types in
ReScript are more strict.
In a compile-time process called static typechecking, the compiler
checks that the types of expressions and functions in our program are
consistent with each other, allowing us to catch potential bugs without
needing to run the program.

2
Chapter 1 ReScript Basics

To help the typechecker, the programmer can write down what


type each variable or function is supposed to be – this is called a type
annotation. Unlike other languages with static typechecking, type
annotations in ReScript are usually optional, thanks to a feature called
type inference which automatically detects the type of expressions or
functions based on how they are used.
Whether or not a programmer chooses to annotate types is a trade-off
between clarity and conciseness – type annotations take time to write, but
they can help make the code easier for other people to read.
In this book, you’ll learn the names of types and how to write type
annotations, but not every example will be fully annotated.

Primitive Types and Operators


Integer and Float
Unlike JavaScript which only has a single type for numbers (number), there
are two types of numbers in ReScript: integers and floats (int and float).
Integers in ReScript are limited to 32 bits, while floats in ReScript are
identical to JavaScript numbers.
They have different sets of arithmetic operators:
Int operators – +, -, *, /

Float operators – +., -., *., /., **

ReScript’s type system is strict, so integer arithmetic operators can only


be used on two integers, and float arithmetic operators can only be used
on two floats.
Integers may be converted to floats using float() or Belt.Int.
toFloat(); the opposite conversion can be done using int_of_float() or
Belt.Int.fromFloat().

3
Chapter 1 ReScript Basics

Additionally, there are a few operations that are done using functions
instead of operators:

Js.Math.pow_int(x, y) is the equivalent of ** for


integers

mod(x, y) remainder/modulo for integers

land(x, y) bitwise logical AND for integers

lor(x, y) bitwise logical OR for integers

lxor(x, y) bitwise logical XOR for integers

lnot(x) bitwise logical NOT for integers

lsl(x, y) left shift for integers

lsr(x, y) logical right shift for integers

asr(x, y) arithmetic right shift for integers

Here are some examples of int and float arithmetic operators and their
equivalents in JavaScript:

ReScript JavaScript

1+1 1+1
1.0 +. 1.0 1.0 + 1.0
1.0 +. float(1) 1.0 + 1
2.0 ** 3.0 Math.pow(2.0, 3.0)
float(2) ** float(3) Math.pow(2, 3)

Other standard library functions for ints and floats are found in the
Belt.Int, Belt.Float, Js.Int, Js.Float, and Js.Math modules. The
latter contains many useful utilities like random numbers, rounding,
trigonometry, and more.

4
Chapter 1 ReScript Basics

Integers are convenient to use when representing small numbers in


ReScript programs – they support more operations and their operators feel
more natural. However, we should always use floats to represent very large
whole numbers (such as unix timestamps) – using integer operations on
those numbers will truncate them to 32 bits!

Boolean
The boolean values true and false in ReScript are exactly the same as
booleans in JavaScript. Unlike in JavaScript, there is no such thing as
interpreting non-boolean values like numbers or strings as “truthy” and
“falsy” in ReScript – the only possible values for the bool type are true and
false. Furthermore, only boolean values may be passed to conditionals
and boolean operators, like while, if, &&, ||, etc.
Here are some common boolean operators:

Logical AND, OR, NOT: &&, ||, !

Comparison operators: >, <, >=, <=

Structural equality: ==, !=

Referential equality: ===, !==


Structural equality is a deep comparison of the values of nested
objects – two objects are structurally equal if they have the same fields and
the values of all the corresponding fields are structurally equal, and two
arrays are structurally equal if they have the same number elements and
the corresponding elements are structurally equal.
On the other hand, referential equality evaluates to true if both sides
are actually the same object.
For example:

{ "a": 1 } == { "a": 1 } evaluates to true.

{ "a": 1 } === { "a": 1 } evaluates to false.

5
Chapter 1 ReScript Basics

[1, 1] == [1, 1] evaluates to true.

[1, 1] === [1, 1] evaluates to false.

x == x evaluates to true.

x === x evaluates to true.

Note that equality operators can only be used on two values that are
the same type; for example, the expression 1 == "2" is not allowed and
will result in the following typechecker error:

This has type: string


Somewhere wanted: int

String
String literals in ReScript are written with double quotes and may span
multiple lines. Strings can be concatenated using the ++ operator.
To convert other primitive values to strings, use the toString functions
in the standard library – for example, Js.Int.toString, Js.Float.
toString, etc. Here is an example of string concatenation and conversion:

"I am " ++ Js.Int.toString(5) ++ " years old"

Here is an example of slicing a string using the standard library:

// this expression evaluates to "34"


Js.String2.slice("12345", ~from=2, ~to_=4)

ReScript supports string interpolation, although with the restriction


that interpolated expressions must all be strings. The string concatenation
example implemented using interpolation would look like this:

`I am ${Js.Int.toString(5)} years old`

6
Chapter 1 ReScript Basics

The standard library functions for strings are found in the Js.String2
module – for the most part, these functions are exactly the same as the
JavaScript function with the same name.
In JavaScript, we might trim and split a string like this:

let s = "  1,2,3,4,5  "
let s2 = s.trim().split(",")

In ReScript, we can do the same using functions in Js.String2 (don’t


worry about the arrow syntax for now, that will be explained in the next
chapter):

let s = "  1,2,3,4,5  "
let s2 = s->Js.String2.trim->Js.String2.split(",")

Unit
ReScript has a type called unit that only has one possible value, the
unit value. Unit is the name of the type, AND it is the name of the single
possible value of that type.
Just like how the two possible values for the boolean type (true and
false) can be written with the literals true and false, the single possible
value for the unit type is written with the literal ().
In JavaScript, calling a function that does not return any values and
assigning the result to a variable will make the variable equal undefined.
In ReScript, the result is modeled as the unit value.
Since the ReScript compiler compiles () to undefined, the unit value is
undefined at run time.

7
Chapter 1 ReScript Basics

When the computation of an expression does not yield a useful value,


we can have it yield a unit value, and the entire expression will have the
unit type. This is common when we are primarily evaluating an expression
for its side effects.
One example of this is the console.log function (called Js.log in
ReScript) – calling it returns a value of type unit and has the side effect of
printing to the console. Any time we would want to write a function that
doesn’t return a value in JavaScript, the ReScript equivalent will return
unit. Another example is while-loops or for-loops. Since loops do not
return a value in ReScript, they evaluate to unit.

Printing and Debugging


ReScript allows us to call JavaScript’s console.log via the Js.log function.
This is useful for printing values and debugging our programs, and the
examples in this book will make heavy use of it.
Unlike in JavaScript where console.log takes a variable number
of arguments, ReScript’s logging functions take in a fixed number of
arguments. There are several different variants that we call depending on
how many arguments we want to log, but they all compile to console.log.
Here are some examples:

Js.log("hello")

Js.log2("hello", "world") – This can be used to


log two values; additionally, log3 and log4 can be
used to log three and four things, respectively. Each
argument can have a different type.

Js.logMany(["hello", "world"]) – This can be


called with an array to log many values at once, but
each argument in the array must be the same type.

8
Chapter 1 ReScript Basics

Bindings
Bindings in ReScript allow us to associate values with names so that
they can be referenced later. They are like variable declarations in other
languages, but we don’t call them variables in ReScript because the value
can’t be changed (more on that later).
Let bindings in ReScript look similar to variable let declarations in
JavaScript. Names must start with lowercase letters:

let x = 1
let y = 2
let z = x + y

Types may be added to a binding, but they are not required. If a type
is added, the compiler checks to make sure that the value on the right side
matches the declared type. If the type is not added, then the binding is
whatever type the compiler infers the right side to have:

let w: unit = ()
let x: int = 1
let y: bool = true
let z: string = "hello"

In JavaScript, a let declaration can be updated with a new value, but we


cannot declare another variable with the same name in the same scope. In
ReScript, this is the exact opposite – we cannot update the binding with a
new value, but we can create another binding with the same name in the
same scope.
ReScript’s bindings are immutable, meaning that their values cannot
be updated, but they can be shadowed. Shadowing means declaring a
binding with the same name as an existing one – all later usages of the
name within that scope will point to the new declaration:

let x = 1
let x = 2

9
Chapter 1 ReScript Basics

Shadowed bindings do not need to be the same type as other bindings


of the same name, because they are an entirely separate declaration:

let x: int = 1
let x: string = "hello"

It’s very important to emphasize that shadowing a binding is NOT


the same as mutating a variable. Bindings are immutable, and shadowed
bindings are in fact compiled to entirely different variables in JavaScript.

ReScript JavaScript

let x = Js.Math.random_int(0, 5) var x = Js_math.random_int(0, 5);


Js.log(x) console.log(x);
let x = Js.Math.random_int(0, 5) var x$1 = Js_math.random_int(0, 5);
Js.log(x) console.log(x$1);
let x = Js.Math.random_int(0, 5) var x$2 = Js_math.random_int(0, 5);
Js.log(x) console.log(x$2);

Mutation and Refs


Although bindings are immutable by default, we can wrap values in a ref,
allowing them to be updated without creating a new binding.
To create a ref, just wrap an expression with ref(). The expression will
be evaluated and the result will be stored in the ref.
The ref can be bound to a name just like any other value, as shown
in the following example. The type of a ref holding a value of type 'a is
ref<'a>:

let x: ref<int> = ref(0)

10
Chapter 1 ReScript Basics

Compiler output:

var x = {
contents: 0
};

To access the contents of a ref, use .contents:

let x: ref<int> = ref(0)


Js.log(x.contents)

Compiler output:

var x = {
contents: 0
};
console.log(x.contents);

Console output:

To mutate the contents of a ref, use the := operator:

let x: ref<int> = ref(0)


x := x.contents + 1
Js.log(x.contents)

Compiler output:

var x = {
contents: 0
};
x.contents = x.contents + 1 | 0;
console.log(x.contents);

11
Chapter 1 ReScript Basics

Console output:

As seen in the compiled JavaScript outputs earlier, refs are essentially


JavaScript objects with a single contents property. Be careful not to
overuse refs – if we write a program that passes around a ref and mutates
it in a bunch of different places, it will be difficult to track those mutations
and may lead to bugs.
In general, mutation should be regarded as a feature that should be
used sparingly and intentionally. In this way ReScript differs from some
languages you may be used to: immutable bindings are the default and
choosing to use mutation needs to be an explicit decision made by the
programmer. However, there are some cases where mutation makes sense,
such as when we want to write imperative loops.

Blocks
Multiple expressions can be grouped into a block, where they will be
evaluated in sequence. The result of evaluating the whole block is the
value of the last expression. Blocks are delimited by curly braces, and the
expressions within can be separated by newlines or semicolons.
Blocks must contain at least one expression or binding, and every
expression in a block except for the last one must evaluate to unit.
This expression block has type unit and evaluates to unit:

{
Js.log(1); Js.log(2); Js.log(3)
}

12
Chapter 1 ReScript Basics

This expression block has type int and evaluates to 3:

{
Js.log("hello")
3
}

Blocks may be used as expressions or nested in other blocks. In this


example, the right-hand side of the binding is a block. Evaluating the block
will print “hello” and “world” and yield the final value 3, which is what gets
bound to x:

let x = {
Js.log("hello")
{
   Js.log("world")
}
3
}
Js.log(x)

Console output:

Unused bindings, like those that are at the end of a block, evaluate
to unit:

let x: unit = {
let blah = 3
}

13
Chapter 1 ReScript Basics

Block Scoping
Bindings are local to each block.
This example prints 3, because x is only bound to the value 4 inside
that block. It is also a good showcase of immutable bindings – the value of
x is not updated by the new binding in the nested scope, only shadowed:

let x = 3
{
let x = 4
}
Js.log(x)

Console output:

Here’s an example which fails to compile, since the binding for y does
not exist outside of the nested scope:

let x = {
let y = Js.Math.random_int(0, 5)
y
}
let z = y

Compiler output:

The value y can't be found

Conditionals
If-expressions in ReScript do not behave like if-statements in JavaScript.
Instead, they are like ternaries – each branch is an expression and the
entire if-expression evaluates to the value of the branch corresponding
with the condition.

14
Chapter 1 ReScript Basics

The semantics are pretty straightforward: if the condition evaluates to


true, then only the expression in the first branch is evaluated, and if the
condition evaluates to false, only the else branch is evaluated.
Here’s an example of if-expressions in ReScript. They compile to
ternaries in JavaScript.

ReScript JavaScript

let x = if y > 4 { var x = y > 4 ? 1 : 2;


1
} else {
2
}

If-expressions support else if syntax, which gets compiled into


nested ternaries.

ReScript JavaScript

let x = if y > 4 { var x = y > 4 ? 1 : (


1 y > 5 ? 2 : 3
} else if y > 5 { );
2
} else {
3
}

The expression in each branch of an if-expression must be the same


type. Additionally, an expression must be provided for each branch,
although we can omit the else branch entirely. If the else branch is not
provided, the entire if-expression evaluates to unit, and every other branch
must also evaluate to unit.

15
Chapter 1 ReScript Basics

ReScript JavaScript

let x = if y > 4 { var x = (y > 4, undefined);


()
}

Switches
Switches in ReScript are very different from switches in JavaScript or most
other languages, to such an extent that I would say they are similar in
name only.
Although on the surface they both let us match some expression
against a number of cases and provide nice features like exhaustiveness
checking, there are a number of major differences that make ReScript’s
switches behave differently from switches in other languages:

Like conditionals, ReScript’s switches are also


expressions and will return a single value when
evaluated.

There is no fall-through in the body of each case,


exactly one case will be matched, and the matching
case’s body will be evaluated and returned without
evaluating any more cases.

ReScript’s switches can be used on complex


data types.

Cases do not have to match on literal values; they


can express more complex conditions.

16
Chapter 1 ReScript Basics

The last two points are very important – ReScript’s switches support
a powerful feature called pattern matching. Instead of only being able to
match primitive types against literal values, conditions can be expressed as
patterns, allowing cases to match complex conditions on compound data
types like tuples, lists, records, etc.
As you learn about more data types in ReScript, you’ll also learn how
to leverage pattern matching to work with them. For now, let’s see how
switches can be used on the simple data types we just covered.
When used for simple data types like integers, ReScript’s switches can
be used similarly to JavaScript’s switches. The basic syntax for a switch is
outlined in the following example. Each case begins with a pipe |, then the
value or pattern that we want to match against, then an arrow =>, and finally
the expression that we want to evaluate and return if that case matches.
A case that is just an underscore _ is a default or wildcard which
matches everything. Cases are evaluated in order, so it’s common to see a
wildcard case at the bottom, just like we would use a default in JavaScript
switch statements:

let x = Js.Math.random_int(0, 5)
switch x {
| 0 => Js.log("zero")
| 1 => Js.log("one")
| _ => Js.log("some other number")
}

The preceding switch statement would be analogous to the following


in JavaScript. Since ReScript’s switch does not have fall-through cases, they
can be used like JavaScript switch statements with a break at the end of
each case:

switch x {
case 0:
   console.log("zero");
   break;

17
Chapter 1 ReScript Basics

case 1:
   console.log("one");
   break;
default:
   console.log("some other number");
}

Conditions can fall through, allowing you to use the same body and
return the same value for multiple cases:

let x = Js.Math.random_int(0, 5)
switch x {
| 0 | 1 => Js.log("zero or one")
| _ => Js.log("some other number")
}

The equivalent in JavaScript would be:

switch x {
case 0:
case 1:
   console.log("zero or one");
   break;
default:
   console.log("some other number");
}

Like conditionals, switches in ReScript are expressions and can be used


like any other expression. For example, their result can be bound to a variable:

let x = Js.Math.random_int(0, 5)
let y = switch x {
| 0 | 1 => true
| _ => false
}

18
Chapter 1 ReScript Basics

The JavaScript equivalent would be:

var y;
switch x {
case 0:
case 1:
   y = true;
   break;
default:
   y = false;
}

Patterns can also be used like bindings. When we use a name instead
of a literal in a pattern, it creates a binding with that name in the body of
the case:

let x = Js.Math.random_int(0, 5)
switch x {
| y => Js.log(y)
}

Be careful that the binding in the case does not accidentally shadow
another binding. For example, this switch statement that tries to check if x
and y are equal is incorrect:

let x = Js.Math.random_int(0, 5)
let y = Js.Math.random_int(0, 5)
switch x {
| y => Js.log("x == y")
| _ => Js.log("x != y")
}

19
Chapter 1 ReScript Basics

It outputs the following warnings:

unused variable y.

this match case is unused.

This is because the first case is the equivalent of adding the let y
= x binding, thereby shadowing the binding for y instead of comparing
against the value of y. Therefore, the first case is always hit, not just when x
equals y.
The previous incorrect snippet is equivalent to the following:

let x = Js.Math.random_int(0, 5)
let y = Js.Math.random_int(0, 5)
switch x {
| _ => {
   let y = x
   Js.log("x == y")
}
}

Let’s go over how we would correctly write the switch from the
previous example.
If we want to match against a condition that is not a literal value, we
cannot include the value in the pattern directly. Instead, we can specify
more complex conditions using the when clause in a case:

let x = Js.Math.random_int(0, 5)
let y = Js.Math.random_int(0, 5)
switch x {
| x when x == y => Js.log("x == y")
| _ => Js.log("x != y")
}

20
Random documents with unrelated
content Scribd suggests to you:
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
back
Welcome to our website – the ideal destination for book lovers and
knowledge seekers. With a mission to inspire endlessly, we offer a
vast collection of books, ranging from classic literary works to
specialized publications, self-development books, and children's
literature. Each book is a new journey of discovery, expanding
knowledge and enriching the soul of the reade

Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.

Let us accompany you on the journey of exploring knowledge and


personal growth!

ebookmass.com

You might also like