Advanced FP for the Enterprise Bee: Applicatives

Containers of Honey

Introduction

In the first article of this series we showed how useful the traverse operator can be. As part of this we used an Applicative without further explanation. In this article we will explain Applicatives from scratch, and then circle back to the original example. All the code for this series is available in this repository.

The Sample Problem

Just like last time, let’s say our teams resident FP super-fan has been hard at work. This time they have converted a UI helper function to return an Either.

As you can see we are asking the user a question, and then checking if the answer matches a pattern:

If all goes well we return a Right containing their response. In the case of an error we return a Left with an appropriate message.

Suppose you try this out. But because FP isn’t your thing you struggle to handle the different permutations. In particular you need to work out how, should all go well, to combine the contents of the two Right values. Here’s a potential solution:

What's your name?
Lucy
Where do you live?
Melbourne
Hello Lucy from Melbourne

This does give the required output, but it’s terribly procedural and just feels wrong. Surely there must be a better approach?

Introducing Applicatives

Here’s a much nicer way to solve the problem. It requires we write a special function, which I’ve called action.

val action = { name: String -> { location: String -> "Hello $name from $location" } }

As you can see we have:

  • A lambda that takes a parameter called name and returns a lambda
  • This second lambda takes a parameter called location and returns a string
  • The string includes the values of both name and location

We then use this in the expression name.map(action) — which is itself very curious. As you know map applies a transformation, but normally we transform between standard data types like String and Int.

In this case what will be returned is a Right containing a lambda with the name parameter supplied, but the location parameter still missing. This could be referred to as a ‘partially applied function’.

We then nest this expression within a call to ap like so:

val result = location.ap(name.map(action))

The ap operator is called against an Either, and expects as its input an Either containing a partially applied function. It completes the application of the function by supplying the value within the current Either, should it be available.

On the Happy Path the return value from ap is an Either containing the return value from the inner lambda. In this case that’s our hello message for the user. Success!

What's your name?
Jane
Where do you live?
Bristol
Hello Jane from Bristol

Explaining the Applicative

In essence an Applicative provides a way to manipulate the content of two or more instances of a container type, without having to resort to procedural shenanigans. The ap function is the most commonly used, but there are a number of other operators for creating and combining instances.

For this to work your action must be written in the manner described above. You must provide a function that takes a single input, and then returns a function expecting an additional input. Fortunately Arrow provides a curry operator that converts a lambda taking multiple parameters into such a chain:

If we look at the implementation of the ap method, it’s baffling:

fun <A, B, C> EitherOf<A, B>.ap(ff: EitherOf<A, (B) -> C>): Either<A, C> =
flatMap { a -> ff.fix().map { f -> f(a) } }

As you can see the mysterious fix method from the previous article is in use once again. Plus, where we would expect to see Either, we have EitherOf instead. Let’s not worry about this for now, this will be addressed in the next article on Higher Kinded Types.

Revisiting Traverse

If you remember from last time, this was how we called the traverse operation:

val result = input.traverse(Either.applicative(), ::propertyViaJVM)

You can see now how the Applicative is helpful in building our finished result. But this is a slightly different usage. The call to applicative returns a Singleton Instance. This in turn contains a number of helper methods for combining (in this case) Either objects.

Here’s our original example, rewritten to use the Singleton. When we call the tupledN method we get (on the Happy Path) a Right containing a Tuple2 of the input from the user.

We can map over this as usual, and take the values from the Tuple2 as required. In the Arrow implementation of tuples the properties are simply called ‘a’, ‘b’, ‘c’ and so on.

If you dig into this you will find that the Applicative type in Arrow extends from a type called Apply, and this is where the tupledN method is declared. This is a detail of the Arrow implementation, but still worth mentioning.

The Apply type also declares a mapN operation, whose contract allows the supplied functions to execute in parallel. Here’s how it could be used:

Applicatives and Validated

As in the previous article we can improve the original code by switching from Either to Validated. The Validated type adds the extra functionality of combining error messages.

We need to tell the Validated instance how we want multiple instances combined. We do this by providing a Semigroup. This is simply an object with a combine method. Arrow provides Semigroups for all the standard Kotlin types:

The default Semigroup for strings concatenates the two values. In the case of multiple errors the messages would be run together:

What's your name?
123
Where do you live?
456
Sorry: 123 does not match [A-Z a-z]+456 does not match [A-Z a-z]+

We can avoid this by specifying our own semigroup, which combines strings by concatenating with spaces and a comma:

Now our error messages will be comma separated:

What's your name?
123
Where do you live?
456
Sorry: 123 does not match [A-Z a-z]+ , 456 does not match [A-Z a-z]+

Excellent! Invoking the ap operator directly seems simpler than using the Singleton Instance, so let’s finish with that version:

Conclusions

In this article we explored the Applicative type and the ap operator. Along the way we also saw what a Semigroup is. Next time round we will resolve the other outstanding issue, namely the fix method and Higher Kinded Types. Stay tuned…

Thanks

I am grateful to Richard Gibson and the Instil training team for reviews, comments and encouragement on this series of articles. All errors are of course my own.

--

--

--

Experts on various Google products talking tech.

Recommended from Medium

How to schedule a Cloud Function to run in the future (in order to build a Firestore document TTL)

5 Steps to a Beautiful Terminal That You’ll Love Using

AWS Serverless App: Continuous Integration and Deployment

Applying Little’s Law to Improve Predictability & Flow in Agile Teams

What You Should Know About AWS If You’re a Microsoft Azure User

AWS Serverless App: Testing

GraphQL in JavaScript: An Introduction

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Garth Gilmour

Garth Gilmour

Helping developers develop software better. Coding for 30 years, teaching for 20. Google Developer Expert. Trainer at Instil. Also martial arts and philosophy.

More from Medium

Contextual Polymorphism with Operator Overloading (in Kotlin & Scala)

Make Threaded HTTP Requests in Kotlin

Typesafe IDs with Kotlin and Spring

TripwireTrait