This may seem a little strange, but althrough Javascript is a dynamic language, with very loose typing (automatic convertions, equals signs that only works on arrays/numbers/undefined/nil), lots of things that are “falsy” by default, with the new promise-based approach of Javascript, the language is borrowing some very interesting concepts from Haskell.

And yes, this is a great thing. And yes, this will probably change the way we program.

Let’s begin by talking about Javascript, and its new features. Old async-javascript code was probably like this:

some_io_function(function(result) {
  find_name_in_db(result.person_id, function(name) {
    console.log(name);
  })
});

Now, it’s like this:

some_io_function()
  .then((result) => find_name_in_db(result.person_id))
  .then(console.log)

And, with new ES6 features:

async () => {
  var result = await some_io_function();
  var name = await find_name_in_db(result.person_id);
  console.log(name)
}

Now, what does this have to do with Haskell? Multiple things, but the most important: Functors!

The most simple explanation (and a little bit incorrect/incomplete) of a functor is some kind of “box” that “maps” values to another box. So, an Array is a functor: it is a “box” for “multiple values”, and by a function, we can “map” it’s contents to another “box”, or an Array. On Haskell, the most notable functor is IO (that is also a monad, but this is a talk for another time). It’s kinda strange, but on Haskell, functions can have the IO type: this means that if a function prints something, reads something, or in any way interacts with the outside world, it will have IO type. Also, IO is some of these “boxes”: it has a fmap (Haskell’s equivalent of map in other languages) that transforms an IO of anything into another IO of other thing. Let’s see it in practice: supose we want to upper-case a string. This means the code: fmap Data.Char.toUpper "some string". Now, we want the user to be able to read this string. So, we want a function that will map a “user interaction” to a upper-case string.

In Haskell, this is not possible: getLine, the function that we use to read a string from the user, have type IO String. It’s not possible to get rid of IO in any way. What we can do is to, again, “map a box of something into another box of other thing”, so:

import Data.Char

stringToUpper :: String -> String
stringToUpper someString = fmap toUpper someString

readUpperCase :: IO String
readUpperCase = fmap stringToUpper getLine

Now, I’ve added type signatures so we can see which function will have each type. Now, what happened? getLine will return a functor: an IO String. We use fmap, which has type Functor f => (a -> b) -> f a -> f b, which means: “Given that f is a Functor, we expect a function that converts from some type a into type b. Then, when we pass a f a, that is, a “box” of type f containing a data, we’ll return a box of the same type f containing b data. Now, onto concrete types, in our function, f is a Functor, and it’s name is IO. a is a String. Our first parameter is a function that have type String -> String, so our b is a String too. So, the “full” type of our fmap, applied to our case, is Functor IO => (String -> String) -> IO String -> IO String.

Now, this means that IO functions will always be “separated” from the rest of our code: we can’t pass an IO String when we expect just a String, so we’ll need to use fmap to extract it’s value and put it back. Now, a little detour:

Back to Javascript

Let’s re-implement our example with Javascript, using Node.JS:

const readline = require('readline');

// Some JS to read a line with promises
const getLine = () => new Promise( resolve => {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  rl.resume();
  rl.on('line', (line) => {
    rl.close();
    resolve(line);
  });
});

const readUpperCase = () {
  getLine().then(e => e.toUpperCase())
}

Now, Javascript is not a statically typed language. But this doesn’t means we can’t give this function a type. I think all we agree that the type of this function is something like Promise String. That means we expect no parameters, and we return a String, inside a Promise. As with Haskell, we never “exit” the Promise: it’s promisses all the way down, like in our IO case.

Now, let’s make a code that will concatenate two strings, like a “given name” and a “surname”. In JS, we could do this:

const readline = require('readline');

const getLine = () => new Promise( resolve => {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });
  rl.resume();
  rl.on('line', (line) => {
    rl.close();
    resolve(line);
  });
});

console.log("Type your name")
let givenName = getLine()

console.log("Type your surname")
let surname = getLine()

givenName.then(gn => surname.then(sn => console.log(gn + " " + sn)))

But it would be wrong. First, because the getLine returns a promise. This means that the code is asynchronous. See what happens when I try to run this code on my machine:

Okay, so we need a way to synchronize our code. We can do it the old way:

console.log("Type your name")
getLine().then(gn => {
  console.log("Type your surname")
  getLine().then(sn => {
    console.log(gn + " " + sn)
  })
})

Or, the new way:

(async () => {
  console.log("Type your name")
  let gn = await getLine()
  console.log("Type your surname")
  let sn = await getLine()
  console.log(gn + " " + sn)
})()

Which is just a syntax sugar on the old way.

Diving into sugar syntax

The reason we need for this sugared syntax is not simply convenience: it is kinda difficult to synchronize all data that we need using callbacks (even considering that Javascript’s Promise is flatten – there’s no way to return a promise inside another promise). By the way, Haskell does have a sugared syntax for its IO (in truth, for all monad but this, again, is a subject for another time) operations too:

readName = do
  putStrLn "Digite seu nome"
  gn <- getLine
  putStrLn "Digite seu sobrenome"
  sn <- getLine
  putStrLn (gn ++ " " ++ sn)

That de-sugars to:

readNameNoSugar =
  putStrLn "Digite seu nome" >>=
  \_ -> getLine >>=
  \gn -> putStrLn "Digite seu sobrenome" >>=
  \_ -> getLine >>=
  \sn -> putStrLn (gn ++ " " ++ sn)

And I think that it is pretty cool.


1 Comment

Why I tend to avoid core.async? – Maurício Szabo · 2019-12-05 at 22:26

[…] Even if you use promises (await is only syntax sugar around .then(<callback>). More on this on another post), so things become really complicated really […]

Comments are closed.