We found ourselves having to decide between TypeScript and ReScript very pretty early in our journey. In this blog post, we'll delve into why ReScript emerged as a better fit for Hyperswitch, an open-source payments orchestrator that enables seamless integration with multiple payment processors via a single API.

GoodBye JavaScript: Moving Towards Type safety

JavaScript's absence of a type system makes it an excellent choice for rapid prototyping. However, it also renders the language prone to errors due to its lack of enforced types. 

I love JS, Why should I bother about Type Safety?

We as developers frequently encounter the challenging task of establishing and overseeing types throughout the application, aiming for a seamless process of error detection and adaptation to changes.

Without a robust type safety system, making basic alterations to your database's structure, your API layer's object definitions, or even how your web client perceives that object could lead to the absence or deformation of data somewhere along the sequence of instructions if each point of contact isn't updated correctly.

Action Time: Show, Don't Tell – A Concrete Example Unveiled!

For example, let's say you have a web app where you have to process user's contact information. Without a robust type safety system, if you decide to change the format of how phone numbers are stored in the database (like switching from a string to a numerical format), and you forget to update the API layer and the web client accordingly, you might encounter issues.

The web client may still use phone numbers as strings, expecting them to be stored as strings in the database, but the database is now configured to send numerical values. This mismatch in expectations could lead to breaking of application, as you may be expecting it to be a string and processing it likewise.

A robust type safety system would help catch these inconsistencies early on, ensuring that changes to data formats are uniformly applied across all parts of the system, maintaining data integrity and preventing such issues.

Simultaneously, the absence of types increases the chances of runtime code breakage with each modification. In larger codebases, this scalability of errors significantly hampers maintenance efforts and time-to-market efficiency.

Imagine you're working on a shopping website. There's a function that calculates the total price of items in a shopping cart. Now, you want to update it to include a new type of discount, like a percentage off.

Without type safety, if the function previously expected only integers and you now accidentally pass a discount value as a string or a different data type, this change could break the code during runtime. The absence of type checking makes it challenging to catch such errors before execution.

Now, imagine this in a larger codebase with numerous interconnected functions and components. Without a type system to catch potential issues early on, the risk of breaking code in various parts of the application significantly increases.

Safeguarding Success: Unveiling the Power of Type-Safe Languages

Numerous approaches exist for implementing a type system in frontend development. Some notable options include TypeScript, ClojureScript, PureScript, Scala.JS, Elm, JS_of_ocaml, ReScript, and the possibility of writing frontend code in languages such as C or Rust, facilitated by WebAssembly and JS bindings like wasm-bindgen. 

Breaking the TypeScript Mold: The Reasons Behind Our Unconventional Path

We at Hyperswitch considered using TypeScript as it is widely adopted in industry. However there were certain concerns which tilted our decision in favour of Rescript.

What is ReScript?

ReScript, a new language, takes the best parts of JavaScript and OCaml, offering a compiler that translates your code into JavaScript while benefiting from OCaml's strong type system. Over the last four decades, OCaml has matured as a language with a highly robust type system.

In simpler terms, think of ReScript as a syntactic sugar on top of OCaml just like JSX is for JS.

So, even though you enjoy a more straightforward syntax, you're essentially working with the power of OCaml under the hood.

Fast Compilation: Accelerating The Development Experience

In the case of large codebases such as ours, the compilation time for TypeScript tends to be excessively prolonged especially for a monorepo with thousands of lines of code, resulting in a poor developer experience. Recognizing this challenge, we were aware that our project's expansion would inevitably escalate compilation times. In contrast, ReScript boasts remarkably swift compilation, mitigating the issues associated with prolonged compilation times. Thanks to Rescript, It stands out as one of the fastest compiler & build system for JS development.

ReScript: Subset of JS, TypeScript: The Flip Side!

TypeScript aims to encompass the complete JavaScript feature set and extend it further, reflecting a commendable goal. In contrast, ReScript focuses on a carefully curated subset of JavaScript. 

While JavaScript supersets are expected to expand over time, ReScript remains committed to its streamlined approach.

Also, any valid JavaScript (JS) code is also considered valid TypeScript (TS). However, since JavaScript has certain drawbacks, TypeScript inherently supports those aspects as well.

ReScript's type safety is seamlessly integrated into the development process and guaranteed 100%.

The migration process to TypeScript takes a "breadth-first" approach, where you enable it for all files and add annotations throughout. However, determining the extent of gained type safety and measuring it can be challenging.

