Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

Lab 6 | Reason

CS17 Fall ’25

October 15, 2025

Objectives

By the end of this lab, you will know:

• Reason types

• Reason syntax

• how and when to use options

By the end of this lab, you will be able to:

• use VS Code and compile Reason code on the command line

• write recursive procedures based on built-in recursive types like lists

• write recursive procedures based on user-defined variant types

• pattern match in Reason

1 Getting Started in ReasonML

ReasonML is a syntax extension for the OCaml language created by Facebook. Reason was established to solve real-world problems in production-grade applications – in fact, Facebook has used ReasonML in Facebook Messenger for quite some time! Rather than creating an entirely new language, the creators of Reason chose to build on top of OCaml, a battle-tested functional programming language that’s been around since the late 1990s. Reason makes syntax improvements to native OCaml, and is also able to be compiled to readable JavaScript thanks to BuckleScript (within which Reason comes installed).

1.1 Installing ReasonML

First, install ReasonML if you haven’t installed it yet! We have a ReasonML install guide here.

1.2 Running Your First Program!

Note: You should do this step even if you have installed Reason, just to ensure there were no issues with the installation. If you installed Reason via Docker, open the Docker desktop app and start your container.

Now, let’s try to create, compile, and run our very first ReasonML program using BuckleScript-build (called bsb), one of the main ReasonML tools!

Using the terminal, create a directory for Lab 6 – preferably called lab06 – in the folder where you keep your CS17 work. Navigate into this directory in Terminal/Powershell using cd (change directory), ls (list subdirectories - this might not work if you’re using Powershell), and pwd (print working directory). Generate a ReasonML “project” using the following command:

bsb -init my-first-project -theme basic-reason

Make sure that you include the -theme basic-reason ! Otherwise, BuckleScript-build will think you’re attempting to write OCaml code.

Note: If you follow the Mac instructions on the install guide but get an error saying the command bsb is not found, try running sudo npm install -g bs-platform . It will prompt you for a password; type in the password you use to login to your computer and hit enter. It might not look like anything is being typed, but don’t worry – the terminal is just not displaying your password for security! After that, try rerunning this command: bsb -init my-first-project -theme basic-reason

You can also press the up arrow key to navigate your previously typed commands in the terminal, instead of retyping or copy and pasting every time. Went too many commands back? Press the down arrow key to scroll back down.

Once the bsb command has been run, cd into the folder that has just been generated in your lab06 directory:

cd my-first-project

We are now in our project’s directory! To compile our code (i.e., to turn it from ReasonML to Javascript, which we’ll never look at!) within the project we’ve just created, run the following command:

npm run build

This produces a file src/Demo.bs.js, which contains a Javascript program. Now we are all set to run our code! All of our ReasonML code is stored in our project directory’s src directory, as is the Javascript program. To run our program, type the following command (while still in the project directory):

node src/Demo.bs.js

The node program runs Javascript programs and prints out any output they produce. So you should now see the output of the default ReasonML code contained in the project’s src directory!

1.3 Experimenting with ReasonML

So far, we’ve written and run all our code in an Integrated Development Environment, or IDE, called DrRacket. This means that we could run our code in the same window that we were writing it in. But sadly, as we move on to ReasonML, we have to say good bye to our friend DrRacket. Instead, for this lab, we will be running our code on the website RePlay, which is an interactive ReasonML playground that allows you to write ReasonML code with inline evaluation! Soon, however, we’ll start to use VSCode, an IDE that supports many different languages (ReasonML being one of them), and which looks like DrRacket on steroids — lots more menus and buttons. On the good side, there’s still a place to edit programs, and still a place just below it to see the results of running those programs, so it’ll seem at least somewhat familiar.

Returning to RePlay, to get started, open up a web browser, and go to the RePlay website. Wait a few seconds for the website to load, and then click on New Playground in the top right.

Now you can either copy in any existing ReasonML code you may have written, or write new code altogether! Since you don’t yet have any existing ReasonML code, let’s go ahead and write some. Type in the following lines of code:

let a = 17;

let b = a ;

a + b ;

To save your program to a file, copy and paste your code into a text editor such as Visual Studio Code, and save the file. Give your file an informative name, too, such as Test.re (all Reason files should end with the extension .re).

Note: You cannot save any of your code in RePlay; it will disappear after you close your RePlay tab.

We highly recommend that you copy code into a VSCode file right before running it, so that you don’t accidentally lose your work!

Note that in ReasonML, we use camelCase formatting, meaning that all of our files will begin with capital letters and every additional word in the filename will also start with a capital letter (don’t separate words with spaces, dashed, underscores, etc.), i.e. TestFile.re , ReasonTest.re . All procedures, arguments, etc. will start with a lowercase letter, but additional words in the procedure or argument name will still begin with a capital letter, i.e. testProcedure , testArgument , etc.

