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.

2 Comments
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 […]
There is NO SUCH THING as COLOR FUNCTIONS! – Maurício Szabo · 2024-07-31 at 21:39
[…] personal opinion is simple: Javascript is the first mainstream language to incorporate some Haskell ideas for people to use. This basically means, well, people are not aware that these different […]
Comments are closed.