Kleisli Compositions in JavaScript

You’ve probably read many times that the goal of functional programs is to achieve a state where you can compose only pure or side effect-free functions. One day, you sit at your computer at work determined to begin crafting your functions to meet this requirement. It’s all looking good, your functions are terse and singular, yet nearly 10 minutes into it you think: “Wait, I need to access the file system or the database,” or just simply “I need to branch my code here based on some condition.” You’re not alone, obviously, as these are very common tasks. Could you still do all of this in a pure manner and without introducing too many imperative structures? Let’s address both of these. The need to:

  1. Access the file system, or to the functional mindset in you, the need to implement an effectful action. The good news is that, with the right level of abstraction, you can perform any effect without sacrificing the functional requirement.
  2. Branch your code, or implement a conditional statement. In our case, we’ll access the file system, and would like to break out of our logic if the operation results in an error. In pure functional languages, you have this notion of pattern matching, which allows the code to take on different paths according to some test. Again, with some abstraction (in the form of an algebraic data type) we can emulate this.

Another underlying concern here that you’ll soon run into with JavaScript is interfacing with asynchronous APIs, such as the file system. There are lots of ways to skin a cat, of course; so you can choose to give implement this in any level of abstraction you like, or provide different levels of error handling to your code.

Consider a simple example, suppose we are given a file path as input and we need to read said file and count the words in it — a simple, everyday task. Let’s put our functional hat and get to work: user-provided paths can point to non-existent files so we’ll need some error checking around that. File streams are read into buffers in NodeJS, which means we’ll need to decode to something like UTF-8 to obtain the string. Lastly, we’ split those words and count them for the result. We’ll call this processFile :: String -> Number and is made up from the following, at a high level:

read(path), check(buffer), decode(encoding, buffer), words(body)

Composing them all together using Ramda would look roughly like this:

processFile :: String -> Number
const processFile = R.compose(
words,
decode('utf-8'),
check,
read
)
processFile('/dir/somefile.txt')

This is how we would like to read code. It’s legible, parseable, and extensible. But does it work? Remember that a function such as fs.readFile used in read is asynchronous (and, of course, we should all prefer the async APIs). Now, you have the mismatch between the synchronous nature of compose and the asynchronous nature of the APIs it’s calling. In, Functional Programming in JavaScript I presented very similar types of problems. My solutions involved using a Promise to abstract the notion of time and embedding functions that knew how to basically resolve the promise and pass the value along to the next function in the chain. This worked really elegantly and allowed me to preserve all of the nice properties of functional programs over these effectful operations.

In this blog post, I want to raise the bar \(^o^)/, and make that a bit more elegant. How do I raise the bar? Raise the abstraction.

Kleisli Composition

To understand Kleisli compositions, we must first understand what a Kleisli category is. Think of it as representing a category of functions with side effects such as printing to the screen, reading from a file, or writing to a database. The type of tasks that we deal with on a daily basis. So, it’s a way to abstract those side effects and shove them into structures that know how to encapsulate them — yeah, yeah… monads (see chapter 5).

While a regular category models composition of functions, a Kleisli category models “composition of effects,” or “composition of monads.” This category has all of the same objects your initial category had, but instead of our simple f :: A -> B maps, we need f :: A -> m B where m is the monadic type providing the abstraction we need and, by definition, implements chain.

In lieu of raising the abstraction, instead of R.compose, we need a higher-level composition as well, smart enough to chain those monads or containers seamlessly, called composeK (for Kleisli, of course).composeK has the following form, taking 3 Kleisli functions:

R.composeK(h, g, f) = R.compose(R.chain(h), R.chain(g), R.chain(f))

composeK has built-in smarts to reach into the algebraic data types used, these containers or boxes, pull it’s value and send it to the next function in the chain.

First example

Let’s begin with a simple use case using our favorite Maybe monad. Suppose you need to access the nested property state of an arbitrary JSON string (this example is inspired from an example in the Ramda docs):

{
user:
{
address:
{
state: "fl"
}
}
}

In this case, after parsing the object, it’s obvious the path is user.address.state; but, you’re making big assumptions (like address might be null) about your data, which may or may not be safe to do. Let’s not make any assumptions and use functions parse(json) and prop(obj, name) that are guarded using a Maybe, which leads to maybeParse and maybeProp.

// parse :: String -> Object|null
const parse = json => {
try {
return JSON.parse(json)
}
catch(e) {
return null
}
}
// prop :: (Object, String) -> Object|String|null
const prop = (obj, name) => obj[name]
// maybeParse :: String => Maybe
const maybeParse = json => Maybe.fromNullable(parse(json)) *
// maybeProp :: String -> Object -> Maybe
const maybeProp = name => obj => Maybe.fromNullable(obj[name]) *

