TypeScript vs PureScript: Not all compilers are created equal
Tags: typescript, functionalprogrammi, purescript, javascript
Published: 2019-07-08 19:09:20

---
title: TypeScript vs PureScript: Not all compilers are created equal
published: true
tags: typescript,functional-programmi,purescript,javascript
canonical_url: https://blog.logrocket.com/typescript-vs-purescript-not-all-compilers-are-created-equal-c16dadaa7d3e
---

![](https://cdn-images-1.medium.com/max/1024/1*jemvx9kY-VXKlaSKGtqTlw.png)

There are many ways to skin a cat, and for each one there is a **statically typed language** that compiles to JavaScript. Among the most known we have _TypeScript (TS)_, _PureScript (PS)_, _Elm_, _Reason_, _Flow_ and _Dart_. But why are there so many? Why do they exist and what is their rationale? In this post, we’ll take a look at the first two languages. We’ll analyze their similarities and differences, see how they approach different situations and understand what guarantees they provide.

[![](https://cdn-images-1.medium.com/max/1024/1*GPjaPKNNUYHU8EsA3Z0JGA.png)](https://logrocket.com/signup/)

### Types, restrictions and guarantees

All languages have types, even the most permissive ones such as JavaScript (JS). At some point, there is a mapping between the logical values of our program and their physical representation. Knowing how they are translated will help us understand things, like why in some languages 0.1 + 0.2 != 0.3. Languages like _JavaScript_, _Python_ and _PHP_ are **dynamically typed** , which implies that when the code is executed and there’s a problem with the types, the interpreter will need to decide whether to coerce the values or throw a runtime error.
```jsx
"Hello" + 1 // "Hello1"
null.f()    // TypeError: Cannot read property 'f' of null
```
Coercing string and numbers can be really handy when creating messages, but as the epic talk [WAT](https://www.destroyallsoftware.com/talks/wat) by Gary Bernhardt shows, it can get weird really fast which can lead to unexpected errors.

In contrast, statically typed languages such as _TypeScript_ or _PureScript_ make us think about types explicitly. Most languages will infer most of the types so we don’t have to be too verbose, but at some point, we’ll have to provide some information about the _data_ we want to compute, and _how_ we are going to compute it. That information will help other programmers (or even our future self) understand the code, and it will allow our tooling to give us information and warnings, apply automatic fixes and even assist with refactoring. If there is a problem with the program, we’ll have an error at compile time, so the feedback loop will be shorter.

Each language can introduce different restrictions that impact the way we program. These restrictions will give us certain guarantees that will increase our confidence in the code. For example, if the language doesn’t allow us to use _null_, we’ll have a guarantee that we won’t have _NullPointerExceptions,_ the billion dollar mistake, and we’ll probably need a different concept to represent failure or emptiness.

### TypeScript vs PureScript

[_TypeScript_](https://www.typescriptlang.org/) is a language created by Microsoft in 2012 with the idea to help developers work with large-scale applications. It is a _JavaScript_ superset, which means that a valid _JavaScript_ program is a valid _TypeScript_ program. This decision tells us a lot about their rationale, instead of creating new language features (e.g. traits, pipe operator, pattern matching, etc) _TypeScript_ focuses on adding ways to type existing _JavaScript_, closely following the specification updates. It’s stated very clearly in their latest [roadmap goals](https://github.com/Microsoft/TypeScript/issues/29288), when they say “Types on every desk, in every home, for every JS developer” and “Productivity through strong tooling”.

[_PureScript_](http://www.purescript.org/) is a language created by Phil Freeman in 2013 and it’s maintained by the community. It is a strict, purely functional language inspired by _Haskell_. As such it provides many features and restrictions aimed to improve code correctness and developer productivity, such as _immutability_, _pattern matching_, _currying_, _type classes_ and _do expressions_ among others. It uses _JavaScript_ as the main compilation target because of the benefits of running on the web, server, mobile and even [google sheets](https://twitter.com/sherman3ero/status/970034089813315584), but it can also compile to C, C++ and even Erlang.

_TypeScript_ took off in 2015 when [Angular announced](https://devblogs.microsoft.com/typescript/angular-2-built-on-typescript/) that it was building its second version with it. The decision to closely follow JS, the developer experience from using tools like VSCode and the confidence given by embracing its restrictions, encouraged other teams to rewrite big projects like Vue, Jest and Yarn. According to the [State of JS 2018](https://2018.stateofjs.com/javascript-flavors/typescript/), TypeScript adoption doubled from 2016 to 2018. All of this resulted in an explosion of learning resources and a big, healthy ecosystem.

![](https://cdn-images-1.medium.com/max/1024/0*RZ94nP6TEbnkmPkZ.png)

_PureScript_ is not that popular in comparison, but functional programming, in general, has caught the eyes of many developers. Languages like PHP or Java added [lambda expressions](http://tutorials.jenkov.com/java/lambda-expressions.html) which enable the use of higher-order patterns, and the popularity of libraries like _React_ or _Redux_ helped people adopt pure functions and immutability. Other languages such as _Elm_ have bigger communities and are a really good starting point in the functional world, but _PS_ has some nice features that we’ll analyze in the post. Despite being small, the _PureScript_ community is very active in the [functional programming slack](https://fpchat-invite.herokuapp.com/) (#purescript channel) and in its [discourse page](https://discourse.purescript.org/).

### Dissecting the output

A compiler transforms a higher level language into a lower level language, for example, _C_ and _GO_ compile to _machine code_ that can be executed directly on a device, _Scala_ and _Kotlin_ compile to _Java ByteCode_, intended to be run in the _Java Virtual Machine_ (JVM), and _TypeScript_ and _PureScript_ compile to _JavaScript_. The difference between the previous examples is that both the _machine code_ and the _Java ByteCode_ are very low-level binary languages while _JavaScript_ is a high-level language that still needs to be interpreted by an engine like Chrome’s V8 or Firefox’s SpiderMonkey.

In this section, we’ll analyze the result of the compiled output of a simple hello world module. In both cases, we’ll export a _main_ function that prints two lines in the console and uses a helper private function. The _TypeScript_ source pretty much resembles the compiled output. Notice that the type information is removed and some module code is added, but other than that, the code is the same.

![](https://cdn-images-1.medium.com/max/1024/0*7YLP9fwFVgw06GG5.png)<figcaption>TypeScript with commonjs module and es5 target</figcaption>

**_TypeScript_** has many [compiler options](https://www.typescriptlang.org/docs/handbook/compiler-options.html) that can increase or decrease the strictness level and change how the output is constructed. For example, the _target option,_ which defaults to _es5_, allows us to use newer language features such as [arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), [async-await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) and [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) in older browsers. Another option is _module,_ which we can use to best suit our build process. By default, it uses _commonjs_, which is the default module loader in _Node_ and it can also serve as the input for _Browserify, Webpack_ or _Parcel_. If we set the option to _es6,_ then the output will resemble the input even more because we are using [es6 imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import), that can be later fed to tools like rollup.

Even if both examples do exactly the same, they don’t resemble too much. That’s because _PureScript_ tries to look more like _Haskell_ than _JavaScript._ Coming from a _C-family_ language, this syntax can seem strange, but we’ll explain it as we go. For now, notice that the type information is also missing from the output. Being **static** means that all type checks are performed at compile time and don’t incur in runtime overhead. By default, PS uses _commonjs_ as its module definition, so you can use it in _Node_ directly or feed it to an external bundler. You can also instruct the compiler to bundle all your files using globals.

![](https://cdn-images-1.medium.com/max/1024/0*ekGV4J9Lzb3xlcFh.png)<figcaption>PureScript hello world compiled</figcaption>

The compiled code doesn’t look like something we’d write as our first choice. It has a lot of weird words like _Semigroup_, _Bind_ and _Effect_ and we can see it has an extra level of indirection inside the _main_ function, where we first create a _computation_ using _Effect\_Console.log(“Hello”)_, and then immediately execute it using _()_. This indirection is due to a restriction imposed by the language. As its name implies, PureScript code must be pure. It’s not obvious here, but this restriction will allow us to compose and extend our computations, building complex features out of simpler ones.

The pureness restriction gives us powerful guarantees. We said both examples do exactly the same, and at this moment they do nothing (at least not by themselves). In both cases, we are creating a module that exports a _main_ function, and that’s it. If we want the code to actually run we should, at some point, call _main()_. In _TypeScript_ we could’ve added the invocation in the same file, after all, it doesn’t impose us the purity restriction. _PureScript,_ on the other hand_,_ forbids us from doing it, thus it assures us that importing a module can’t result in executing unknown side effects, such as connecting to a database. A library such as [colors](https://github.com/Marak/colors.js#usage) could use the freedom JS/TS gives to “improve its syntax” by automatically patching the _String.prototype_ when you import the library. Introducing new properties to _String.prototype_ could seem innocuous at first, but as the [smoosh gate](https://developers.google.com/web/updates/2018/03/smooshgate) showed us, it could become a problem.

The purity assurances come with a cost. To interact with existing _JavaScript_ from _PureScript_ we need to create bindings using the [Foreign Function Interface](https://github.com/purescript/documentation/blob/master/language/FFI.md), and make sure all impure code gets wrapped. _TypeScript_, being closer to the metal (if you can call JS a metal), just requires us to provide typing information, and we have the freedom to choose when we want to be pure, and when we don’t.

### Expressing types

In order to let other users and tooling know what your data and functions look like, we need to provide type information. _TypeScript_, being a _JavaScript_ superset belongs to the _C-family_ syntax, in which values, keywords and type information are intertwined in the same expression. Among the [basic types](https://www.typescriptlang.org/docs/handbook/basic-types.html) we have _JS_ primitive types, that doesn’t make a distinction between float types and integer types, there is just _number_.
```jsx
const PI: number = 3.1416

let age: number = 32
```
Another common C convention is that identifiers such as _PI, SOME\_REGEX or API\_URL_ are written in uppercase to indicate they are constant values (as if the const keyword wasn’t enough). Keep in mind that for complex types, constant values are not the same as immutable values. This example is overly verbose and could be simplified. The compiler can infer from the value that the type is _number,_ so there is no need to be explicit, in here we’re just showing the complete syntax.

If we recall the _exclaim_ function, we can notice that only the input was typed. It’s common in simple cases like this to omit the return type and let the inference system save our precious keystrokes. But we could add the type explicitly to work as a post-condition, making sure the compiler fails if we have some discrepancy.
```jsx
function exclaim (str: string): string {
    return str + "!!!";
}
```
We need to provide explicit types for the input of a top-level function, if we don’t, the compiler will infer the unsafe type _any._ This can lead to errors as the _any_ silently propagates, which is why TS added a strictness option called _no-implicit-any_ that will throw an error. To increase developer productivity through tooling, in version 3.2 _TypeScript_ added a [quick fix](https://devblogs.microsoft.com/typescript/announcing-typescript-3-2/) to its language services to suggest a type from the function usage.

Given its rationale, _TypeScript_ has a lot of flexibility in the ways we can write functions and express their types. In the following example, _exclaim1_ and _exclaim2_ are analogous. There are many places where you have to add a function type definition, and it can be confusing knowing which syntax to use.
```jsx
interface Exclaimable {
    exclaim1 (str: string): string
    exclaim2: (str: string) => string
}
```
If we are working with JavaScript files, we can avoid using a special syntax and just write the types using JSDoc. These features allow newcomers to [experience some of the _TypeScript_ benefits](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) without going all in and is the kind of decisions that make me think of TS as tooling more than a new language (having special syntax just for the sake of being more expressive).
```jsx
/**
 * Adds two numbers together
 * @param {number} a The first number to add
 * @param {number} b The second number to add
 */
function add (a, b) {
    return a + b
}
```
In the following example, functions _sub_ and _div_ are also analogous, but the later is written using arrow functions which is more concise. Receiving two parameters makes these functions harder to compose. So for _mul_ we decided to take one argument at a time, which enable us to create new functions like _times2_ from it.
```jsx
function sub (a: number, b: number) {
  return a - b
}

const div = (a: number, b: number) => a / b

const mul = (a: number) => (b: number) => a * b

const times2 = mul(2)
```
The downside of having _mul_ written like this is that it seems weird when we want to call it with both arguments: _mul(2)(4)_. If we want the best of both worlds, we can use a [curry function](https://medium.com/@hernanrajchert/creating-typings-for-curry-using-ts-3-x-956da2780bbf) like ramda’s, but it also has some limitations in TS, as it does not work with generic functions.
```jsx
const mul = curry((a: number, b: number) => a * b)
mul(2, 2) // 4
mul(2)(2) // 4
```
**_PureScript_** , like _Elm_ and _Haskell,_ has a _Hindley-Milner_ based type system which is well suited for a functional language, and makes easier the transition between them. We can notice that the type information is placed above using “_::”_ to separate the identifier from its type_,_ and in a new line we use _“=” to_ separate the identifier from its value. Even if the compiler can infer the type correctly, PS will warn us if we don’t provide explicit information for all top-level expressions.
```jsx
pi :: Number
pi = 3.1416

age :: Int
age = 32
```
Being focussed on correctness, the [primitive types](https://github.com/purescript/documentation/blob/master/language/Types.md) makes the distinction between float numbers and integers. Also, notice that we don’t need the _const_ or _let_ keyword and that we write _pi_ in lowercase as we have the guarantee that all data is immutable.

When we describe functions, the types are also written above the function implementation, decoupling the parameter name from its type. We use an arrow to separate the input from the output, so a type like “_String → String_” means “A function that given a string, returns a string”. If we don’t know the output type we can use an underscore to produce a warning like “Wildcard type definition has the inferred type String”.
```jsx
exclaim :: String -> String
exclaim str = str <> "!!!"

what :: String -> _
what str = str
```
**Unlike TypeScript** , there is only one way to define a function type, which resembles the arrow function way in TS. All functions are automatically curried without the generic limitation, so we can create _times2_ just like before. By partially applying the number 2 to _mul_ we change the signature “_Number → Number → Number_” into “_Number → Number_”.
```jsx
add :: Number -> Number -> Number
add a b = a + b

sub :: Number -> Number -> Number
sub a b = a - b

div :: Number -> Number -> Number
div a b = a / b

mul :: Number -> Number -> Number
mul a b = a * b

times2 :: Number -> Number
times2 = mul 2
```
A big syntax difference from _C_-_family_ languages is that the function application is not done surrounding the parameters with parenthesis, it is done by separating them with a space, so the PS expression “_mul 2 4_” its the same as the TS expression “_mul(2)(4)_”. It can be confusing at first, but it enables clearer syntax, as we’ll see in the next section.

Also notice that in both versions of “_times2_”, the _b_ parameter is implicit. This technique is called [point-free programming](https://lucasmreis.github.io/blog/pointfree-javascript/), which can save us the keystrokes of doing something like “_const times2 = b => mul(2)(b)_”. This is a powerful technique, but it shouldn’t be abused as there are times where it can reduce the legibility.

### A language made for composition

In this section, we’ll leave _TypeScript_ to rest for a bit and focus on what makes _PureScript_ a language made with composition in mind. Let’s recall the _main_ function from the section “dissecting the output”. There are three things we haven’t talked about: A special symbol “_do_”, a not so special symbol “_$_”, and the type of _main_, which doesn’t look like a function.
```jsx
main :: Effect Unit
main = do
  log "Hello"
  log $ exclaim "World"
```
_PureScript_ has a language feature called **do notation** that does different things depending on the underlying type. We could write a whole post describing it in detail, but for now, let’s just say it’s a way for us to call one effectful computation after the other in a way that resembles imperative programming.

To help us investigate _$_ and _Effect Unit_ we’ll use the _REPL_ to see the type of an expression and the kind of type. We need to have _pulp_ installed and then execute “_pulp repl”_. Using the _:t_ command we can see that _log_ is a function that receives a _String_ and returns an _Effect Unit,_ the type of our _main_ “function”.
```jsx
$ pulp repl
PSCi, version 0.12.2
Type :? for help

import Prelude
import Effect
import Effect.Console

> :t log
String -> Effect Unit
```
All the expressions inside “_do_” must return an _Effect Unit_. The first call to _log_ is trivial but the second poses a problem, as we want to log the exclaimed string. Given that function application is done using a space, if we write the expression _log exclaim “World”_, the compiler will throw an error because it understands that we are passing two arguments to a function that only accepts one. There are three common ways to write the expression that we want: With parenthesis, with _apply_ (_$_) and with _applyFlipped_ (_#_).
```jsx
> :t log "Hello"
Effect Unit

> :t log exclaim "World"
Error found:
  Could not match type                    
    String -> String                    
  with type          
    String

> :t log (exclaim "World")
Effect Unit
> :t log $ exclaim "World"
Effect Unit
> :t exclaim "World" # log
Effect Unit
```
The symbols _$_ and _#_ are not language features, they are just normal functions called _apply_ and _applyFlipped_ respectively and they are defined in the standard library Prelude. The special feature is that we can define an infix operator for any function of two arguments. As the [documentation](https://pursuit.purescript.org/packages/purescript-prelude/4.1.0/docs/Data.Function#v:(%24)) says, _apply_ lets you omit parenthesis in some cases, making the code easier to read.

Looking at the source code, the implementation is pretty straight forward, but the types could use some explanation as these are the first abstract functions that we see. If we look at _apply_, the first part declares two type variables “_a”_ and “_b”_ that could be any concrete type. Then we receive two arguments, a function “_f”_ that goes from (_a_ → _b)_ and a value “_x_” of type _“a”._ If we use _log_ as our “_f_”, we can substitute the types to see that “_a”_ will be of type _String_, and “_b”_ will be _Effect Unit._ The implementation is just applying the argument “_x_” to the function “_f_”. Notice that _applyFlipped_ is the same, but it first receives the value and then the function.
```jsx
apply :: forall a b. (a -> b) -> a -> b
apply f x = f x

infixr 0 apply as $

applyFlipped :: forall a b. a -> (a -> b) -> b
applyFlipped x f = f x

infixl 1 applyFlipped as #
```
Once again, there is nothing special with _$_ and _#_, the language decisions that make this possible are: function application is done with a space, parenthesis only serve to define precedence and any function of two arguments can be infixed. This is a very powerful concept that Guy Steele describes in his talk g[rowing a language](https://www.youtube.com/watch?v=_ahvzDzKdB0), it involves well-thought syntax primitives that can compose into more complex constructs and it can be eventually be used to define a **D** omain **S** pecific **L** anguage.

In _JavaScript_/_TypeScript_ there are many language features that could be implemented in _PureScript_ userland without having to go through a committee. The [pipe operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator) is a proposal in stage 1 that could enable better syntax for functional programmers, which does the same as PS _applyFlipped (#)._ Async await is a feature around Promises that allow us to write code more imperatively, in PS we could combine do notation with the type Aff. And the [optional chaining operator](https://github.com/tc39/proposal-optional-chaining), which is at stage 1 could be replaced with do notation and the Maybe type.

Now that all the expressions inside _do_ return the same type, let’s go back to the REPL to understand what the type means. We can use the _:k_ command to inspect the kind of a type. For example, _Unit_ and _Number_ are regular types, but _Effect_ and _Array_ are type constructors. A type constructor is a function for types instead of values, hence the similar syntax “_Type → Type_”. The constructor can be applied to a type using a space (just like normal function application), so _Array Number_ and _Effect Unit_ will have the same kind “_Type”_. The type _Unit_ comes from the word “unit of work” and it’s analogous to _void_ in TypeScript.
```jsx
> :k Number
Type

> :k Unit
Type

> :k Effect
Type -> Type

> :k Array
Type -> Type

> :k Effect Unit
Type
> :k Array Number
Type
```
We can think of _Array_ as a simple data structure or we can think of it as a way to express a computation of multiple values. In the same way, we can think of _Effect_ as a computation that modifies the world. Strict functional languages have the restriction to be pure, which enables a whole set of guarantees, but a programs main goal is to modify the world in some way, either by reading a file, mutating the DOM, etc. We can cope with this limitation by working with types that represent the effectful computations.

As we saw in the section “dissecting the output”, all _Effects_ were compiled to functions, adding an extra level of indirection. This allows us to compose those computations before actually running them. In the first eight minutes of his talk “[Constraints Liberate, Liberties Constrain](https://www.youtube.com/watch?v=GqmsQeSzMdw&list=WL)”, Runar Bjarnason gives one of the best explanations of this concept that I’ve seen.

- _If we are gonna work with explosives, it's easier to work with the TNT than the exploded pieces._

and it also has this quote from David J. Wheeler

- _We can solve any problem by introducing an extra level of indirection._

A nice thing about expressing your computations this way is that you can encode _what_ you want to do and some notion of _how_ you want to do it, all in the type system. And we can create our programs as a combination of multiple computations like this:

- **Effect** _Unit_: An effectful computation that changes the world in some way, synchronous writing a file to the console, mutating the DOM, etc
- **Array** _Student_: A computation of multiple Students
- **Maybe** _User_ **:** A computation that may resolve in a user or may be empty
- **Either** _String_ _Prime_: A synchronous computation that can resolve to a prime number or fail with a string message
- **Aff** _BlogPost_: An asynchronous effectful computation that can resolve to a blog post
- **State** _AST Number_: An stateful computation that works with an AST and returns a Number

In this post, we’ve seen some differences between _TypeScript_ and _PureScript_, more notable their rationale, the reason to exist. As always, the decision to use them over plain _JavaScript_ depends more on factors like what your team is comfortable with, how much do you care for correctness vs speed of development, etc. Knowing what each language provides will help you make an informed decision. Please comment or share if you find it useful.
* * *
### Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps

[![](https://cdn-images-1.medium.com/max/1024/1*s_rMyo6NbrAsP-XtvBaXFg.png)](https://logrocket.com/signup/)<figcaption><a href="https://logrocket.com/signup/">https://logrocket.com/signup/</a></figcaption>

[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

[Try it for free.](https://logrocket.com/signup/)
* * *
The post [TypeScript vs PureScript](https://blog.logrocket.com/typescript-vs-purescript-not-all-compilers-are-created-equal-c16dadaa7d3e/) appeared first on [LogRocket Blog](https://blog.logrocket.com).