Selective applicative functors

Update: I have written a paper about selective applicative functors, and it completely supersedes this blog post (it also uses a slightly different notation). You should read the paper instead.

I often need a Haskell abstraction that supports conditions (like Monad) yet can still be statically analysed (like Applicative). In such cases people typically point to the Arrow class, more specifically ArrowChoice, but when I look it up, I find several type classes and a dozen of methods. Impressive, categorical but also quite heavy. Is there a more lightweight approach? In this blog post I’ll explore what I call selective applicative functors, which extend the Applicative type class with a single method that makes it possible to be selective about effects.

Think of handle as a selective function application: you apply a handler function of type a → b when given a value of type Left a, but can skip the handler (along with its effects) in the case of Right b. Intuitively, handle allows you to efficiently handle errors, i.e. perform the error-handling effects only when needed.

Note that you can write a function with this type signature using Applicative functors, but it will always execute the effect associated with the handler so it’s potentially less efficient:

Here we tag a given function a → b as an error and turn a value of type a into an error-handling function ($a), which simply applies itself to the error a → b yielding b as desired. We will later define laws for the Selective type class which will ensure that apS is a legal application operator <*>, i.e. that it satisfies the laws of the Applicative type class.

The select function is a natural generalisation of handle: instead of skipping one unnecessary effect, it selects which of the two given effectful functions to apply to a given value. It is possible to implement select in terms of handle, which is a good puzzle (try it!):

This data type is used for validating complex data: if reading one or more fields has failed, all errors are accumulated (using the operator <> from the semigroup e) to be reported together. By defining the Selective instance, we can now validate data with conditions. Below we define a function to construct a Shape (a Circle or a Rectangle) given a choice of the shape s :: f Bool and the shape’s parameters (Radius, Width and Height) in an arbitrary selective context f.

In the last example, since we failed to parse which shape has been chosen, we do not report any subsequent errors. But it doesn’t mean we are short-circuiting the validation. We will continue accumulating errors as soon as we get out of the opaque conditional:

The definition of the Selective instance for the Const functor simply falls back to the applicative handleA, which allows us to extract the static structure of any selective computation very similarly to how this is done with applicative computations. In particular, the function dependencies returns an approximation of dependencies of a given key: instead of ignoring opaque conditional statements as in Validation, we choose to inspect both branches collecting dependencies from both of them.

Here is an example from the Taskblog post, where we used the Monad abstraction to express a spreadsheet with two formulas: B1 = IF(C1=1,B2,A2) and B2 = IF(C1=1,A1,B1).

This produces the graph below, which matches the one I had to draw manually last time, since I had no Selective to help me.

Laws

Instances of the Selective type class must satisfy a few laws to make it possible to refactor selective computations. These laws also allow us to establish a formal relation with the Applicative and Monad type classes. The laws are complex, but I couldn’t figure out how to simplify them. Please let me know if you find an improvement.

Note that there is no law for handling a pure value, i.e. we do not require that the following holds:

handle (pure (Right x)) y = pure x

In particular, the following is allowed too:

handle (pure (Right x)) y =const x <$> y

We therefore allow handle to be selective about effects in this case. If we insisted on adding the first version of the above law, that would rule out the useful Const instance. If we insisted on the second version of the law, we would essentially be back to Applicative.

A consequence of the above laws is that apS satisfies Applicative laws (I do not have a formal proof, but you can find some proof sketches here). Note that we choose not to require that apS = <*>, since this forbids some interesting instances, such as Validation defined above.

If f is also a Monad, we require that handle = handleM.

Using the laws, it is possible to rewrite any selective computation into a normal form (the operator + denotes the sum type constructor):

In words, we start with a sum type and handle each alternative in turn, possibly skipping unnecessary handlers, until we end up with a resulting value.

Alternative formulations

There are other ways of expressing selective functors in Haskell and most of them are compositions of applicative functors and the Either monad. Below I list a few examples. All of them are required to perform effects from left to right.

I believe these formulations are equivalent to Selective, but I have not proved the equivalence formally. I like the minimalistic definition of the type class based on handle, but the above alternatives are worth consideration too. In particular, SelectiveS has a much nicer associativity law, which is just (x |.| y) |.| z = x |.| (y |.| z).

