ReasonML & TypeScript: Comparing Their Type Systems

ReasonML & TypeScript: Comparing Their Type SystemsSeif GhezalaBlockedUnblockFollowFollowingFeb 1Illustration by evildesignA type is a labeled set of constraints that can be imposed on a value.

A type system analyses values in a piece of code, and validates them against their types.

JavaScript has a type system, but it’s dynamic.

This is one of the key advantages of using the language, providing an incredible productivity gain.

Yet, we recently started to find more and more use-cases for statically typed languages that compile to JavaScript.

TypeScript and ReasonML are two leaders of this trend that started a couple of years ago.

They both compile to JavaScript but have different type systems.

TypeScript was built as a superset of JavaScript, while Reason was built as an extension to the functional programming language OCaml.

In this article, we will put these two type systems under a microscope, studying some of their differences and similarities.

It’s in no way intended to be a dance-off between Reason and TypeScript, but rather a basic and objective comparison of their type systems (jokes on you if you were planning to call me out on this ????).

It’s also not a comprehensive analysis, and there are probably other interesting points of comparison that this article doesn’t treat.

To run the code-snippets in the article, I suggest to use the following tools:JavaScript: the browser console.

Reason: sketch.

sh.

TypeScript: the typescript playground.

Note: In Reason, all variables are declared with the let keyword.

In TypeScript, just like in JavaScript, you can use var , let or const .

Since this is not the focus of the article, I will be merely using let in all the examples.

Type checking timeYou can classify type systems into 3 categories, based on their time of type validation:Dynamic typing: JavaScriptDynamically-typed languages don’t check for the types until run-time.

Let’s look at the following example in JavaScript:let name = "John";At compile-time, only the declaration of the variable name is considered.

At that point, the variable does not have a type associated with it.

At run-time, we bind the value “John” with the variable name and the variable is then considered to be a string.

Static typing: ReasonStatically-typed languages check for types everytime the program is compiled and you get feedback about the type correctness even before running it.

Let’s try the same code snippet in Reason:let name = "John";In Reason, the variable name is associated with the type string before running the code.

You might ask: We didn’t specify any type here, so how can Reason determine that name is of type string?This is called type inference.

Languages such as Reason can infer the type of almost every value without you having to specify it.

let sum = (a, b) => a + b;If you run this example on sketch.

sh, you will see that Reason was able to determine that a , b and the return of sum are all integers.

Reason managed to do that because we used + , which is an operator specific to integers.

In fact, each type has its own specific operators.

Gradual typing: TypeScriptGradual typing lies somewhere in-between the two previous type systems.

let name = "John";If you hover over the variable name in the playground, you’ll see that TypeScript managed to associate name with the string variable.

In fact, TypeScript provides type inference as well.

let sum = (a, b) => a + b;If you hover over sum in the playground, you will see something like this:let sum: (a:any, b:any) => anyIn this case, TypeScript didn’t associate a particular type to either a , b or the return value of sum .

The types of the sum function’s arguments or return could be anything.

These types will be determined in runtime when executing the function.

This is due to the + operator, which is not specific to any particular type in TypeScript or JavaScript.

This gradual typing is exactly what distinguishes TypeScript from Reason.

In a gradually-typed language such as TypeScript, some declarations will have their types checked during compile-time and others will have their types checked at run-time.

Type annotationIn the previous section, we relied on the type system’s inference ability.

However, both TypeScript and Reason support explicit typing through type annotations.

They have exactly the same syntax:VARIABLE_NAME:TYPEHere’s an example that works in both TypeScript and Reason:let name:string = "John";For functions, you can annotate both the parameters and the return value.

Let’s look again at the sum example.

In TypeScript:let sum = (a:number, b:number):number => a + b;In Reason:let sum = (a:int, b:int):int => a + b;Common basic typesRegardless of how types are checked, TypeScript and Reason share the following basic types:Boolean: (bool in Reason, boolean in TypeScript)StringNumbersJust like in JavaScript, TypeScript has a single type for numbers.

