Skip to content

Instantly share code, notes, and snippets.

@rjchatfield
Last active December 13, 2015 20:15
Show Gist options
  • Save rjchatfield/1d5b3592cfa4154e36e6 to your computer and use it in GitHub Desktop.
Save rjchatfield/1d5b3592cfa4154e36e6 to your computer and use it in GitHub Desktop.
Types are Coming

Types are coming

Last week I had a crash course in PureScript - a Haskell-like language that compiles to JavaScript. I’m not about to preach that you should write your web apps in Haskell (though I strongly recommend learning Haskell), however, I do wish to explore an important part of any Functional Programming language that has eluded Javascript...

Types.

There is a lot to be said about Javascript and its lack of static typing. We’ve built the whole web with it! But this post will explore the benefits of a compile-time strong static type system.

“But Javascript does have types!?”

Yes, but with two major flaws (excluding coercion):

  • No Type annotations mean our API contracts are weak (JSDoc is our only hope)
  • Weak & dynamic typing mean we depend upon run-time tests to prove the behaviour of our code. Imagine our code couldn’t break?

Thankfully there are big leaps forward in both of these areas.

Are Types important to Javascript?

These people think so:


Examples

I wish to assert that Type have a benefit to three parties:

  1. The Developer writing and testing the code
  2. The Maintainer: a developer that comes to this code later and may need to touch it
  3. The Consumer of the code

Let’s get into some examples. Given:

const xs = [1,3,2]

Author’s note Pronouce xs as the plural of a single x eg. “exes”

We want to make a sort function, that takes in our xs and returns as a sorted xs. Without types, it would look something like:

function safeSort(xs) {
	return xs.slice().sort()  
}

Thankfully arrays can do the sorting for us! Sadly .sort() mutates xs. I’ll save my immutability rant for another blog.

Bad news:

  • As a Consumer of that function, how do I know what this actually takes? Is there a way to know which of these work and which will fail (without tests)?
safeSort() // does it handle undefined?
safeSort(“Alpha Charlie Beta”) // can it sort words in a string?
safeSort(myExpenses) // how about my expenses?

In a strongly typed language, we’ve effectively written…

function safeSort(xs: ?any): ?any {
	return yolo()
}

So let’s add some types.


Adding Types

I’m going to use markup that can be processed by TypeScript, but could be converted to Flow type or AtScript with some minor changes.

function safeSort(xs: Array<Number>): Array<Number> {
	return xs.slice().sort()
}

What’s good:

  • The Developer only has to unit test Arrays of numbers
  • The Maintainer knows what xs are (if we ever need to refactor it)
  • The Consumer know what xs are (and gets auto-completing types)

This is the heart of why types are so important for teams.

What could be better:

  • Only works with Numbers? What about for all Array types?

One way to solve this is to create a bunch of classes and use overloading. Let’s explore some more flexible and less shit ways…


Generics

function safeSort<T>(xs: Array<T>): Array<T> {
	return xs.slice().sort()
}

What’s good:

  • Generics!
  • We’ve kept the return type identical with a homogenous generic
  • The Developer… doesn’t have anything to test
  • The Consumer cay use an Array of any things
  • The Maintainer has less to refactor, because there is less information

That last point is the heart of generics. I’ll go more into Generics later.

What could be better:

  • Why an Array? What about for all SafeSortable things?

Let’s go deeper.


Interfaces

interface SafeSortable {
	safeSort: () => Sortable
}
function safeSort(xs: SafeSortable): SafeSortable {
	return xs.safeSort()
}

What’s good:

  • Interfaces!
  • Now anything that has a safeSort method can be passed into here
  • This is how I'd love to write my functions

There is a great talk about this pattern by the designers of Apple's Swift language. An Interface is a Swift Protocol. Advanced Swift (WWDC 2014) Protocol-Oriented Programming in Swift (WWDC 2015)

What could be better:

  • We don’t have type safety

You’re probably thinking, “Wait, what?”. But this is a trap for young players. When switching from Array to SafeSortable, we transformed it into a heterogeneous generic. Let me explain.