Concluding remarks

Selective functors are powerful: like monads they allows us to inspect values in an effectful context. Many monadic computations can therefore be rewritten using the Selective type class. Many, but not all! Crucially, selective functors cannot implement the function join:

I’ve been playing with selective functors for a few weeks, and I have to admit that they are very difficult to work with. Pretty much all selective combinators involve mind-bending manipulations of Lefts and Rights, with careful consideration of which effects are necessary. I hope all this complexity can be hidden in a library.

I haven’t yet looked into performance issues, but it is quite likely that it will be necessary to add more methods to the type class, so that their default implementations can be replaced with more efficient ones on instance-by-instance basis (similar optimisations are done with Monad and Applicative).

Have you come across selective functors before? The definition of the type class is very simple, so somebody must have looked at it earlier.

Also, do you have any other interesting use-cases for selective functors?

Big thanks to Arseniy Alekseyev, Ulan Degenbaev and Georgy Lukyanov for useful discussions, which led to this blog post.

Footnotes and updates

(*) As rightly pointed out by Darwin226 in the reddit discussion, handle = handleA gives a valid Selective instance for any Applicative, therefore calling it less powerful may be questionable. However, I would like to claim that Selective does provide additional power: it gives us vocabulary to talk about unnecessary effects. We might want to be able to express three different ideas:

Express the requirement that all effects must be performed. This corresponds to the Applicative type class and handleA. There is no way to distinguish necessary effects from unnecessary ones in the Applicative setting.

Express the requirement that unnecessary effects must be skipped. This is a stricter version of Selective, which corresponds to handleM.

Express the requirement that unnecessary effects may be skipped. This is the version of Selective presented in this blog post: handle is allowed to be anywhere in the range from handleA to handleM.

I think all three ideas are useful, and it is very interesting to study the stricter version of Selective too. I’d be interested in hearing suggestions for the corresponding set of laws. The following two laws seem sensible:

handle (Left<$> x) f =flip($)<$> x <*> f
handle (Right<$> x) f = x

Note that the Const and Validation instances defined above do not satisfy these laws.

Note also that efficiencyis an engineering notion full of tradeoffs and it might not always be obvious what is more efficient: to skip an unnecessary effect or to perform it and discard unused results. For example, if you have parallelism, you might in some cases prefer to start the evaluation of both arguments of handle in parallel to speed up the error branch. The stricter version of selective functors forbids such optimisations.

This doesn’t type check. Feel free to link to a GitHub gist where we could continue the discussion with code highlighting.

I think one can make it work in two ways:

* By eventually rewriting your code into `handleA`, which is in the blog post. But then when you have got a `Right b`, you don’t need the handler effect and would like to skip it, but the Applicative interface doesn’t allow you to do that.

* You could also use `join` to collapse `f (f b)` into `f b`. But that requires a monad, hence, being equivalent to `handleM`.

Hi there. A friend and I were discussing this, and came upon the suspicion that this is basically saying `handle :: (Functor f, Monad (EitherT b f)) => f (Either a b) -> f (a -> b) -> f b`.

This stuff gets a little awkward to express in Haskell because instance dictionaries aren’t explicit and overlap is a nono, but I think the `Validation` stuff is also a monad instance for `EitherT (Validation e) b`s. I’ve put some experimentation along these lines in a gist here:

Hi @Andrey. It breaks the laws if you compare `ap` to the “ given in the instance `instance Semigroup e => Applicative (Validation e)`. However we have two independent instances of applicative for `Validation`, and for the second one we have by definition `() = ap`. The multiple instances of applicative is why we run into issues with overlap.

Under some hypothetical world with [scrap your typeclasses](http://www.haskellforall.com/2012/05/scrap-your-type-classes.html) we could have multiple instances of a class for the same datatype without even the need for newtype wrappers like `EitherT`. In such circumstances you’d again unambiguously see the “ derived from the monad to be identical to the monad’s `ap`, and the “ from Validation’s standard applicative instance to be some other thing.

In theory I don’t think is not a problem; after all we don’t expect the empty for `IntProduct` to obey the laws with respect to `IntSum`’s `mappend`. In practice, this means your encoding is much more idiomatic haskell.