let a = 1; // type: numberlet b = 1.

0; // type: numberReason distinguishes between Integers and Floats:let a = 1; // type: integerlet b = 1.

0; // type: floatReason has different operators for these types and won’t even let you perform operations between a and b unless you convert one of them to the type of the other.

a + b; ^Error: this expression has type float but an expression was expected of type int.

Strings and charactersBoth TypeScript and Reason share the string type.

However, Reason has an extra type for single characters: char.

To distinguish between them, a double-quoted text is considered to be of type string and single-quoted text is considered to be a character.

let someString = "hello"; // type: stringlet someChar = 'c'; // type: characterNon-existing valuesvoid & unitLet’s look at functions that don’t return anything.

How would you represent that in TypeScript or Reason?In TypeScript, the return type of the function is void .

let greetName = (name:string):void => { console.

log(`Hello ${name}`);}The equivalent of void in Reason is unit :let greetName = (name:string):unit => { print_endline(“Hello” ++ name);}null, undefined & optionLet’s implement a function that finds the first string item in an array that satisfies a given condition:let find = (arr: string[], condition: (item:string):boolean):string => { return arr.

find(condition);}Let’s play around with it:let array = ["foo", "bar"];// (1)find(array, item => item.

length === 3); // output: "foo"// (2)find(array, item => item.

length === 1); // output: undefinedIn the first example, a string item is returned just like we promised in our function annotation.

In the second example, an item is not found and undefined is returned instead.

This is still correct in TypeScript, since undefined is a subtype of all the other types.

In other words, you can assign undefined to a number or a string variable.

The same applies for null .

In these use cases, TypeScript handles non-existing values with either the undefined or null type.

Reason approaches this kind of non-exisitng values differently.

Our find function will have an option(string) return type.

Here’s how we would implement the same function in Reason:Note: For simplicity, the function receives a list here instead of an array.

A list is pretty much an immutable version of an array.

let find = (l:list, condition: (item:string):bool):string => { return List.

find(l, condition);}The returned value can then be handled through pattern matching to check whether a value is returned:let list = ["foo", "bar"];let condition = item => item.

length === 3;switch(find(list, condition)) { | Some(value) => print_endline(value); | None => print_endline("Not found");}TuplesA tuple represents a couple of values with specific types.

Here’s how we would define and use a type in TypeScript:let person: [string, number] = ["Marie", 24];Tuples exist in Reason as well and have a slightly different syntax:let person:(string, int) = ("Marie", 24);Records vs InterfacesIn real-world applications, we often deal with values that can’t be expressed with just the previously seen ones.

Restricting values to a certain shape provides more confidence in dealing with these values.

In TypeScript, it’s possible to create shapes of values through Interfaces:interface Car { color: string; year: number; brand: string;}let printCar = (car: Car):void => { console.

log(car.

color, car.

year, car.

brand);}printCar({ color: "black", year: 2017, brand: "Tesla" });The same can be achieved in Reason through Records:type car = { color: string, year: int, brand: string}let printCar = (c: car):unit => { print_endline(c.

color ++ string_of_int(c.

year) ++ c.

brand);}printCar({ color: "black", year: 2017, brand: "Tesla" });Variants vs EnumsValues are sometimes restricted to a specific set.

In TypeScript, it’s possible to define such sets through enums:enum Answer { YES, NO}let printResponse = (r: Answer): void => { if (r === Answer.

YES) { console.

log("Yes"); } else { console.

log("No"); }}The same can be achieved in Reason through Variants:type answer = | YES | NO;let printAnswer = (a: answer): unit => if (a === YES) { print_endline("Yes"); } else { print_endline("No"); };ConclusionThis was a very basic comparison between the two type systems.

Was it useful for you? Did you learn something about either TypeScript or ReasonML from this article? Do you disagree with anything explained?I would love to hear your feedback, thoughts or questions if you have any.

Notice: I publish articles every month.

Follow me on twitter @seif_ghezala if you want to interact with me directly and stay tuned ;).

. More details

Leave a Reply