Why does partial application of functions with different signatures work?
Take Control.Monad.join as an example:
GHCi> :t (=<<)
(=<<) :: Monad m => (a -> m b) -> m a -> m b
GHCi> :t id
id :: a -> a
GHCi> :t (=<<) id
(=<<) id :: Monad m => m (m b) -> m b
Why does it accepts id :: a -> a in place of (a -> m b) argument, as they are obviously different ?
=<<'s type signature says that the first argument is a function from an a (anything) to a monad of b.
Well, m b counts as anything, right? So we can just substitute in m b for every a:
(=<<) :: Monad m => (m b -> m b) -> m (m b) -> m b
ids type says that it is a function from anything to the same anything. So if we sub in m b (not forgetting the monad constraint), we get:
id :: Monad m => m b -> m b
Then you can see that the types match.
Some useful concepts to use here:
Any type with a variable a can be converted into a different type by replacing every instance of a with any other type t. So if you have the type a -> b -> c, you can obtain the type a -> d -> c or the type a -> b -> Int by replacing b with d or c with Int respectively.
Any two types that can be converted to each other by replacement are equivalent. For example, a -> b and c -> d are equivalent (a ~ c, b ~ d).
If a type t can be converted to a type t', but t' can't be converted back to t, then we say that t' is a specialization of t. For example, a -> a is a specialization of a -> b.
Now, with these very useful concepts, the answer to your question is very simple: even if the function's "native" types don't match exactly, they are compatible because they can be rewritten or specialized to get that exact match. Matt Fenwick's answer shows specializations that do it for this case.
It tries to unify a with m b, and simply decides that a must be m b, so the type of (=<<) (under the assumption a ~ m b) is Monad m => (mb -> m b) -> m (m b) -> m b, and once you apply it to id, you are left with Monad m => m (m b) -> m b.
Related
In Control.Lens.Lens, there is a function
modifying :: MonadState s m => ASetter s s a b -> (a -> b) -> m ()
which allows the value under a lens on the MonadState state to be transformed by a pure function (a -> b).
However, we may want to allow the transform function to fail in m, requiring it to have type (a -> m b).
I've looked through the lens library for such a function, but I can't find one, so I implemented:
modifyingM l f = use l >>= f >>= assign l
Which does the trick, but I was wondering if there is a function already in the lens library that will do this.
I don't see anything like that. ASetter is defined
type ASetter s t a b = (a -> Identity b) -> s -> Identity t
so it's not powerful enough for the job (and Setter can't do it either). It turns out, on the other hand, that a Lens is a bit stronger than necessary. Let's consider, then, how to do it with a Traversal.
type Traversal s t a b =
forall f. Applicative f => (a -> f b) -> s -> f t
So
Traversal s s a b =
forall f. Applicative f => (a -> f b) -> s -> f s
Which Applicative do we want? m seems like the obvious one to try. When we pass the traversal a -> m b, we get back s -> m s. Great! As usual for lens, we'll actually only require the user to provide an ATraversal, which we can clone.
modifyingM
:: MonadState s m
=> ATraversal s s a b
-> (a -> m b) -> m ()
modifyingM t f = do
s <- get
s' <- cloneTraversal t f s
put s'
That's nice because it only traverses the state once.
Even that is overkill, really. The most natural thing is actually
modifyingM
:: MonadState s m
=> LensLike m s s a b
-> (a -> m b) -> m ()
modifyingM t f = do
s <- get
s' <- t f s
put s'
You can apply that directly to a Traversal, Lens, Iso, or Equality, or use cloneTraversal, cloneLens, cloneIso, or (in lens-4.18 or later) cloneEquality to apply it to the monomorphic variants.
data M a = M a deriving (Show)
unitM a = M a
bindM (M a) f = f a
joinM :: M (M a) -> M a
joinM m = m `bindM` id
joinM' :: M a -> a
joinM' m = m `bindM` id
Note that joinM (M 0) will fail to type check, whereas joinM' (M 0) will be fine.
My question: why is joinM defined as M (M a) -> M a but not as M a -> a?
From my understanding,
unitM puts the value a into the monad M a
joinM gets the value a from the monad M a
So joinM should really work on any monad, i.e., not necessarily nested ones such as M (M a), right?
The point of monads is that you can't get a value out of them. If join had type m a -> a then the IO monad would be perfectly useless, since you could just extract the values freely. The point of monads is that you can chain computations together (>>= can be defined in terms of join, provided you have return and fmap) and put values into a monadic context, but you can't (in general) get them out.
In your specific case, you've defined what is essentially the identity monad. In that case, it's easy to extract the value; you just strip away the layer of M and move on with your life. But that's not true for general monads, so we restrict the type of join so that more things can be monads.
Your bindM is not of the correct type, by the way. The general type of >>= is
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Your function has type
bindM :: M a -> (a -> b) -> b
Notice that your type is more general. Hence, again, in your specific case, you can get away with being looser on the requirements of joinM, whereas specific monads cannot. Try giving bindM an explicit type signature of M a -> (a -> M b) -> M b and then see if both of your join functions still typecheck.
Given a type constructor M :: * -> *, and a type a, consider the following sequence of types
a, M a, M (M a), M (M (M a)), ...
If we have polymorphic functions return :: b -> M b and extract :: M b -> b (your alternative join), we can convert a value of any type above to any other type above. Indeed, we can add and remove M as wanted using these two functions, choosing the type b suitably. In more casual words, we can move both to the right and to the left in such type sequence.
In a monad, instead, we can move to the right without limits (using return). We can also move to the left almost everywhere: the important exception being that we can not move from M a to a. This is realized by join :: M (M c) -> M c, which has the type of extract :: M b -> b restricted to the case b = M c. So essentially, we can move left (as with extract), but only when we end up in a type which has at least one M -- hence, no further to the left than M a.
As Carl mentions above in the comments this restriction makes it possible to have more monads. For instance, if M = [] is the list monad, we can properly implement return and join but not extract.
return :: a -> [a]
return x = [x]
join :: [[a]] -> [a]
join xss = concat xss
Instead extract :: [a] -> a can not be a total function, since extract [] :: a would be well typed, yet tries to extract a value of type a from the empty list. It is a well-known theoretical result that no total expression can have the polymorphic type ... :: a. We can have undefined :: a, fromJust Nothing :: a, or head [] :: a but all of these are not total, and will raise an error when evaluated.
The type signature for >>= is the following:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
And the following makes sense to me (it is also one of the monad laws):
(>>=) (Just 1) (id . return) == Just 1
Yet the Prelude gives the following:
Prelude> :t (>>=) (Just 1) id
(>>=) (Just 1) id :: Num (Maybe b) => Maybe b
I would have expected the Prelude to return some error as the type signature on id is (a -> a) as opposed to Monad m => (a -> m b).
Is there a great way to understand what is going on here? Is there any use for (>>=) (Just 1) id?
The type of id is c -> c (using a different letter so not to conflict with a and b that occur in the type of >>=). We can unify c -> c with a -> Maybe b if we pick c = a = Maybe b.
So this means that >>= in your example is used at type:
(>>=) :: Maybe (Maybe b) -> (Maybe b -> Maybe b) -> Maybe b
Now you have
(>>=) (Just 1) id
For Just 1 to be of type Maybe (Maybe b), Maybe b must be in Num (because then 1 can be interpreted as Maybe b).
I recently stumbled across Djinn and was briefly playing around with it to try to see whether it would be useful in my everyday coding workflow. I was excited to see that Djinn had monads and tried to see whether it might be able to find some cool functions.
Djinn did in fact work some wonders. The type signature of the initially (at least to me) unintuitive function >>= (>>=) is Monad m => ((a -> m b) -> m a) -> (a -> m b) -> m b. Djinn was able to immediately demystify this by stating
Djinn> f ? Monad m => ((a -> m b) -> m a) -> (a -> m b) -> m b
f :: (Monad m) => ((a -> m b) -> m a) -> (a -> m b) -> m b
f a b = a b >>= b
Unfortunately, Djinn can't seem to find other standard functions on monads, despite knowing about the Monad typeclass.
join (which should be join = (>>= id) or in Djinn's more verbose syntax join a = a >>= (\x -> x))
Djinn> join ? Monad m => m (m a) -> m a
-- join cannot be realized.
liftM (which should be liftM f = (>>= (return . f)) or in Djinn's more verbose syntax liftM a b = b >>= (\x -> return (a x)))
Djinn> liftM ? Monad m => (a -> b) -> m a -> m b
-- liftM cannot be realized.
Even the basic return :: Monad m => m a -> m (m a) cannot be found by Djinn or return :: Monad m => (a, b) -> m (a, b).
Djinn> f ? Monad m => (a, b) -> m (a, b)
-- f cannot be realized.
Djinn knows how to use \ to construct anonymous functions so why is this the case?
My rough suspicion is that perhaps Djinn has a simplistic notion of typeclass and somehow treats m a as "fixed" so that m (a, b) is not seen as a case of m a, but I have no idea how to make that any more concrete than its current hand-wavy form or whether that intuition is correct.
Supporting type classes properly looks a lot like supporting rank-2 types; compare:
join :: Monad m
=> m (m a) -> m a
vs:
join :: (forall a. a -> m a)
-> (forall a b. m a -> (a -> m b) -> m b)
-> m (m a) -> m a
Unfortunately, the techniques Djinn uses don't handle rank-2 types at all. If you float the foralls so that Djinn can process it, suddenly what you get instead are concrete choices:
join :: (b -> m b)
-> (m c -> (c -> m d) -> m d)
-> m (m a) -> m a
which looks a lot less like you could implement it! If you tell Djinn which instantiations to use, it does a lot better, of course.
join :: (b -> m b)
-> (m (m a) -> (m a -> m a) -> m a)
-> m (m a) -> m a
For this one, Djinn will give the right implementation. ...but then, that's cheating.
The join utility function is defined as:
join :: (Monad m) => m (m a) -> m a
join x = x >>= id
Given that the type of >>= is Monad m => m a -> (a -> m b) -> m b and id is a -> a, how can that function also be typed as a -> m b as it must be in the definition above? What are m and b in this case?
The as in the types for >>= and id aren't necessarily the same as, so let's restate the types like this:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
id :: c -> c
So we can conclude that c is the same as a after all, at least when id is the second argument to >>=... and also that c is the same as m b. So a is the same as m b. In other words:
(>>= id) :: Monad m => m (m b) -> m b
dave4420 hits it, but I think the following remarks might still be useful.
There are rules that you can use to validly "rewrite" a type into another type that's compatible with the original. These rules involve replacing all occurrences of a type variable with some other type:
If you have id :: a -> a, you can replace a with c and get id :: c -> c. This latter type can also be rewritten to the original id :: a -> a, which means that these two types are equivalent. As a general rule, if you replace all instances of type variable with another type variable that occurs nowhere in the original, you get an equivalent type.
You can replace all occurrences of a type variable with a concrete type. I.e., if you have id :: a -> a, you can rewrite that to id :: Int -> Int. The latter however can't be rewritten back to the original, so in this case you're specializing the type.
More generally than the second rule, you can replace all occurrences of a type variable any type, concrete or variable. So for example, if you have f :: a -> m b, you can replace all occurrences of a with m b and get f :: m b -> m b. Since this one can't be undone either, it's also a specialization.
That last example shows how id can be used as the second argument of >>=. So the answer to your question is that we can rewrite and derive types as follows:
1. (>>=) :: m a -> (a -> m b) -> m b (premise)
2. id :: a -> a (premise)
3. (>>=) :: m (m b) -> (m b -> m b) -> m b (replace a with m b in #1)
4. id :: m b -> m b (replace a with m b in #2)
.
.
.
n. (>>= id) :: m (m b) -> m b (indirectly from #3 and #4)