In contrast, migrating to ReScript follows a "depth-first" approach. Each piece of converted ReScript code is entirely clean. You convert one file at a time, and each conversion consistently enhances your safety.

ReScript feels like Functional Programming

What is functional Programming?

it's a style of writing code where functions are treated as first-class citizens, meaning they can be passed around and used as arguments just like other types of data. The focus is on using pure functions, which means they produce the same output for the same input and have no side effects.

Examples include Haskell, Lisp, Scala, and JavaScript (to some extent, with features like higher-order functions and closures).

We at Hyperswitch embrace functional programming to the core.TypeScript isn't exactly a functional language; it's more like bringing Java-style typing to the browser. While you can try to add functional features, it often leads to complicated and over engineered code. Instead of simplifying things, TypeScript can make them more challenging, whereas functional programming aims to make things simpler. On the contrary, ReScript offers much simple and robust functional programming approach. 

Some may argue that writing plain JavaScript (JS) and TypeScript (TS) in a functional style is a viable option. However, it's essential to recognize the distinction between opting for a functional style and utilizing a functional language that has evolved over a span of 40 years.

Ok, What else ReScript has to offer?

There are no Null and Undefined in Rescript!

The type system can't fix every issue in our code; it won't catch mistakes in how we think about our program (logic bugs). However, it does help with some cases. In JavaScript, null and undefined can sneak into our code and cause unexpected issues. TypeScript doesn't completely solve this problem either. But with ReScript, there's a difference - it doesn't allow undefined or null values.

In ReScript, every value must have the correct type. When there might be no value or an expression could result in an error, ReScript uses variant types. These are like options (think of Haskell's Maybe) and results. They carry a value or information about its absence or an error.

Additionally, the ReScript compiler ensures that we account for negative scenarios through pattern matching, making our code more robust.

Dive into Examples for a Deeper Grasp!

Let's understand with the help of an example. We use something called the option type to show whether there's a value or nothing at all. In simpler terms, it's like putting the value inside a box labeled "Some" if it's there, or leaving the box empty, labeled "None," if there's nothing. So, an option type is a way of saying, "Here's something, or there's nothing at all."

Let's consider a function written in Rescript as below. This function accepts a parameter personHasACar and based on that value is assigned to a variable licenseNumber

Later on, when we unwrap and use such an optional value, it'd be forced to handle both cases i.e when a value exists and when it doesn't through pattern matching. If you don't handle all the cases a warning will be raised like below.

Below is the compiled JS code by Rescript for the example discussed earlier. Note the unwrappedValue is always neither null nor undefined.


Dead Code Elimination: The Dead Code Slayer You Want!

It's important to note that the ReScript output isn't the final production bundle. Instead, the ReScript compiler provides JavaScript files.To create the actual bundle, we use a tool like Webpack.

The best part is, our output consists of clean and easily readable JavaScript files, and there's no JSX involved. Additionally, ReScript takes care of dead code elimination, meaning all unused code is removed in the output files generated by ReScript. While developing Hyperswitch SDK we were highly attentive to the size of JavaScript bundle. If you are also a JS bundle size phobic. Rescript is perfect for you.

Rescript : The Seasoned JS Code Optimizer!

We as developers are often lazy writing highly optimized JS code. At the same time it is not so easy to achieve high level of low level optimizations. But, Rescript got us covered. ReScript's type system and compiler act as a natural guide, steering you towards code that is inherently performant by default. This includes effectively leveraging various Just-In-Time optimizations such as hidden classes, inline caching.

Again Let's take an example

Consider the below function written in Rescript to calculate factorial of a number, it accepts two arguments n i.e. the number whose factorial is to be calculated and other ans i.e. the result of factorial computation.

Now let's see what Rescript compiler makes out of this code.Below is the output JS code generated by Rescript.

ReScript supports tail call optimization (TCO), which means that certain recursive functions are optimized to avoid stack overflow errors. The compiler transforms the recursive call into a loop, optimizing the memory usage. Rescript is capable of making many more optimizations like this under the hood. 

Rescript Clear Winner?

Not really, We used Rescript because we love functional programming. Moreover we choose Rescript over TypeScript as it is possible to leverage the power of vast, React ecosystem and widely used tools such as Webpack and have better type guarantees and more functional code than TypeScript. 

The icing on the cake is that it prevents any unintentional efforts to mess up type safety, providing support even for those who are newer to the team.

But, if Object-Oriented programming is your thing, then TypeScript is the recommended choice.