Imagine that there are 3 Types that implement SafeSortable Array, String & PersonalExpenses. Our type signature isn’t promising the Consumer exactly what they’ll be getting. We’re basically saying, “If you pass me any one of those 3 types, I’ll return to you any of those 3 types… not necessarily the same type.

// safeSort: (SafeSortable) => SafeSortable
safeSort([1,3,2]) // may return a String ¯\_(ツ)_/¯

This is not good. What we need instead of a heterogeneous generic is a homogenous generic.

(hold onto your butts)


Homogenous vs Heterogeneous

interface SafeSortable<T> {
	safeSort: () => SafeSortable<T>
}
function safeSort<T>(xs: SafeSortable<T>): SafeSortable<T> {
	return xs.safeSort()
}

interface Array<T> {
    safeSort: () => Array<T>
}
Array.prototype.safeSort = function() {
    return this.slice().sort()
}

What’s good:

  • Our interface will return the same kind

What could be better:

  • Turns out (unfortunately) that TypeScript and Flow type don't enforce this. And worst…
  • While our T’s are homogenous but our SafeSortables are still heterogeneous. We need Higher Kinded Types.

A suggested syntax:

function safeSort<T, S: SafeSortable<_>(xs: S<T>): S<T> {
	return xs.safeSort()
}

This is not valid in TypeScript or Flow type. In fact, this ability to build Higher Kinded Types like this is reserved for languages like Haskell, PureScript and Scala. This is a bummer.


Generics pt.II: "Guess That Function"

Let’s play the “Guess That Function” with these method signatures:

a: (Array<Addable>) => Addable

Given an array of addable things, this will return one addable thing. The only possible implementation would be…

a: (xs) => xs.reduce(+)

Let’s play again:

b: (Array<T>, (T) => U) => Array<U>

So how can we return an array of U’s with only array of T’s? There’s only one possible implementation…

b: (xs, f) => xs.map(f)

Great! I’d now love to go one further and question why we expect an Array type. That implementation doesn’t require all of the Array’s abilites, and we’re ignoring a whole class of type that may have a .map() functions. We really just want a Mappable.

b: (Mappable<T>, (T) => U) => Mappable<U>

This has a name in the functional programming world of “Functor”; a cousin of the infamous Monad.

Now, this signature gives both the consumer and the maintainer a shared certainty.

  • As a Consumer, I know that they aren’t doing anything to my array under the hood.
  • As a Developer testing this code, I can only pass Mappables into this function, and I always get a Mappable back. What do my tests need to do?
  • As a Developer handing on tested code, if a future developer wanted to touch my code, I know that its safe. So long as the signature holds, we are very unlikely to introduce bugs to our consumers.

Bonus Round: Higher Kinded Types

If you've read this far, go read this blog post... and go learn Haskell too. But the above example really needs a polymorphic kind so that we can ensure that we get the right types back.

b<T, U, M<_>> : (M<T>, (T) => U) => M<U>

Without this, we may give it an Array, and get back a PersonalExpences. That's not what our implementation does, but without a correct type signature, we would need to rely on unit tests to assert that our code it correct.


Conclusion: Types are for Teams

I like the idea of developing your Javascript applications in two stages. Firstly, explore and developer the app organicly, without types. Get an early prototype working quickly. Then stage 2, harden the code base. Treat Types as tests. You want to have Types on everything that merges to master.

Talking with other FP'ers, they prefer the opposite approach: Start with the Type signatures, and work out the implementation as you go. They called it "TDD: Type Driven Development".

With the rise of transpilers & linters in our tools, Javascript is becoming a much better language to build in. And while ES6 is a great step forward, it will be much better when Types make it into the language.

So where should you start?

No one uses AtScript. Babel is so hot right now and it supports stripping out Flow type annotations. So make sure to give it ago in any and all your projects that have Babel. Otherwise, TypeScript offer a great online playground and VS Code supports live linting and auto-completes. It’s been a pleasure to use it.

Start learning about Type systems now. They’re coming to the web.

@alexreardon
Copy link

Yet another compelling argument for types on function parameters. I need to do some thinking on this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment