9

There are quite a few of questions here about whether or not certain transformations of types that involve Monads are possible.

For instance, it's possible to make a function of type f :: Monad m => [m a] -> m [a], but impossible to make a function of type g :: Monad m => m [a] -> [m a] as a proper antifunction to the former. (IE: f . g = id)

I want to understand what rules one can use to determine if a function of that type can or cannot be constructed, and why these types cannot be constructed if they disobey these rules.

Community
  • 1
  • 1
AJF
  • 11,767
  • 2
  • 37
  • 64
  • 2
    What do you mean by "proper antifunction"? That composition with both orders result in the identity, i.e. the inverse? Your question is imho very interesting a starting point might be wadler's theorems for free (http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf) that derives rules that polymorphic functions must obey (as an implication of being polymorphic). Maybe these theorems could be in contradiction with the monad laws for specific signatures? – uberwach Apr 10 '15 at 18:22
  • @uberwach yes, exactly, inverse/antifunction are often used interchangeably in linear algebra. I'll make it clearer... – AJF Apr 10 '15 at 18:24
  • 1
    Sometimes the impossibility proof can be derived from logical considerations via Curry-Howard e.g. (shameless plugs) http://stackoverflow.com/questions/27267848/turning-a-mb-into-ma-b/27267992#27267992 and http://stackoverflow.com/questions/24141972/how-to-apply-higher-order-function-to-an-effectful-function-in-haskell/24142893#24142893 – chi Apr 10 '15 at 18:32
  • 1
    More on Curry-Howard: the logic related to monads is called Lax Logic, and is decidable (via e.g. cut-elimination of its sequent calculus). I think that you can only build functions whose type corresponds to a theorem of such logic. So, given a (mono)type you can decide whether it's a theorem in Lax Logic and know whether you can implement it for any monad. – chi Apr 10 '15 at 18:36
  • 2
    You may want to read ["Representing Applicatives"](http://comonad.com/reader/2013/representing-applicatives/), and Gershom Bazerman's other posts to the Comonad.Reader blog, which mentions that all representable functors are *distributive*: `distribute :: (Functor f, Representable g) => f (g a) -> g (f a)` – Luis Casillas Apr 10 '15 at 18:58
  • 1
    Beware that antifunction has a different definition, as the morphisms in Set* by Vaughn Pratt. – alancalvitti Jul 25 '19 at 20:54

4 Answers4

5

The way that I've always thought about monads is that a value of type Monad m => m a is some program of type m that executes and produces an a. The monad laws reinforce this notion by thinking of composition of these programs as "do thing one then do thing two", and produce some sort of combination of the results.

  • Right unit Taking a program and just returning its value should be the same as just running the original program.

    m >>= return = m
  • Left unit If you create a simple program that just returns a value, and then pass that value to a function that creates a new program, then the resulting program should just be as if you called the function on the value.

    return x >>= f = f x
  • Associativity If you execute a program m, feed its result into a function f that produces another program, and then feed that result into a third function g that also produces a program, then this is identical to creating a new function that returns a program based on feeding the result of f into g, and feeding the result of m into it.

    (m >>= f) >>= g  =  m >>= (\x -> f x >>= g)

Using this intuition about a "program that creates a value" can come to some conclusions about what it means for the functions that you've provided in your examples.

  1. Monad m => [m a] -> m [a] Deviating from the intuitive definition of what this function should do is hard: Execute each program in sequence and collect the results. This produces another program that produces a list of results.
  2. Monad m => m [a] -> [m a] This doesn't really have a clear intuitive definition, since it's a program that produces a list. You can't create a list without getting access to the resulting values which in this case means executing a program. Certain monads, that have a clear way to extract a value from a program, and provide some variant of m a -> a, like the State monad, can have sane implementations of some function like this. It would have to be application specific though. Other monads, like IO, you cannot escape from.
  3. Monad m => (a -> m b) -> m (a -> b) This also doesn't really have a clear intuitive definition. Here you have a function f that produces a program of type m b, but you're trying to return a function of type m (a -> b). Based on the a, f creates completely different programs with different executing semantics. You cannot encompass these variations in a single program of type m (a -> b), even if you can provide a proper mapping of a -> b in the programs resulting value.

This intuition doesn't really encompass the idea behind monads completely. For example, the monadic context of a list doesn't really behave like a program.

Mokosha
  • 2,737
  • 16
  • 33
  • 1
    `Monad m => (a -> m b) -> m (a -> b)` can be done if you know something about `a` -- for example that it is finite. The [universe](http://hackage.haskell.org/package/universe-1.0/docs/Data-Universe-Instances-Reverse.html#t:Traversable) package provides `sequence :: (Finite a, Ord a, Monad m) => (a -> m b) -> m (a -> b)` (via a `Traversable` instance for functions). – Daniel Wagner Apr 10 '15 at 22:41
  • Sure, it can also be done for monads for which you understand the environment, like `Reader`. – Mokosha Apr 10 '15 at 23:03
2

Something easy to remember is : "you can't escape from a Monad" (it's kind of design for it). Transforming m [a] to [m a] is a form of escape, so you can't.

On the other hand you can easily create a Monad from something (using return) so traversing ([m a] -> m [a]) is usually possible.

mb14
  • 22,276
  • 7
  • 60
  • 102
1

If you take a look at "Monad laws", monad only constrain you to define a composition function but not reverse function.

In the first example you can compose the list elements. In the second one Monad m => m [a] -> [m a], you cannot split an action into multiple actions ( action composition is not reversible).

Example:

Let's say you have to read 2 values.

s1 <- action
s2 <- action

Doing so, action result s2 depends by the side effect made by s1. You can bind these 2 actions in 1 action to be executed in the same order, but you cannot split them and execute action from s2 without s1 made the side effect needed by the second one.

Community
  • 1
  • 1
Gabriel Ciubotaru
  • 1,042
  • 9
  • 22
  • I see what you mean by sequence, but how can I use that to determine, for instance, if a function of type `Monad m => (a -> m b) -> m (a -> b)` is possible? – AJF Apr 10 '15 at 18:54
  • I'm new to Haskell, and i didn't fully understand the Monads but i think it makes non-sense to can decompose a function and take out the action from it. – Gabriel Ciubotaru Apr 10 '15 at 19:04
0

Not really an answer, and much too informal for my linking, but nevertheless I have a few interesting observations that won't fit into a comment. First, let's consider this function you refer to:

f :: Monad m => [m a] -> m [a]

This signature is in fact stronger than it needs to be. The current generalization of this is the sequenceA function from Data.Traversable:

sequenceA :: (Traversable t, Applicative f) -> t (f a) -> f (t a)

...which doesn't need the full power of Monad, and can work with any Traversable and not just lists.

Second: the fact that Traversable only requires Applicative is I think really significant to this question, because applicative computations have a "list-like" structure. Every applicative computation can be rewritten to have the form f <$> a1 <*> ... <*> an for some f. Or, informally, every applicative computation can be seen as a list of actions a1, ... an (heterogeneous on the result type, homogeneous in the functor type), plus an n-place function to combine their results.

If we look at sequenceA through this lens, all it does is choose an f built out of the appropriate nested number of list constructors:

sequenceA [a1, ..., an] == f <$> a1 <*> ... <*> an
    where f v1 ... vn = v1 : ... : vn : []

Now, I haven't had the chance to try and prove this yet, but my conjectures would be the following:

  1. Mathematically speaking at least, sequenceA has a left inverse in free applicative functors. If you have a Functor f => [FreeA f a] and you sequenceA it, what you get is a list-like structure that contains those computations and a combining function that makes a list out of their results. I suspect however that it's not possible to write such a function in Haskell (unSequenceFreeA :: (Traversable t, Functor f) => FreeA f (t a) -> Maybe (t (Free f a))), because you can't pattern match on the structure of the combining function in the FreeA to tell that it's of the form f v1 ... vn = v1 : ... : vn : [].
  2. sequenceA doesn't have a right inverse in a free applicative, however, because the combining function that produces a list out of the results from the a1, ... an actions may do anything; for example, return a constant list of arbitrary length (unrelated to the computations that the free applicative value performs).
  3. Once you move to non-free applicative functors, there will no longer be a left inverse for sequenceA, because the non-free applicative functor's equations translate into cases where you can no longer tell apart which of two t (f a) "action lists" was the source for a given f (t a) "list-producing action."
Luis Casillas
  • 29,802
  • 7
  • 49
  • 102
  • I'm not asking for a specific reason for one example, I'm asking for general rules for working this out, and for Monads specifically, not Applicatives. – AJF Apr 10 '15 at 19:27
  • @AJFarmar: `Applicative` is a superclass of `Monad`, so statements about `Applicative` are certainly relevant to `Monad`. And I don't think the right approach here is that you work this out directly from some general rules: rather, you use [*lemmas*](http://en.wikipedia.org/wiki/Lemma_%28mathematics%29) that follow from general rules. Part of the process of learning to apply general rules is to learn a bunch of lemmas that allow you to recognize specific situations where the rules apply. And there's no shortcut there—you have to read a lot. – Luis Casillas Apr 10 '15 at 19:32