Now, let’s return to RePlay and run your program. Simply click on the Run button at the top-left of the webpage and observe what is outputted! As you can see, RePlay helpfully annotates each line! It lets you know that a and b were both set to the integer 17, and that the output of a + b is the integer 34.

Now, let’s try entering a bad program. We might, for example, refer to a procedure that doesn’t yet exist:

factorial (5) ;

Note: When you finish writing a program, make sure to save it in the src directory of the Reason project in your lab06 directory as a .re file

After typing this in, let’s check for errors by running the code. You should now see an error message showing what caused the problem. RePlay should also point to the line contains the error. Let’s try something else, like making a spelling mistake:

let five = 5;

faive + 6;

Again, you will see a similar error message, but at the end, it will also helpfully ask “Did you mean five?”

✜ You’ve reached a checkpoint! Please call over a lab TA to review your work.

2 Reason Basics

2.1 ReasonML Types

Like Racket, ReasonML has data types too! In addition to the types you are familiar with, like lists, strings, and bools, Reason also distinguishes between integers like -78 and floating point numbers like 32.87 (ints and floats respectively).

Task: For each ReasonML expression below, state the type of its value (not its value). For example, 3 has the value 3, but the type int .

If there is a type clash, meaning the expression’s type is not well-defined, explain why. Please use the ReasonML interpreter only to check your answers.

1. 3;

2. 3.0;

3. 3 + 3.0;

4. 3 / 2;

5. x => x / 2.0;

6. [1, 2, [3, 4]];

7. (17, ["shiba", "labrador", "pug", "corgi"], (false, true));

8. [(1, 2, []), (3, 4, [5, 6, 7])];

9.

if ( test ) {

1

} else {

false

};

10.

let createThreeElementList : int = > list ( int ) =

x = > [x, x, x]

2.2 Reason Loves Types – and So Should You!

Unlike Racket, Reason rigidly enforces types: if a procedure expects an integer argument, and you pass it a boolean, ReasonML won’t even let you compile the program, let alone run it.

Clarifying your types helps the user of your function to know what types of arguments to put in and what order they go. We’ll do this by annotating the function name in a way that looks a lot like the type-signatures we wrote in comments in Racket.

For example:

let rec factorial : int = > int =

x = >

switch ( x ) {

| 0 = > 1

| 1 = > 1

| _ = > x * factorial ( x - 1)

};

The int => int following the name factorial tells you that factorial is a procedure that takes as input an integer and outputs an integer. This procedure requires that you give factorial an int . If you give it a float – or any other type, for that matter – it will produce an error.

Note: We require you to type annotate all of your functions. You can actually annotate just about anything in a ReasonML program, so if you wanted to add some detail, you could write the code above in the below form, where we’ve annotated the argument to the procedure and one of the possible return values.

let rec factorial : int = > int =

( x : int ) = >

switch ( x ) {

| 0 = > 1

| 1 = > 1

| _ = > (( x * factorial ( x - 1) ) : int )

};

3 Reason Syntax

3.1 Pervasives Library

ReasonML has lots of procedures and types predefined for you to use. Most of them are contained in libraries. To make use of procedures defined in libraries, you need to use a directive, a command that tells ReasonML to import the library. There is one exception, however: the Pervasives library is preloaded when you start ReasonML, so the definitions in that library are immediately accessible.

The Pervasives Library, in essence, contains the procedures that you can use without defining them yourselves (“Reason Builtins”, so to speak). You can find out which procedures are defined for you here.

3.2 Reason Syntax Practice!

Task: Below, we have given you some expressions in Racket. Convert them to ReasonML, and then execute them by running them in RePlay to ensure that your code is correct. Look through the Pervasives library to help you write some of the expressions!

( + 17 18)

( * ( + 19 21) ( - 4 3) )

( equal? 3 4)

( >= 0 -1 )

( min 2 .5 -0.6 )

(and true false )

(or true true )

( not (and true (or false false ) ) )

✜ You’ve reached a checkpoint! Please call over a lab TA to review your work.

4 Options (Not Optional)

Recall that in Bignum you described that a bignum was a list of ints, but that was all in comments. In ReasonML, you can actually give names to types, like this:

type bignum = list ( int ) ;

let rec bnPlus : ( bignum, bignum ) = > bignum =

...

Then, as shown above, you can use those types in your programs!

