Modules group related methods, exceptions and types into a package. In addition, Reason provides module interfaces that make it easier to swap out one module for another module that fulfills the same interface.
In contrast to type
s like variants and records,
a module
must begin with a capital letter. A variable that begins
with a capital letter often indicates it is a module
.
A module declaration looks generally like this:
module <CapitalizedName> : <ModuleInterfaceType> = <implementation>;
.
The ModuleInterfaceType
is optional as it is in Reason's type annotation
in a let binding
:
let <variable name here> [: optionalTypeAnnotation] = "concreteValue"
.
Here is a basic example of a Person Module:
/* loading */
In software, requirements evolve over time with feedback from a product's owner(s). It often happens that a system needs a new feature that the original engineers did not anticipate. This becomes especially complicated in large projects. For example, if you need to modify components A and B to provide a new feature, and components C-M depend on A while components G-Z depend on B, it can become difficult to figure out what will break if the engineers that built A and B are no longer around. This problem gets worse if clients rely on direct implementation details.
One strategy to is to ensure you can always swap out a component with another "of roughly the same size and shape," that is, with a component that fulfills the contract it currently guarantees its clients. As long as the interface that a component's clients use stays the same, the implementation is free to change.
Let's say you are an engineer for a social networking site. Your product's component A used the Google Maps API to display a map of where users are logged in right now. It didn't use an interface and instead exposed the Google Maps data structures to its downstream components. Other coders ended up using Google Maps' data structures directly in the components they wrote, since that was the easiest thing to do. Then your product owner decided to extend your product's prescence in China, where Google Maps API doesn't work. Now you have to go into all of those downstream components and re-engineer how they are storing data ... ouch. Instead, you should have used an interface hiding how the information was stored, and if you need to switch out a different API provider, you won't have to change as many of your components.
ReasonML's type checking ensures two things about the relationship between
module
and module type
expressions:
module type
declares a method, type or
other entity, then the module
that conforms to that interface must implement
that entity.module type
for a module,
clients can't see that part of the implementation.In the context of ReasonML, the terms interface, signature, and module type are all used interchangeably.
/* loading */
Everything in the module type
must also be in the module
. We left out y
from
the module M1, and it wouldn't compile.
/* loading */
By declaring M2
to conform to interface S2
, then everything not in
S2
is "private" to the inside M2
. Here, we tried to get the "private"
property y
outside of M2
's closure, so Reason threw a compile time error.
This exercise is Steffen Smolka's, taken from Cornell's CS 3110 Lecture 7 Lab.
Write a module implementing the following Fraction
module type:
/* loading */
Your module should ensure that make
, add
, and mul
return numbers
in reduced form
where the denominator is positive. See the Gradus Reason step
Names and Functions
for a sample implementation of Euclid's Algorithm.
Module
Image Credit: Port Birch South - street 1 by cimddwc on Flickr