Functional Programming Patterns: Cookbook

We tried to invoke the toFixed method on a String, which doesn’t really exist.

We need to make sure that bankBalance is really a Number before we invoke toFixed on it.

Let’s try to solve it with our safe helper.

We pipe the results of the prop function to our safe(isNumber) function which also returns a Maybe, depending on whether the result of prop satisfies the predicate.

The pipeline above guarantees that the last map which contains the toFixed will only be called when bankBalance is a Number.

If you’re going to dealing with a lot of similar cases, it would make sense to extract this pattern as a helper:Using Applicatives to keep Functions CleanOften times, we find ourselves in situations where we would want to use an existing function with values wrapped in a container.

Let’s try to design a safe add function that allows only numbers, using the concepts from the previous section.

Here’s our first attempt.

This does exactly what we need, but it’s a bit unwieldy because of how we have to reach into the containers to get the values that we need.

Here’s another attempt using Applicatives.

Applicatives allow us to preserve core implementation of our function (add in this case), while making it possible to use them with values contained in ADTs.

Let’s try to reverse engineer ap a bit.

The first step beings with lifting our add function into a Maybe.

Then, we use ap to pass in Maybe a and Maybe b to our curried add function.

We’ve been using map so far to access the value inside a container and ap is no different.

It internally uses map on safeNumber(a) and applies the a to our curried add function.

This returns a Maybe that contains a partially applied add which is waiting for a b.

We then apply it with safeNumber(b) to obtain the result if both a and b are valid, or a Nothing the either of them is a Nothing.

Crocks also provides us the liftA2 and liftN helpers to express the same concept in a pointfree manner.

A trivial example follows:We shall use this helper extensively in the section Expressing Parallelism.

Tip: Since we’ve observed that ap uses map to access values, we can do cool things like generating a Cartesian product when given two lists.

Using Async for Predictable Error Handlingcrocks provides the Async data type that allows us to build lazy asynchronous computations.

To know more about it, you can refer to the extensive official documentation here.

This section aims to provide examples of how we can use Async to improve the quality of our error reporting and make our code resilient.

Often, we run into cases where we want to make API calls that depend on each other.

Here, the getUser endpoint returns a user entity from GitHub and the response contains a lot of embedded URLs for repositories, stars, favorites and so on.

We will see how we can design this use case with using Async.

The usage of the maybeToAsync transformation allows us to use all of the safety features that we get from using Maybe and bring them to our Async flows.

We can now flag input and other errors as a part of our Async flows.

Using Monoids EffectivelyWe’ve already been using Monoids when we perform operations like string/array concatenation and number addition in native JavaScript.

It’s simply a data type that offers us the following methods.

concat :: Monoid m => m a -> m b -> m cconcat allows us to combine two Monoids of the same type together with a pre-specified operation.

empty :: Monoid m => () => m aThe empty method provides us an identity element, that when concat ed with other Monoids of the same type, would return the same element.

Here’s what I’m talking about.

By itself, this doesn’t look very useful, but crocks provides some additional Monoids along with helpers mconcat, mreduce, mconcatMap and mreduceMap.

The mconcat and mreduce methods take a Monoid and an list of elements to work with, and applies concat to all of its elements.

The only difference between them is that mconcat returns an instance of the Monoid while mreduce returns the raw value.

The mconcatMap and mreduceMap helpers work in the same way, except that they accept an additional function that is used to map over every element before calling concat.

Let’s look at another example of a Monoid from crocks, the First Monoid.

When concatenating, First will always return the first, non-empty value.

Using the power of First, let’s try creating a function attempts to get the first available property on an object.

Pretty neat!.Here’s another example that tries to create a best-effort formatter when provided different types of values.

Expressing Parallelism in a Pointfree mannerWe might run into cases where want to perform multiple operations on a single piece of data and combine the results in some way.

crocks provides us two methods to achieve this.

The first pattern leverages Product Types Pair and Tuple.

Let’s look at a small example where we have an object that looks like this:{ ids: [11233, 12351, 16312], rejections: [11233] }We would like to write a function accepts this object and returns an array of ids excluding the rejected ones.

Our first attempt in native JavaScript would look like this:This of course works, but it would explode in case one of the properties is malformed or is not defined.

Let’s make getIds return a Maybe instead.

We use fanout helper that accepts two functions, runs it on the same input and returns a Pair of the results.

One of the main benefits of using the pointfree approach is that it encourages us to break our logic into smaller pieces.

We now have the reusable helper difference (with liftA2, as seen previously) that we can use to merge both halves the Pair together.

The second method would be to use the converge combinator to achieve similar results.

converge takes three functions and an input value.

It then applies the input to the second and third function and pipes the results of both into the first.

Let’s use it to create a function that normalizes an array of objects based on their ids.

We will use the Assign Monoid that allows us to combine objects together.

Using Traverse and Sequence to Ensure Data SanityWe’ve seen how to use Maybe and friends to ensure that we’re always working with the types we expect.

But what happens when we’re working with a type that contains other values, like an Array or a List for example?.Let’s look at a simple function that gives us the total length of all strings contained within an array.

Great.

We’ve made sure our function always returns a Nothing if it doesn’t receive an array.

Is this enough though?Not really.

Our function doesn’t guarantee that the contents of the list won’t hold any surprises.

One of the ways we could solve this would be to define a safeLength function that only works with strings:If we use safeLength instead of length as our mapping function, we would receive a [Maybe Number] instead of a [Number] and we cannot use our sum function anymore.

Here’s where sequence comes in handy.

sequence helps us to basically swap the inner type with the outer type, given an input.

With an Array however, it behaves a bit differently and it gives us the result of calling concat with each element of the Array.

Since we cannot infer the types of the Array elements using JavaScript, we have to explicitly provide the inner type as the first argument.

Let’s see how we can use sequence to refactor our totalLength implementation.

Great!.We’ve built a completely bulletproof totalLength.

This pattern of mapping over something from a -> m b and then using sequence is so common that we have another helper called traverse which performs both operations together.

Let’s see how we can use traverse instead of sequence in the above example.

There!.It works exactly the same way.

If we think about it, our sequence operator is basically traverse, with an identity as the mapping function.

It’s easy to see how sequence and traverse are invaluable for validating data.

Let’s try to create a generic validator that takes a schema and validates an input object.

We’ll use the Result type, which accepts a Semigroup on the left side, allowing us to collect errors.

A Semigroup is similar to a Monoid and it defines a concat method — but unlike the Monoid, it doesn’t require the presence of the empty method to be lawful.

We’re also introducing the transformation function maybeToResult below, that’ll helps us interoperate between Maybe and Result.

Thanks to Ian Hofmann-Hicks, Denis Zolkin and Dale Francis for their inputs on this post.

.

. More details

Leave a Reply