And in Racket, there’s a type, bool, for which there are just two possible values, true and false, right? But when we wanted to talk about base-pairs in DNA, we had to use strings for A, T, C, and G, even though the most natural thing would be to have a “base” data type that could be one of four different values, and only those values. Well, ReasonML lets you define new data types with a fixed number of possible values. The names for the values have to be capitalized, so that for seasons, for instance, you could write

type season = Fall | Winter | Spring | Summer ;

to say that a “season” can be Fall, Winter, Spring, or Summer (the vertical bar is read as “or”). And those four names are called “constructors” for the type. These things (types that have an “or bar” and multiple constructors) are called “variant types” or “or-types”.

You can go further, though: you can define constructors that take some data, like this one, that says you can build a “Student” from a string (the name) and an int (the grade):

type student = Student ( string, int ) ;

let s = Student (" Spike ", 42) ;

Finally, you can build types that depend on an unknown type like 'a, like this definition for a “pair of somethings”:

type pair ('a ) = ('a, 'a ) ;

let s = ("hi", "bye ") : pair ( string ) ;

This almost works – ReasonML needs a little help figuring out what you’re type-annotating, so you have to write this instead:

type pair ('a ) = ('a, 'a ) ;

let s = (("hi", "bye ") : pair ( string ) ) ;

Types like this, which involve a parameter like 'a, are called “parameterized types.”

All this was a long-winded lead-in to a particular parameterized variant type that gets used a lot in Rea sonML: options.

Options are an important example of a parameterized variant type that is built in to ReasonML:

type option ('a ) =

| None

| Some ('a ) ;

In words, an option('a) is either a None or a Some applied to an argument of type 'a .

Options are useful when writing a procedure that might not produce a value, such as a procedure that looks up a word in a dictionary, since that word might not be in the dictionary!

Formally, a dictionary is a set of key-value pairs. For instance, in an English language dictionary, the keys are words and the values are the corresponding definitions. If you look up a word that is in the dictionary, then what you find is the corresponding definition. But if you look up a word that is not in the dictionary, then you don’t find anything at all.

What should a procedure that looks up keys in a dictionary produce when the key is not found? One possibility is the string "key not found" . But what if there is a word in the dictionary whose definition is precisely "key not found" ? If such a definition is possible, how could you distinguish between a successful and an unsuccessful lookup? A better alternative for when lookup fails is to produce a special value that cannot possibly be confused with a definition – this is exactly what options allow you to do. Let’s look at an example of this!

First, here is a data definition for dictionaries:

type dict ('a, 'b ) = list ((' a, 'b ) ) ;

/* Examples of dict */

[(1 , 1) , (2 , 4) , (3 , 9) , (4 , 16)];

[(1 , "one") , (2 , "two") , (3 , " three ")];

Then, if you have a procedure lookup , which consumes a dictionary and a key, and produces an option of the corresponding value, here are some examples of what that procedure would produce:

lookup ([(1 , 1) , (2 , 4) , (3 , 9) , (4 , 16) ], 3)

= > Some (9)

lookup ([(1 , " one") , (2 , "two ") , (3 , " three ")], 2)

= > Some ("two")

lookup ([(1 , " one") , (2 , "two ") , (3 , " three ")], 4)

= > None

5 The Design Recipe, in Reason!

The design recipe carries over more or less exactly from one programming language to another. The only difference between applying it to Reason, as compared to Racket, is that the type signature does not only appear in a comment, but it also appears as actual code.

In particular, the call structure of every procedure you write in Reason should be annotated with the types it consumes and the type it produces.

For example, here is the call structure together with the type signature for the flip procedure in ReasonML:

let rec flip : list (( string, string ) ) = > list (( string, string ) ) = alop = >

...

As for testing, your old friend checkExpect , and a new tool, checkWithin are available for all your testing needs! You can use checkWithin to check the value of floats. To use checkWithin , pass in the procedure and its arguments, the expect result, and the tolerance, i.e. the decimal value to which we want to test the accuracy. For both checkExpect and checkWithin , there is also another argument – a string, which you should use in order to describe what is being tested. In order to use checkExpect and checkWithin , you’ll need to include the file CS17setup.re within your src folder (which we will provide to you whenever necessary), and include the following line at the top of each ReasonML file:

open CS17Setup;

In ReasonML, checkExpect and checkWithin are in camelCase, and are used like this:

checkExpect(<actual>, <expected>, <description>)

checkWithin(<actual>, <expected>, <within>, <description>)

For example, here’s the flip procedure in ReasonML, developed by following the design recipe. Notice how much shorter it is than its Racket counterpart, because we can use tuples!

open CS17Setup ;

/* flip : list (( string, string )) -> list (( string, string ))

Input : a list of pairs of strings, alop

Output : a list where the ith element is in the same position,

but the order of its elements is reversed */

let rec flip : list (( string, string ) ) = > list (( string, string ) ) =

alop = >

switch ( alop ) {

| [] = > []

| [( a, b ) , ... tail ] = > [( b, a ) , ... flip ( tail )]

};

/* Test cases for flip */

checkExpect ( flip ([]) , [], " Test empty case for flip ") ;

checkExpect ( flip ([(" hello ", " world ")]) ,

[(" world ", " hello ")],

" Test one - pair case for flip ") ;

checkExpect ( flip ([("i", " love ") , (" reason ", " programming ")]) ,

[(" love ", "i") , (" programming ", " reason ")]

" Test multi - pair case for flip ") ;

Note: Since we are writing all of our code in RePlay for this lab, you will be unable to use checkExpect or checkWithin . Instead, simply write calls to your procedure (that cover each of the various general, base, and edge cases) at the bottom of your code, and RePlay will tell you what they output!

Note: One more thing: our checkExpect is not as sophisticated as the check-expect of DrRacket. If your test produces an error, checkExpect won’t give you a nice explanation of how it failed to match the expected value, etc. – it’ll just report an error, and print out the description that you passed in as the final argument. Still, it suffices for testing a lot of examples very well.

6 Procedures

Now, it’s your turn to write some simple procedures! Make sure to include the design recipe with all of the procedures that you write in this section. Note that you must also include the types in the call structure of each procedure.

Task: Follow the design recipe to implement a procedure, addOne , that takes in an integer and returns an integer that is one greater than the input. Here are a few examples:

addOne (5) = > 6

addOne ( -3) = > -2

addOne (0) = > 1

Task: Follow the design recipe to implement a procedure, convert , that converts degrees in Fahrenheit to degrees in Celsius. convert should take in a float, and output a float.

Hint: C = 9/5 (F − 32), where C denotes degrees in Celsius, and F denotes degrees in Fahrenheit.

Task: Define a type, base , that has one of the four values A , C , G , or T .

Task: Follow the design recipe to implement a procedure, complement , that takes in a base and returns its complement.

Note: A and T are complements of each other, and C and G are complements of each other.

Task: Convert the following procedure, fibonacci , from Racket into ReasonML. Make sure to follow the design recipe!

( define ( fibonacci x )

( cond

[( = x 0) 1]

[( = x 1) 1]

[( > x 1) ( + ( fibonacci ( - x 1) ) ( fibonacci ( - x 2) ) ) ]) )

Note: Check the lab slides to see how we implemented equalsOne , and use that as a starting point for fibonacci ! Also note that this is a recursive procedure, so you will need to use let rec to define it.

✜ You’ve reached a checkpoint! Please call over a lab TA to review your work.

7 Just for Fun: List Recursion!

In programming, as in natural languages, when one learns a new language, the tendency at first is to try to write programs in an old language, and then translate those programs into the new one. Sometimes this fails outright; more often it is possible, but bad style.

We’d like you to avoid the temptation to “write Racket programs in Reason.” So in this problem we are explicitly forbidding the use of List.hd and List.tl , Reason’s equivalent of first and rest . You must use pattern matching instead.

Here is the template for writing procedures that recur on lists in ReasonML:

let rec proc : list ('a ) = > < return -type > =

alod = >

switch ( alod ) {

| [] = >

| [ head, ... tail ] = > ... head ... ( proc ( tail ) ) ...

};

And here is the familiar length procedure:

let rec length : list ('a ) = > int =

alod = >

switch ( alod ) {

| [] = > 0

| [_, ... tail ] = > 1 + length ( tail )

};

The use of the underscore in the second pattern says ‘match anything, but don’t bother giving a name to the matched thing, because we won’t be using it.’

Task: Write the familiar procedure sumList in Reason. This procedure consumes a list of int s and produces the sum of the int s in that list.

Now that you’ve written sumList recursively, you might be wondering if we could use a higher-order procedure to write it. Thus, if we wanted to rewrite sumList using List.fold_right , it would look like this!

/* sumList : list (int ) = > int

Input : a list of integers, aloi

Output : the sum of all the integers in aloi */

let sumList : list ( int ) = > int =

aloi = > List . fold_right ((+) , aloi, 0) ;

/* Test cases for sumList */

sumList ([]) ; /* 0 */

sumList ([1]) ; /* 1 */

sumList ([1 , -1 , 2 , -2 , 3]) ; /* 3 */

Note: List.fold_right is the ReasonML analogue of foldr in Racket. It takes in arguments in a different order than foldr – check the Lists documentation here for more info!

✜ You’ve reached a checkpoint! Please call over a lab TA to review your work.