You can clearly see my Kleisli arrows above (marked with an *), those will never return null. Because Maybe is a proper monad (abiding by laws of identity and associativity) and implements chain (or bind, flatmap), my solution becomes:

// getStateCode :: String => Maybe
const getStateCode = R.composeK(
R.compose(Maybe.of, R.toUpper),
maybeProp('state'),
maybeProp('address'),
maybeProp('user'),
maybeParseJson
)

This looks very elegant and bullet-proof. In fact, I reckon that bugs in a functional program are hardly ever due to a bug in the code itself; 95% of the time, it’s a bad assumption made on the data.

Testing it:

it('Should extract the value of a JSON object', () => {    
const result = getStateCode(
'{"user":{"address": {"state":"fl"}}}'
).get()
assert.equal(result, 'FL')
})
it('Should fail to extract a value', () => {
const fn = () => getStateCode('[XXX]').get()
assert.throws(fn, TypeError,
"Can't extract the value of a Nothing.");
})

Works like a charm! In one case I get my value — a Just; in the other a Nothing. So, with Kleisli you always deal with these boxed types. Here’s some food for thought: a Just is an identity function in the Kleisli category of the Maybe monad. Just like aList.of('apples') is also an identity function in the category of the List (FruitBowl?) monad. Make sense? Great!

Second example

Moving on to our original problem. Let’s see how we can tackle the original two quandaries:

  1. Instead of using a Promise, this time I’ll use the Folktale Task data type to remove the asynchronous component of this program.
  2. For branching, I would like to let the user know when the file is empty, so instead of Maybe, I’ll use Either. This type allows my code to take values on one path Right or another Left according to the test of some condition, a very rudimentary pattern match, if you will.

All of these, of course, are conformant to the proper implementation of chain from the Fantasy Land spec. First, our Kleisli arrows:

// read :: String -> Task
const read = path => new Task((reject, resolve) => {
fs.readFile(path, (error, data) => error ? reject(error) :
resolve(data))
})
// check  :: buffer -> Task(_, Either)
const check = buffer => Task.of(
buffer.length > 1 ?
Either.Right(buffer) :
Either.Left('File is empty!')
)
// decode :: String -> Either(Buffer) -> Task(_, String)
const decode = encoding => buffer =>
Task.of(buffer.map(b => b.toString(encoding)))
// words :: String -> Task(_, String)
const words = text => Task.of(text.map(t => t.split(' ').length))

I use Task and Either for different levels of error handling. Task will fork out into an error if the file is not present (user provided an invalid path). If the file is present, but the text is empty, then Either forks out into a Left and alerts the user that the file is empty; otherwise, it chooses Right.

Lo and behold, by working in this higher-level of abstraction my program actually comes out just how I wanted it:

// processFile :: String -> Task(Error, Either)
const processFile = R.composeK(
words,
decode('utf8'),
check,
read
)

It can’t get any cleaner than this, this is the result of monads removing those mundane concerns away from our code. And the fact that Ramda curries everything is brilliant. Here are the use cases we can test:

it('Should extract text from a file and count words', (done) => {
const file = path.join(__dirname, 'test.txt')
processFile(file).fork(
err => {
assert.fail(null, null, 'Should never fork left!')
done()
},
result => {
assert.isTrue(result.isRight)
assert.equal(result.get(), 6)
done()
})
})
it('Should realize the file is empty and not decode at all', (done) => {
const file = path.join(__dirname, 'empty-test.txt')
processFile(file).fork(
err => {
assert.fail(null, null, 'Should never fork left!')
done()
},
result => {
assert.isTrue(result.isLeft)
const fn = () => result.get()
assert.throws(fn, TypeError,
"Can't extract the value of a Left(a).");
done()
})
})
it('Should error-out on invalid file path', (done) => {
const file = path.join(__dirname, 'bad-path.txt')
processFile(file).fork(
err => {
assert.match(err, /ENOENT: no such file or directory/)
done()
},
result => {
assert.fail(null, null, 'Should never fork right!')
done()
})
})

Parting thought

In my professional opinion, having delved into FP coding for years, I think Kleisli compositions needs to be everyone’s modus operandi. You can never make assumptions about your data because that’s when code fails — in production! So it’s always best to expect the worst and hope for the best :-)

For those that don’t know me, my name is Luis Atencio @luijar, I’m the author of Functional Programming in JavaScript and RxJS in Action.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.