Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's preferred among liftM, lilftA, etc

Tags:

haskell

Lately I've been writing FFI code that returns a data structure in the IO monad. For example:

peek p = Vec3 <$> (#peek aiVector3D, x) p
              <*> (#peek aiVector3D, y) p
              <*> (#peek aiVector3D, z) p

Now I can think of four nice ways to write that code, all closely related:

peek p = Vec3 <$> io1 <*> io2 <*> io3
peek p = liftA3 Vec3 io1 io2 io3
peek p = return Vec3 `ap` io1 `ap` io2 `ap` io3
peek p = liftM3 Vec3 io1 io2 io3

Notice that I'm asking about monadic code that doesn't require anything beyond what Applicative provides. What is the preferred way to write this code? Should I use Applicative to emphasize what the code does, or should I use Monad because it might (?) have optimizations over Applicative?

The question is slightly complicated by the fact that there are only [liftA..liftA3] and [liftM..liftM5] but I have several records with more than three or five members, so if I decide to go with lift{A,M} I lose some consistency because I would have to use a different method for the larger records.

like image 851
Joel Burget Avatar asked Mar 01 '12 01:03

Joel Burget


1 Answers

The first thing to remember is that this is slightly more complicated than it ought to be--any Monad instance should have an associated Applicative instance such that the liftM and liftA functions coincide. As such, here's two guidelines:

  • If you're writing a generic function for any Monad, use liftM &co. to avoid incompatibility with other functions that have only a Monad constraint.

  • If you're working with a specific Monad instance that you know has an accompanying Applicative instance, use Applicative operators consistently for any definition or subexpression where you don't need Monad operations, but avoid mixing them aimlessly.

Should I use Applicative to emphasize what the code does, or should I use Monad because it might (?) have optimizations over Applicative?

In general, if there is a difference, it will be the other way around. Applicative only supports a static "structure" of the computation, whereas Monad permits embedded control flow. Consider lists, for instance--with Applicative, all you can do is generate all possible combinations and transform each one--the number of result elements is determined entirely by the number of elements in each input. With Monad, you can generate different numbers of elements at each step based on input elements, allowing you to filter or expand arbitrarily.

A more potent example is is the Applicative and Monad instances based on zipping infinite streams--Applicative can simply zip them together in the obvious way, whereas Monad has to recalculate lots of stuff that it then throws away.

So, the final issue is of liftA2 f x y vs. f <$> x <*> y, or the Monad equivalents. My advice here would be the following guidelines:

  • If you're writing every argument out anyway, use the infix form, because it's easier to read for large expressions.
  • If you're just lifting an existing function, use foo = liftA2 bar rather than foo x y = bar <$> x <*> y--it's shorter and more clearly expresses what you're doing.

And finally, on the issue of consistency, there's no reason you couldn't simply define your own liftA4 and so on, if you need them.

like image 94
C. A. McCann Avatar answered Oct 24 '22 10:10

C. A. McCann