liftM2 foo a b
when we could instead write foo <$> a <*> b
? But we seldom use the Applicative as such — when Functor is too little, Monad is too much, but a lax monoidal functor is just right. I noticed lately a spate of proper uses of Applicative —Formlets (and their later incarnation in the reform library), OptParse-Applicative (and its competitor library CmdTheLine), and a post by Gergo Erdi on applicatives for declaring dependencies of computations. I also ran into a very similar genuine use for applicatives in working on the Panels library (part of jmacro-rpc), where I wanted to determine dependencies of a dynamically generated dataflow computation. And then, again, I stumbled into an applicative while cooking up a form validation library, which turned out to be a reinvention of the same ideas as formlets.
Given all this, It seems post on thinking with applicatives is in order, showing how to build them up and reason about them. One nice thing about the approach we'll be taking is that it uses a "final" encoding of applicatives, rather than building up and then later interpreting a structure. This is in fact how we typically write monads (pace operational, free, etc.), but since we more often only determine our data structures are applicative after the fact, we often get some extra junk lying around (OptParse-Applicative, for example, has a GADT that I think is entirely extraneous).
So the usual throat clearing:
{-# LANGUAGE TypeOperators, MultiParamTypeClasses, FlexibleInstances, StandaloneDeriving, FlexibleContexts, UndecidableInstances, GADTs, KindSignatures, RankNTypes #-} module Main where import Control.Applicative hiding (Const) import Data.Monoid hiding (Sum, Product) import Control.Monad.Identity instance Show a => Show (Identity a) where show (Identity x) = "(Identity " ++ show x ++ ")"
And now, let's start with a classic applicative, going back to the Applicative Programming With Effects paper:
data Const mo a = Const mo deriving Show instance Functor (Const mo) where fmap _ (Const mo) = Const mo instance Monoid mo => Applicative (Const mo) where pure _ = Const mempty (Const f) < *> (Const x) = Const (f <> x)
(Const
lives in transformers as the Constant
functor, or in base as Const
)
Note that Const
is not a monad. We've defined it so that its structure is independent of the `a` type. Hence if we try to write (>>=)
of type Const mo a -> (a -> Const mo b) -> Const mo b
, we'll have no way to "get out" the first `a` and feed it to our second argument.
One great thing about Applicatives is that there is no distinction between applicative transformers and applicatives themselves. This is to say that the composition of two applicatives is cleanly and naturally always also an applicative. We can capture this like so:
newtype Compose f g a = Compose (f (g a)) deriving Show instance (Functor f, Functor g) => Functor (Compose f g) where fmap f (Compose x) = Compose $ (fmap . fmap) f x instance (Applicative f, Applicative g) => Applicative (Compose f g) where pure = Compose . pure . pure (Compose f) < *> (Compose x) = Compose $ (< *>) < $> f < *> x
(Compose
also lives in transformers)
Note that Applicatives compose two ways. We can also write:
data Product f g a = Product (f a) (g a) deriving Show instance (Functor f, Functor g) => Functor (Product f g) where fmap f (Product x y) = Product (fmap f x) (fmap f y) instance (Applicative f, Applicative g) => Applicative (Product f g) where pure x = Product (pure x) (pure x) (Product f g) < *> (Product x y) = Product (f < *> x) (g < *> y)
(Product
lives in transformers as well)
This lets us now construct an extremely rich set of applicative structures from humble beginnings. For example, we can reconstruct the Writer Applicative.
type Writer mo = Product (Const mo) Identity tell :: mo -> Writer mo () tell x = Product (Const x) (pure ())
-- tell [1] *> tell [2] -- > Product (Const [1,2]) (Identity ())
Note that if we strip away the newtype noise, Writer turns into (mo,a)
which is isomorphic to the Writer monad. However, we've learned something along the way, which is that the monoidal component of Writer (as long as we stay within the rules of applicative) is entirely independent from the "identity" component. However, if we went on to write the Monad instance for our writer (by defining >>=
), we'd have to "reach in" to the identity component to grab a value to hand back to the function yielding our monoidal component. Which is to say we would destroy this nice seperation of "trace" and "computational content" afforded by simply taking the product of two Applicatives.
Now let's make things more interesting. It turns out that just as the composition of two applicatives may be a monad, so too the composition of two monads may be no stronger than an applicative!
We'll see this by introducing Maybe into the picture, for possibly failing computations.
type FailingWriter mo = Compose (Writer mo) Maybe tellFW :: Monoid mo => mo -> FailingWriter mo () tellFW x = Compose (tell x *> pure (Just ())) failFW :: Monoid mo => FailingWriter mo a failFW = Compose (pure Nothing)
-- tellFW [1] *> tellFW [2] -- > Compose (Product (Const [1,2]) (Identity Just ())) -- tellFW [1] *> failFW *> tellFW [2] -- > Compose (Product (Const [1,2]) (Identity Nothing))
Maybe over Writer gives us the same effects we'd get in a Monad — either the entire computation fails, or we get the result and the trace. But Writer over Maybe gives us new behavior. We get the entire trace, even if some computations have failed! This structure, just like Const, cannot be given a proper Monad instance. (In fact if we take Writer over Maybe as a Monad, we get only the trace until the first point of failure).
This seperation of a monoidal trace from computational effects (either entirely independent of a computation [via a product] or independent between parts of a computation [via Compose]) is the key to lots of neat tricks with applicative functors.
Next, let's look at Gergo Erdi's "Static Analysis with Applicatives" that is built using free applicatives. We can get essentially the same behavior directly from the product of a constant monad with an arbitrary effectful monad representing our ambient environment of information. As long as we constrain ourselves to only querying it with the takeEnv function, then we can either read the left side of our product to statically read dependencies, or the right side to actually utilize them.
type HasEnv k m = Product (Const [k]) m takeEnv :: (k -> m a) -> k -> HasEnv k m a takeEnv f x = Product (Const [x]) (f x)
If we prefer, we can capture queries of a static environment directly with the standard Reader applicative, which is just a newtype over the function arrow. There are other varients of this that perhaps come closer to exactly how Erdi's post does things, but I think this is enough to demonstrate the general idea.
data Reader r a = Reader (r -> a) instance Functor (Reader r) where fmap f (Reader x) = Reader (f . x) instance Applicative (Reader r) where pure x = Reader $ pure x (Reader f) < *> (Reader x) = Reader (f < *> x) runReader :: (Reader r a) -> r -> a runReader (Reader f) = f takeEnvNew :: (env -> k -> a) -> k -> HasEnv k (Reader env) a takeEnvNew f x = Product (Const [x]) (Reader $ flip f x)
So, what then is a full formlet? It's something that can be executed in one context as a monoid that builds a form, and in another as a parser. so the top level must be a product.
type FormletOne mo a = Product (Const mo) Identity a
Below the product, we read from an environment and perhaps get an answer. So that's reader with a maybe.
type FormletTwo mo env a = Product (Const mo) (Compose (Reader env) Maybe) a
Now if we fail, we want to have a trace of errors. So we expand out the Maybe into a product as well to get the following, which adds monoidal errors:
type FormletThree mo err env a = Product (Const mo) (Compose (Reader env) (Product (Const err) Maybe)) a
But now we get errors whether or not the parse succeeds. We want to say either the parse succeeds or we get errors. For this, we can turn to the typical Sum functor, which currently lives as Coproduct in comonad-transformers, but will hopefully be moving as Sum to the transformers library in short order.
data Sum f g a = InL (f a) | InR (g a) deriving Show instance (Functor f, Functor g) => Functor (Sum f g) where fmap f (InL x) = InL (fmap f x) fmap f (InR y) = InR (fmap f y)
The Functor instance is straightforward for Sum, but the applicative instance is puzzling. What should "pure" do? It needs to inject into either the left or the right, so clearly we need some form of "bias" in the instance. What we really need is the capacity to "work in" one side of the sum until compelled to switch over to the other, at which point we're stuck there. If two functors, F and G are in a relationship such that we can always send f x -> g x
in a way that "respects" fmap (that is to say, such that (fmap f . fToG == ftoG . fmap f
) then we call this a natural transformation. The action that sends f to g is typically called "eta". (We actually want something slightly stronger called a "monoidal natural transformation" that respects not only the functorial action fmap
but the applicative action <*>
, but we can ignore that for now).
Now we can assert that as long as there is a natural transformation between g and f, then Sum f g can be made an Applicative, like so:
class Natural f g where eta :: f a -> g a instance (Applicative f, Applicative g, Natural g f) => Applicative (Sum f g) where pure x = InR $ pure x (InL f) < *> (InL x) = InL (f < *> x) (InR g) < *> (InR y) = InR (g < *> y) (InL f) < *> (InR x) = InL (f < *> eta x) (InR g) < *> (InL x) = InL (eta g < *> x)
The natural transformation we'll tend to use simply sends any functor to Const.
instance Monoid mo => Natural f (Const mo) where eta = const (Const mempty)
However, there are plenty of other natural transformations that we could potentially make use of, like so:
instance Applicative f => Natural g (Compose f g) where eta = Compose . pure instance (Applicative g, Functor f) => Natural f (Compose f g) where eta = Compose . fmap pure instance (Natural f g) => Natural f (Product f g) where eta x = Product x (eta x) instance (Natural g f) => Natural g (Product f g) where eta x = Product (eta x) x instance Natural (Product f g) f where eta (Product x _ ) = x instance Natural (Product f g) g where eta (Product _ x) = x instance Natural g f => Natural (Sum f g) f where eta (InL x) = x eta (InR y) = eta y instance Natural Identity (Reader r) where eta (Identity x) = pure x
In theory, there should also be a natural transformation that can be built magically from the product of any other two natural transformations, but that will just confuse the Haskell typechecker hopelessly. This is because we know that often different "paths" of typeclass choices will often be isomorphic, but the compiler has to actually pick one "canonical" composition of natural transformations to compute with, although multiple paths will typically be possible.
For similar reasons of avoiding overlap, we can't both have the terminal homomorphism that sends everything to "Const" and the initial homomorphism that sends "Identity" to anything like so:
-- instance Applicative g => Natural Identity g where -- eta (Identity x) = pure x
We choose to keep the terminal transformation around because it is more generally useful for our purposes. As the comments below point out, it turns out that a version of "Sum" with the initial transformation baked in now lives in transformers as Lift
.
In any case we can now write a proper Validation applicative:
type Validation mo = Sum (Const mo) Identity validationError :: Monoid mo => mo -> Validation mo a validationError x = InL (Const x)
This applicative will yield either a single result, or an accumulation of monoidal errors. It exists on hackage in the Validation package.
Now, based on the same principles, we can produce a full Formlet.
type Formlet mo err env a = Product (Const mo) (Compose (Reader env) (Sum (Const err) Identity)) a
All the type and newtype noise looks a bit ugly, I'll grant. But the idea is to think with structures built with applicatives, which gives guarantees that we're building applicative structures, and furthermore, structures with certain guarantees in terms of which components can be interpreted independently of which others. So, for example, we can strip away the newtype noise and find the following:
type FormletClean mo err env a = (mo, env -> Either err a)
Because we built this up from our basic library of applicatives, we also know how to write its applicative instance directly.
Now that we've gotten a basic algebraic vocabulary of applicatives, and especially now that we've produced this nifty Sum applicative (which I haven't seen presented before), we've gotten to where I intended to stop.
But lots of other questions arise, on two axes. First, what other typeclasses beyond applicative do our constructions satisfy? Second, what basic pieces of vocabulary are missing from our constructions — what do we need to add to flesh out our universe of discourse? (Fixpoints come to mind).
Also, what statements can we make about "completeness" — what portion of the space of all applicatives can we enumerate and construct in this way? Finally, why is it that monoids seem to crop up so much in the course of working with Applicatives? I plan to tackle at least some of these questions in future blog posts.
]]>In category theory, the codensity monad is given by the rather frightening expression:
Where the integral notation denotes an end, and the square brackets denote a power, which allows us to take what is essentially an exponential of the objects of by objects of
, where
is enriched in
. Provided the above end exists,
is a monad regardless of whether
has an adjoint, which is the usual way one thinks of functors (in general) giving rise to monads.
It also turns out that this construction is a sort of generalization of the adjunction case. If we do have , this gives rise to a monad
. But, in such a case,
, so the codensity monad is the same as the monad given by the adjunction when it exists, but codensity may exist when there is no adjunction.
In Haskell, this all becomes a bit simpler (to my eyes, at least). Our category is always
, which is enriched in itself, so powers are just function spaces. And all the functors we write will be rather like
(objects will come from kinds we can quantify over), so ends of functors will look like
forall r. F r r
where . Then:
newtype Codensity f a = Codensity (forall r. (a -> f r) -> f r)
As mentioned, we've known for a while that we can write a Monad instance for Codensity f
without caring at all about f
.
As for the adjunction correspondence, consider the adjunction between products and exponentials:
This gives rise to the monad , the state monad. According to the facts above, we should have that
Codensity (s ->)
(excuse the sectioning) is the same as state, and if we look, we see:
forall r. (a -> s -> r) -> s -> r
which is the continuation passing, or Church (or Boehm-Berarducci) encoding of the monad.
Now, it's also well known that for any monad, we can construct an adjunction that gives rise to it. There are multiple ways to do this, but the most accessible in Haskell is probably via the Kleisli category. So, given a monad on
, there is a category
with the same objects, but where
. The identity for each object is
return
and composition of arrows is:
(f >=> g) x = f x >>= g
Our two functors are:
F a = a
F f = return . f
U a = M a
U f = (>>= f)
Verifying that requires only that
, but this is just
, which is a triviality. Now we should have that
.
So, one of the simplest monads is reader, . Now,
just takes objects in the Kleisli category (which are objects in
) and applies
to them, so we should have
Codensity (e ->)
is reader. But earlier we had Codensity (e ->)
was state. So reader is state, right?
We can actually arrive at this result another way. One of the most famous pieces of category theory is the Yoneda lemma, which states that the following correspondence holds for any functor :
This also works for any functor into and looks like:
F a ~= forall r. (a -> r) -> F r
for . But we also have our functor
, which should look more like:
U a ~= forall r. (a -> M r) -> U r
M a ~= forall r. (a -> M r) -> M r
So, we fill in M = (e ->)
and get that reader is isomorphic to state, right? What's going on?
To see, we have to take a closer look at natural transformations. Given two categories and
, and functors
, a natural transformation
is a family of maps
such that for every
the following diagram commutes:
The key piece is what the morphisms look like. It's well known that parametricity ensures the naturality of t :: forall a. F a -> G a
for , and it also works when the source is
. It should also work for a category, call it
, which has Haskell types as objects, but where
, which is the sort of category that
newtype Endo a = Endo (a -> a)
is a functor from. So we should be at liberty to say:
Codensity Endo a = forall r. (a -> r -> r) -> r -> r ~= [a]
However, hom types for are not merely made up of
hom types on the same arguments, so naturality turns out not to be guaranteed. A functor
must take a Kleisli arrow
to an arrow
, and transformations must commute with that mapping. So, if we look at our use of Yoneda, we are considering transformations
:
Now, and
. So
t :: forall r. (a -> M r) -> M r
will get us the right type of maps. But, the above commutative square corresponds to the condition that for all f :: b -> M c
:
t . (>=> f) = (>>= f) . t
So, if we have h :: a -> M b
, Kleisli composing it with f
and then feeding to t
is the same as feeding h
to t
and then binding the result with f
.
Now, if we go back to reader, we can consider the reader morphism:
f = const id :: a -> e -> e
For all relevant m
and g
, m >>= f = id
and g >=> f = f
. So the
naturality condition here states that t f = id
.
Now, t :: forall r. (a -> e -> r) -> e -> r
. The general form of these is state actions (I've split e -> (a, e)
into two pieces):
t f e = f (v e) (st e) where rd :: e -> a st :: e -> e
If f = const id
, then:
t (const id) e = st e where st :: e -> e
But our naturality condition states that this must be the identity, so we must have st = id
. That is, the naturality condition selects t
for which the corresponding state action does not change the state, meaning it is equivalent to a reader action! Presumably the definition of an end (which involves dinaturality) enforces a similar condition, although I won't work through it, as it'd be rather more complicated.
However, we have learned a lesson. Quantifiers do not necessarily enforce (di)naturality for every category with objects of the relevant kind. It is important to look at the hom types, not just the objects .In this case, the point of failure seems to be the common, extra s
. Even though the type contains nautral transformations for the similar functors over , they can (in general) still manipulate the shared parameter in ways that are not natural for the domain in question.
I am unsure of how exactly one could enforce the above condition in (Haskell's) types. For instance, if we consider:
forall r m. Monad m => (a -> m r) -> m r
This still contains transformations of the form:
t k = k a >> k a
And for this to be natural would require:
(k >=> f) a >> (k >=> f) a = (k a >> k a) >>= f
Which is not true for all possible instantiations of f. It seems as though leaving m
unconstrained would be sufficient, as all that could happen is t
feeding a value to k
and yielding the result, but it seems likely to be over-restrictive.
Fortunately, while it was rather inconvenient that they shut down Google Knol completely, and I'll forever remember a knol as a "unit of abandonment", Google did provide a nice way to download at least your own user content and for that I am grateful.
I have fixed up the internal linkage as much as possible and have placed a copy of the original article below.
Sadly, as I am not "Dark Magus", I am unable to download the Russian translation. If anyone knows how to contact him, I would love to obtain and preserve a copy of the translation as well.
]]>Constraint Kinds adds a new kind Constraint
, such that Eq :: * -> Constraint
, Monad :: (* -> *) -> Constraint
, but since it is a kind, we can make type families for constraints, and even parameterize constraints on constraints.
So, let's play with them and see what we can come up with!
First, we'll need a few language features:
{-# LANGUAGE
CPP,
ScopedTypeVariables,
FlexibleInstances,
FlexibleContexts,
ConstraintKinds,
KindSignatures,
TypeOperators,
FunctionalDependencies,
Rank2Types,
StandaloneDeriving,
GADTs
#-}
Because of the particular version of GHC I'm using I'll also need
{-# LANGUAGE UndecidableInstances #-}
#define UNDECIDABLE
but this bug has been fixed in the current version of GHC Head. I'll be explicit about any instances that need UndecidableInstances by surrounding them in an #ifdef UNDECIDABLE
block.
So with that out of the way, let's import some definitions
import Control.Monad import Control.Monad.Instances import Control.Applicative import Data.Monoid import Data.Complex import Data.Ratio import Unsafe.Coerce
and make one of our own that shows what we get out of making Constraints into a kind we can manipulate like any other.
data Dict a where Dict :: a => Dict a
Previously, we coud make a Dict like data type for any one particular class constraint that we wanted to capture, but now we can write this type once and for all. The act of pattern matching on the Dict constructor will bring the constraint 'a' into scope.
Of course, in the absence of incoherent and overlapping instances there is at most one dictionary of a given type, so we could make instances, like we can for any other data type, but standalone deriving is smart enough to figure these out for me. (Thanks copumpkin!)
deriving instance Eq (Dict a) deriving instance Ord (Dict a) deriving instance Show (Dict a)
If we're willing to turn on UndecidableInstances to enable the polymorphic constraint we can even add:
#ifdef UNDECIDABLE deriving instance a => Read (Dict a) instance a => Monoid (Dict a) where mappend Dict Dict = Dict mempty = Dict #endif
and similar polymorphically constrained instances for Enum
, Bounded
, etc.
For that we'll need a notion of entailment.
infixr 9 :- newtype a :- b = Sub (a => Dict b) instance Eq (a :- b) where _ == _ = True instance Ord (a :- b) where compare _ _ = EQ instance Show (a :- b) where showsPrec d _ = showParen (d > 10) $ showString "Sub Dict"
Here we're saying that Sub
takes one argument, which is a computation that when implicitly given a constraint of type a, can give me back a dictionary for the type b. Moreover, as a newtype it adds no overhead that isn't aleady present in manipulating terms of type (a => Dict b) directly.
The simplest thing we can define with this is that entailment is reflexive.
refl :: a :- a refl = Sub Dict
Max has already written up a nice restricted monad example using these, but what I want to play with today is the category of substitutability of constraints, but there are a few observations I need to make, first.
ConstraintKinds overloads ()
and (a,b)
to represent the trivial constraint and the product of two constraints respectively.
The latter is done with a bit of a hack, which we'll talk about in a minute, but we can use the former as a terminal object for our category of entailments.
weaken1 :: (a, b) :- a
weaken1 = Sub Dict
weaken2 :: (a, b) :- b
weaken2 = Sub Dict
Constraints are idempotent, so we can duplicate one, perhaps as a prelude to transforming one of them into something else.
contract :: a :- (a, a) contract = Sub Dict
But to do much more complicated, we're going to need a notion of substitution, letting us use our entailment relation to satisfy obligations.
infixl 1 \\ -- required comment (\\) :: a => (b => r) -> (a :- b) -> r r \\ Sub Dict = r
The type says that given that a constraint a can be satisfied, a computation that needs a constraint of type b to be satisfied in order to obtain a result, and the fact that a entails b, we can compute the result.
The constraint a is satisfied by the type signature, and the fact that we get quietly passed whatever dictionary is needed. Pattern matching on Sub brings into scope a computation of type (a => Dict b)
, and we are able to discharge the a obligation, using the dictionary we were passed, Pattern matching on Dict
forces that computation to happen and brings b into scope, allowing us to meet the obligation of the computation of r. All of this happens for us behind the scenes just by pattern matching.
So what can we do with this?
We can use \\ to compose constraints.
trans :: (b :- c) -> (a :- b) -> a :- c trans f g = Sub $ Dict \\ f \\ g
In fact, the way the dictionaries get plumbed around inside the argument to Sub is rather nice, because we can give that same definition different type signatures, letting us make (,) more product-like, giving us the canonical product morphism to go with the weakenings/projections we defined above.
(&&&) :: (a :- b) -> (a :- c) -> a :- (b, c) f &&& g = Sub $ Dict \\ f \\ g
And since we're using it as a product, we can make it act like a bifunctor also using the same definition.
(***) :: (a :- b) -> (c :- d) -> (a, c) :- (b, d) f *** g = Sub $ Dict \\ f \\ g
Ideally we'd be able to capture something like that bifunctoriality using a type like
#if 0 class BifunctorS (p :: Constraint -> Constraint -> Constraint) where bimapS :: (a :- b) -> (c :- d) -> p a c :- p b d #endif
In an even more ideal world, it would be enriched using something like
#ifdef POLYMORPHIC_KINDS class Category (k :: x -> x -> *) where id :: k a a (.) :: k b c -> k a b -> k a c instance Category (:-) where id = refl (.) = trans #endif
where x is a kind variable, then we could obtain a more baroque and admittedly far less thought-out bifunctor class like:
#if 0 class Bifunctor (p :: x -> y -> z) where type Left p :: x -> x -> * type Left p = (->) type Right p :: y -> y -> * type Right p = (->) type Cod p :: z -> z -> * type Cod p = (->) bimap :: Left p a b -> Right p c d -> Cod p (p a c) (p b d) #endif
Or even more more ideally, you could use the fact that we can directly define product categories!
Since they are talking about kind-indexing for classes and type families, we could have separate bifunctors for (,) for both kinds * and Constraint.
The current constraint kind code uses a hack to let (a,b) be used as a type inhabiting * and as the syntax for constraints. This hack is limited however. It only works when the type (,) is fully applied to its arguments. Otherwise you'd wind up with the fact that the type (,) needs to have both of these kinds:
-- (,) :: Constraint -> Constraint -> Constraint and -- (,) :: * -> * -> *
What is currently done is that the kind magically switches for ()
and (,)
in certain circumstances. GHC already had some support for this because it parses (Foo a, Bar b)
as a type in (Foo a, Bar b) => Baz a b
before transforming it into a bunch of constraints.
Since we already have a notion of sub-kinding at the kind level, we could solve this for ()
by making up a new kind, say, ???
which is the subkind of both *
and Constraint
, but this would break the nice join lattice properties of the current system.
[Edit: in the initial draft, I had said superkind]
-- ? -- / \ -- (#) ?? -- / \ -- # * Constraint -- \ / -- ???
But this doesn't address the kind of (,)
above. With the new polymorphic kinds that Brent Yorgey and company have been working on and a limited notion of sub-superkinding, this could be resolved by making a new super-kind @
that is the super-kind of both *
and Constraint
, and which is a sub-superkind of the usual unnamed Box superkind.
-- Box -- | -- @
Then we can have:
-- (,) :: forall (k :: @). k -> k -> k -- () :: forall (k :: @). k
and kind checking/inference will do the right thing about keeping the kind ambiguous for types like (,) () :: forall (k :: @). k
This would get rid of the hack and let me make a proper bifunctor for (,)
in the category of entailments.
The version of GHC head I'm working with right now doesn't support polymorphic kinds, so I've only been playing with these in a toy type checker, but I'm really looking forward to being able to have product categories!
Next, we'll go over how to reflect the class and instance declarations so we can derive entailment of a superclass for a class, and the entailment of instances.
[Source]
]]>In particular, I want to talk about a novel encoding of linear functionals, polynomials and linear maps in Haskell, but first we're going to have to build up some common terminology.
Having obtained the blessing of Wolfgang Jeltsch, I replaced the algebra package on hackage with something... bigger, although still very much a work in progress.
(Infinite) Modules over Semirings
Recall that a vector space V over a field F is given by an additive Abelian group on V, and a scalar multiplication operator
(.*) :: F -> V -> V
subject to distributivity laws
s .* (u + v) = s .* u + s .* v (s + t) .* v = s .* v + t .* v
and associativity laws
(s * t) .* v = s .* (t .* v)
and respect of the unit of the field.
1 .* v = v
Since multiplication on a field is commutative, we can also add
(*.) :: V -> F -> V v *. f = f .* v
with analogous rules.
But when F is only a Ring, we call the analogous structure a module, and in a ring, we can't rely on the commutativity of multiplication, so we may have to deal left-modules and right-modules, where only one of those products is available.
We can weaken the structure still further. If we lose the negation in our Ring we and go to a Rig (often called a Semiring), now our module is an additive moniod.
If we get rid of the additive and multiplicative unit on our Rig we get down to what some authors call a Ringoid, but which we'll call a Semiring here, because it makes the connection between semiring and semigroup clearer, and the -oid suffix is dangerously overloaded due to category theory.
First we'll define additive semigroups, because I'm going to need both additive and multiplicative monoids over the same types, and Data.Monoid has simultaneously too much and too little structure.
-- (a + b) + c = a + (b + c) class Additive m where (+) :: m -> m -> m replicate1p :: Whole n => n -> m -> m -- (ignore this for now) -- ...
their Abelian cousins
-- a + b = b + a class Additive m => Abelian m
and Multiplicative semigroups
-- (a * b) * c = a * (b * c) class Multiplicative m where (*) :: m -> m -> m pow1p :: Whole n => m -> n -> m -- ...
Then we can define a semirings
-- a*(b + c) = a*b + a*c -- (a + b)*c = a*c + b*c class (Additive m, Abelian m, Multiplicative m) => Semiring
With that we can define modules over a semiring:
-- r .* (x + y) = r .* x + r .* y -- (r + s) .* x = r .* x + s .* x -- (r * s) .* x = r .* (s .* x) class (Semiring r, Additive m) => LeftModule r m (.*) :: r -> m -> m
and analogously:
class (Semiring r, Additive m) => RightModule r m (*.) :: m -> r -> m
For instance every additive semigroup forms a semiring module over the positive natural numbers (1,2..) using replicate1p.
If we know that our addition forms a monoid, then we can form a module over the naturals as well
-- | zero + a = a = a + zero class (LeftModule Natural m, RightModule Natural m ) => AdditiveMonoid m where zero :: m replicate :: Whole n => n -> m -> m
and if our addition forms a group, then we can form a module over the integers
-- | a + negate a = zero = negate a + a class (LeftModule Integer m , RightModule Integer m ) => AdditiveGroup m where negate :: m -> m times :: Integral n => n -> m -> m -- ...
Free Modules over Semirings
A free module on a set E, is a module where the basis vectors are elements of E. Basically it is |E| copies of some (semi)ring.
In Haskell we can represent the free module of a ring directly by defining the action of the (semi)group pointwise.
instance Additive m => Additive (e -> m) where f + g = \x -> f x + g x instance Abelian m => Abelian (e -> m) instance AdditiveMonoid m => AdditiveMonoid (e -> m) where zero = const zero instance AdditiveGroup m => AdditveGroup (e -> m) where f - g = \x -> f x - g x
We could define the following
instance Semiring r => LeftModule r (e -> m) where r .* f = \x -> r * f x
but then we'd have trouble dealing with the Natural and Integer constraints above, so instead we lift modules
instance LeftModule r m => LeftModule r (e -> m) where (.*) m f e = m .* f e instance RightModule r m => RightModule r (e -> m) where (*.) f m e = f e *. m
We could go one step further and define multiplication pointwise, but while the direct product of |e| copies of a ring _does_ define a ring, and this ring is the one provided by the Conal Elliot's vector-space
package, it isn't the most general ring we could construct. But we'll need to take a detour first.
Linear Functionals
A Linear functional f on a module M is a linear function from a M to its scalars R.
That is to say that, f : M -> R such that
f (a .* x + y) = a * f x + f y
Consequently linear functionals also form a module over R. We call this module the dual module M*.
Dan Piponi has blogged about these dual vectors (or covectors) in the context of trace diagrams.
If we limit our discussion to free modules, then M = E -> R, so a linear functional on M looks like (E -> R) -> R
subject to additional linearity constraints on the result arrow.
The main thing we're not allowed to do in our function is apply our function from E -> R to two different E's and then multiply the results together. Our pointwise definitions above satisfy those linearity constraints, but for example:
bad f = f 0 * f 0
does not.
We could capture this invariant in the type by saying that instead we want
newtype LinearM r e = LinearM { runLinearM :: forall r. LeftModule r m => (e -> m) -> m }
we'd have to make a new such type every time we subclassed Semiring. I'll leave further exploration of this more exotic type to another time. (Using some technically illegal module instances we can recover more structure that you'd expect.)
Now we can package up the type of covectors/linear functionals:
infixr 0 $* newtype Linear r a = Linear { ($*) :: (a -> r) -> r }
The sufficiently observant may have already noticed that this type is the same as the Cont monad (subject to the linearity restriction on the result arrow).
In fact the Functor
, Monad
, Applicative
instances for Cont
all carry over, and preserve linearity.
(We lose callCC
, but that is at least partially due to the fact that callCC
has a less than ideal type signature.)
In addition we get a number of additional instances for Alternative
, MonadPlus
, by exploiting the knowledge that r is ring-like:
instance AdditiveMonoid r => Alternative (Linear r a) where Linear f < |> Linear g = Linear (f + g) empty = Linear zero
Note that the (+)
and zero
there are the ones defined on functions from our earlier free module construction!
Linear Maps
Since Linear r
is a monad, Kleisli (Linear r)
forms an Arrow
:
b -> ((a -> r) ~> r)
where the ~> denotes the arrow that is constrained to be linear.
If we swap the order of the arguments so that
(a -> r) ~> (b -> r)
this arrow has a very nice meaning! (See Numeric.Map.Linear)
infixr 0 $# newtype Map r b a = Map { ($#) :: (a -> r) -> (b -> r) }
Map r b a
represents the type of linear maps from a -> b
. Unfortunately due to contravariance the arguments wind up in the "wrong" order.
instance Category (Map r) where Map f . Map g = Map (g . f) id = Map id
So we can see that a linear map from a module A with basis a
to a vector space with basis b
effectively consists of |b| linear functionals on A.
Map r b a
provides a lot of structure. It is a valid instance of an insanely large number of classes.
Vectors and Covectors
In physics, we sometimes call linear functionals covectors or covariant vectors, and if we're feeling particularly loquacious, we'll refer to vectors as contravariant vectors.
This has to do with the fact that when you change basis, you change map the change over covariant vectors covariantly, and map the change over vectors contravariantly. (This distinction is beautifully captured by Einstein's summation notation.)
We also have a notion of covariance and contravariance in computer science!
Functions vary covariantly in their result, and contravariant in their argument. E -> R
is contravariant in E. But we chose this representation for our free modules, so the vectors in our free vector space (or module) are contravariant in E.
class Contravariant f where contramap :: (a -> b) -> f a -> f b -- | Dual function arrows. newtype Op a b = Op { getOp :: b -> a } instance Contravariant (Op a) where contramap f g = Op (getOp g . f)
On the other hand (E -> R) ~> R
varies covariantly with the change of E
.
as witnessed by the fact that it is a Functor
.
instance Functor (Linear r) where fmap f m = Linear $ \k -> m $* k . f
We have lots of classes for manipulating covariant structures, and most of them apply to both (Linear r) and (Map r b).
Other Representations and Design Trade-offs
One common representation of vectors in a free vector space is as some kind of normalized list of scalars and basis vectors. In particular, David Amos's wonderful HaskellForMaths uses
newtype Vect r a = Vect { runVect :: [(r,a)] }
for free vector spaces, only considering them up to linearity, paying for normalization as it goes.
Given the insight above we can see that Vect isn't a representation of vectors in the free vector space, but instead represents the covectors of that space, quite simply because Vect r a varies covariantly with change of basis!
Now the price of using the Monad
on Vect r
is that the monad denormalizes the representation. In particular, you can have multiple copies of the same basis vector., so any function that uses Vect r a
has to merge them together.
On the other hand with the directly encoded linear functionals we've described here, we've placed no obligations on the consumer of a linear functional. They can feed the directly encoded linear functional any vector they want!
In fact, it'll even be quite a bit more efficient to compute,
To see this, just consider:
instance MultiplicativeMonoid r => Monad (Vect r) where return a = Vect [(1,a)] Vect as >>= f = Vect [ (p*q, b) | (p,a) < - as, (q,b) <- runVect (f b) ]
Every >>= must pay for multiplication. Every return will multiply the element by one. On the other hand, the price of return and bind in Linear r is function application.
instance Monad (Linear r) where return a = Linear $ \k -> k a m >>= f = Linear $ \k -> m $* \a -> f a $* k
A Digression on Free Linear Functionals
To wax categorical for a moment, we can construct a forgetful functor U : Vect_F -> Set
that takes a vector space over F to just its set of covectors.
F E = (E -> F, F,\f g x -> f x + g x ,\r f x -> r * f x)
using the pointwise constructions we built earlier.
Then in a classical setting, you can show that F is left adjoint to U.
In particular the witnesses of this adjunction provide the linear map from (E -> F) to V and the function E -> (V ~> F) giving a linear functional on V for each element of E.
In a classical setting you can go a lot farther, and show that all vector spaces (but not all modules) are free.
But in a constructive setting, such as Haskell, we need a fair bit to go back and forth, in particular we wind up need E to be finitely enumerable to go one way, and for it to have decidable equality to go in the other. The latter is fairly easy to see, because even going from E -> (E -> F)
requires that we can define and partially apply something like Kronecker's delta:
delta :: (Rig r, Eq a) => e -> e -> r delta i j | i == j = one | otherwise = zero
The Price of Power
The price we pay is that, given a Rig
, we can go from Vect r a
to Linear r a
but going back requires a
to be be finitely enumerable (or for our functional to satisfy other exotic side-conditions).
vectMap :: Rig r => Vect r a -> Linear r a vectMap (Vect as) = Map $ \k -> sum [ r * k a | (r, a) < - as ]
You can still probe Linear r a
for individual coefficients, or pass it a vector for polynomial evaluation very easily, but for instance determining a degree of a polynomial efficiently requires attaching more structure to your semiring, because the only value you can get out of Linear r a
is an r
.
Optimizing Linear Functionals
In both the Vect r
and Linear r
cases, excessive use of (>>=)
without somehow normalizing or tabulating your data will cause a lot of repeated work.
This is perhaps easiest to see from the fact that Vect r
never used the addition of r
, so it distributed everything into a kind of disjunctive normal form. Linear r
does the same thing.
If you look at the Kleisli arrows of Vect r
or Linear r
as linear mappings, then you can see that Kleisli composition is going to explode the number of terms.
So how can we collapse back down?
In the Kleisli (Vect r)
case we usually build up a map as we walk through the list then spit the list back out in order having added up like terms.
In the Map r
case, we can do better. My representable-tries
package provides a readily instantiable HasTrie
class, and the method:
memo :: HasTrie a => (a -> r) -> a -> r
which is responsible for providing a memoized version of the function from a -> r
in a purely functional way. This is obviously a linear map!
memoMap :: HasTrie a => Map r a a memoMap = Map memo
We can also flip memo around and memoize linear functionals.
memoLinear :: HasTrie a => a -> Linear r a
memoLinear = Linear . flip memo
Next time, (co)associative (co)algebras and the myriad means of multiplying (co)vectors!
]]>The question I hope to answer this time, is whether or not we turn any Haskell Comonad
into a comonad transformer.
Comonads from Comonads
In Monads from Comonads, we built the comonad-to-monad transformer
newtype Co w m a = Co (forall r. w (a -> r) -> r)
by sandwiching a Comonad
w in the middle of a trivial Codensity monad, then proceeded to show that at least in the case where our comonad was given rise to by an adjunction f -| g : Hask -> Hask
, we could reason about this as if we had
Co w ~ Co (f . g) ~ g . f
Now, Codensity
monads are a right Kan extension.
So, what happens if we try to do the same thing to a Left Kan extension?
Using
{-# LANGUAGE GADTs, FlexibleInstances #-} import Control.Comonad import Control.Comonad.Trans.Class
we can define
data L w a where L :: w (r -> a) -> r -> L w a
and a number of instances pop out for free, cribbed largely from the definition for Density.
instance Functor w => Functor (L w) where fmap f (L w r) = L (fmap (f .) w) r instance ComonadTrans L where lower (L w r) = fmap ($r) w instance Extend w => Extend (L w) where duplicate (L w s) = L (extend L w) s instance Comonad w => Comonad (L w) where extract (L w r) = extract w r
Reasoning as before about w
as if it were composed of an adjunction f -| g : Hask -> Hask
to build some intuition, we can see:
L w a ~ exists r. (w (r -> a), r) ~ exists r. (f (g (r -> a)), r) ~ exists r. (f (), g (r -> a), r) ~ exists r. (f (), f () -> r -> a, r) ~ exists r. (f () -> r -> a, f r) ~ exists r. (f r -> a, f r) ~ Density f a ~ Lan f f a ~ (f . Lan f Id) a ~ (f . g) a ~ w a
The latter few steps require identities established in my second post on Kan extensions.
With that we obtain the "remarkable" insight that L ~ IdentityT
, which I suppose is much more obvious when just looking at the type
data L w a where L :: w (r -> a) -> r -> L w a
and seeing the existentially quantified r
as a piece of the environment, being used to build an a
, since there is nothing else we can do with it, except pass it in to each function wrapped by w
! So at first blush, we've gained nothing.
The key observation is that in one case we would up with something isomorphic to the codensity monad of our right adjoint, while in the other case we would up with the density comonad of our left adjoint. The former is isomorphic to the monad given by our adjunction, while the latter is isomorphic to the comonad, which is, unfortunately, right where we started!
In The Future All Comonads are Comonad Transformers!
Of course, we don't have to just modify a trivial left Kan extension. Let's tweak the Density
comonad of another comonad!
data D f w a where D :: w (f r -> a) -> f r -> D f w a
Since both arguments will be comonads, and I want this to be a comonad transformer, I'm going to swap the roles of the arguments relative to the definition of CoT w m
. The reason is that D f w
is a Comonad, regardless of the properties of f, so long as w
is a Comonad
This is similar to how Density f
is a Comonad regardless of what f
is, as long as it has kind * -> *
.
The implementation of D
is identical to L
above, just as CoT
and Co
share implementations and ContT
and Cont
do.
instance Functor w => Functor (D f w) where fmap f (D w r) = D (fmap (f .) w) r instance Extend w => Extend (D f w) where duplicate (D w s) = D (extend D w) s instance Comonad w => Comonad (D f w) where extract (D w r) = extract w r instance ComonadTrans (D f) where lower (D w r) = fmap ($r) w
But in addition to being able to lower :: D f w a -> w a
, we can also lower to the other comonad!
fstD :: (Extend f, Comonad w) => D f w a -> f a fstD (D w r) = extend (extract w) r sndD :: Comonad w => D f w a -> w a sndD = lower
This means that if either comonad provides us with a piece of functionality we can exploit it.
Selling Products
In general Monad products always exist:
newtype Product m n a = Pair { runFst :: m a, runSnd :: n a } instance (Monad m, Monad n) => Monad (Product m n) where return a = Pair (return a) (return a) Pair ma na >>= f = Pair (ma >>= runFst . f) (na >>= runSnd . f)
and Comonad coproducts always exist:
newtype Coproduct f g a = Coproduct { getCoproduct :: Either (f a) (g a) } left :: f a -> Coproduct f g a left = Coproduct . Left right :: g a -> Coproduct f g a right = Coproduct . Right coproduct :: (f a -> b) -> (g a -> b) -> Coproduct f g a -> b coproduct f g = either f g . getCoproduct instance (Extend f, Extend g) => Extend (Coproduct f g) where extend f = Coproduct . coproduct (Left . extend (f . Coproduct . Left)) (Right . extend (f . Coproduct . Right)) instance (Comonad f, Comonad g) => Comonad (Coproduct f g) where extract = coproduct extract extract
but Christoph Lüth and Neil Ghani showed that monad coproducts don't always exist!
On the other hand what we built up above looks a lot like a comonad product!
Too see that, first we'll note some of the product-like things we can do:
fstD
and sndD
act a lot like fst
and snd
, projecting our parts of our product and it turns out we can "braid" our almost-products, interchanging the left and right hand side.
braid :: (Extend f, Comonad w) => D f w a -> D w f a braid (D w r) = D (extend (flip extract) r) w
(I use scary air-quotes around braid, because it doesn't let us braid them in a categorical sense, as we'll see.)
After braiding, one of our projections swaps places as we'd expect:
sndD (braid (D w r)) = -- by braid def sndD (D (extend (flip extract) r) w) = -- by sndD (and lower) def fmap ($w) (extend (flip extract) r) = -- extend fmap fusion extend (($w) . flip extract) r = -- @unpl extend (\t -> flip extract t w) r = -- flip . flip = id extend (extract w) r = -- by fstD def fstD (D w r)
But we stall when we try to show fstD . braid = sndD
.
Why is that?
A Product of an Imperfect Union
Last time, when we inspected CoT w m a
we demonstrated that on one hand given a suitable adjunction f -| g
, such that w = f . g
, Co w ~ Co (f . g) ~ (g . f)
, but on the other CoT w m a
was bigger than g . m . f
, and that if n -| m, then CoT w m a ~ g . m . n . f
.
Of course, these two results agree, if you view Co w
as CoT w Identity
, where Identity -| Identity
, since Identity ~ Identity . Identity
Therefore it should come as no surprise that given w = f . g
, for a suitable adjunction f -| g
, then D w j a
is bigger than f . j . g
. In fact if, j -| k
, then D w j ~ f . j . k . g
.
So what is happening is that we have only managed to "break one of our comonads in half", and D w j a
lets you do 'too much stuff' with the j
portion of the comonad. This keeps us from being symmetric.
Moreover it turns out to be a bit trickier to build one than to just hand in a w (f a)
or w a
and an f a
to build our product-like construction.
Even so, exploiting Density was enough to transform any comonad into a comonad-transformer and to enable us to access the properties of either the comonad we are transforming with, or the comonad that we are transforming.
]]>newtype CoT w m a = CoT { runCoT :: w (a -> m r) -> m r
and so demonstrated that there are fewer comonads than monads in Haskell, because while every Comonad gives rise to a Monad transformer, there are Monads that do not like IO
, ST s
, and STM
.
I want to elaborate a bit more on this topic.
In Monads from Comonads we observed that for non-transformer version of CoT
type Co w = CoT w Identity
under the assumption that w = f . g
for f -| g : Hask -> Hask
, then
Co w ~ Co (f . g) ~ g . f
This demonstrated that the Co w
is isomorphic to the monad we obtain by composing the adjunction that gave rise to our comonad the other way around.
But what about CoT
?
Sadly CoT
is a bit bigger.
We can see by first starting to apply the same treatment that we gave Co
.
CoT w m a ~ forall r. w (a -> m r) -> m r ~ forall r. f (g (a -> m r)) -> m r ~ forall r. f (f() -> a -> m r) -> m r ~ forall r. f (a -> f () -> m r) -> m r ~ forall r. (a -> f () -> m r, f ()) -> m r ~ forall r. (a -> f () -> m r) -> f () -> m r ~ forall r. (a -> g (m r)) -> g (m r) ~ Codensity (g . m) a
(I'm using .
to represent Compose
for readability.)
But we've seen before that Codensity g a
is in a sense bigger than g a
, since given an Adjunction f -| g
, Codensity g a ~ (g . f) a
, not g a
.
Moreover can compose adjunctions:
instance (Adjunction f g, Adjunction f' g') => Adjunction (Compose f' f) (Compose g g') where unit = Compose . leftAdjunct (leftAdjunct Compose) counit = rightAdjunct (rightAdjunct getCompose) . getCompose
So if n -| m
, then we can see that Codensity (g . m) a ~ g . m . n . f
, rather than the smaller g . m . f
, which we can obtain using AdjointT f g m
from Control.Monad.Trans.Adjoint in adjunctions.
So CoT
isn't the smallest monad transformer that would be given by an adjunction.
In fact, it is isomorphic to AdjointT f g (Codensity m) a
instead of AdjointT f g m a
.
Sadly, there doesn't appear to be a general purpose construction of the smaller transformer just given an unseparated w = f . g
.
Today, I'll show that we can go one step further and derive a monad transformer from any comonad!
A Comonad to Monad-Transformer Transformer
Given
newtype CoT w m a = CoT { runCoT :: forall r. w (a -> m r) -> m r }
we can easily embed the type of the previous Co
and create a smart constructor and deconstructor in the style of the MTL.
type Co w = CoT w Identity co :: Functor w => (forall r. w (a -> r) -> r) -> Co w a co f = CoT (Identity . f . fmap (fmap runIdentity)) runCo :: Functor w => Co w a -> w (a -> r) -> r runCo m = runIdentity . runCoT m . fmap (fmap Identity)
In fact, as with between Cont and ContT, none of the major instances even change!
instance Functor w => Functor (CoT w m) where fmap f (CoT w) = CoT (w . fmap (. f)) instance Extend w => Apply (CoT w m) where mf < .> ma = mf >>- \f -> fmap f ma instance Extend w => Bind (CoT w m) where CoT k >>- f = CoT (k . extend (\wa a -> runCoT (f a) wa)) instance Comonad w => Applicative (CoT w m) where pure a = CoT (`extract` a) mf < *> ma = mf >>= \f -> fmap f ma instance Comonad w => Monad (CoT w m) where return a = CoT (`extract` a) CoT k >>= f = CoT (k . extend (\wa a -> runCoT (f a) wa))
We can use CoT as a Monad transformer, or lift IO actions:
instance Comonad w => MonadTrans (CoT w) where lift m = CoT (extract . fmap (m >>=)) instance (Comonad w, MonadIO m) => MonadIO (CoT w m) where liftIO = lift . liftIO
(This monad transformer is available in my kan-extensions package as of 1.9.0 on hackage.)
And as before we can lift and lower CoKleisli arrows, although the results are monadic when lowered.
liftCoT0 :: Comonad w => (forall a. w a -> s) -> CoT w m s liftCoT0 f = CoT (extract < *> f) lowerCoT0 :: (Functor w, Monad m) => CoT w m s -> w a -> m s lowerCoT0 m = runCoT m . (return < $) lowerCo0 :: Functor w => Co w s -> w a -> s lowerCo0 m = runIdentity . runCoT m . (return < $) liftCoT1 :: (forall a. w a -> a) -> CoT w m () liftCoT1 f = CoT (`f` ()) lowerCoT1 :: (Functor w, Monad m) => CoT w m () -> w a -> m a lowerCoT1 m = runCoT m . fmap (const . return) lowerCo1 :: Functor w => Co w () -> w a -> a lowerCo1 m = runIdentity . runCoT m . fmap (const . return)
Since we could mean the MonadFoo instance derived from its comonadic equivalent or from the one we wrap as a monad transformer, we choose to default to the one from the monad, but we can still provide the lifted comonadic actions:
posW :: (ComonadStore s w, Monad m) => CoT w m s posW = liftCoT0 pos peekW :: (ComonadStore s w, Monad m) => s -> CoT w m () peekW s = liftCoT1 (peek s) peeksW :: (ComonadStore s w, Monad m) => (s -> s) -> CoT w m () peeksW f = liftCoT1 (peeks f) askW :: (ComonadEnv e w, Monad m) => CoT w m e askW = liftCoT0 (Env.ask) asksW :: (ComonadEnv e w, Monad m) => (e -> a) -> CoT w m a asksW f = liftCoT0 (Env.asks f) traceW :: (ComonadTraced e w, Monad m) => e -> CoT w m () traceW e = liftCoT1 (Traced.trace e)
and we just lift the monadic actions as usual:
instance (Comonad w, MonadReader e m) => MonadReader e (CoT w m) where ask = lift Reader.ask local f m = CoT (local f . runCoT m) instance (Comonad w, MonadState s m) => MonadState s (CoT w m) where get = lift get put = lift . put instance (Comonad w, MonadWriter e m) => MonadWriter e (CoT w m) where tell = lift . tell pass m = CoT (pass . runCoT m . fmap aug) where aug f (a,e) = liftM (\r -> (r,e)) (f a) listen = error "Control.Monad.Co.listen: TODO" instance (Comonad w, MonadError e m) => MonadError e (CoT w m) where throwError = lift . throwError catchError = error "Control.Monad.Co.catchError: TODO" instance (Comonad w, MonadCont m) => MonadCont (CoT w m) where callCC = error "Control.Monad.Co.callCC: TODO"
I welcome help working through the missing methods above.
This should go a long way towards showing the fact that there are strictly fewer comonads than monads in Haskell, and of course that there are no analogues to IO, STM and ST s in the world of Haskell comonads!
Every comonad gives you a monad-transformer, but not every monad is a monad transformer.
]]>Monad
from any old Comonad
you have lying around.
But first, we'll need to take a bit of a bit of a detour.
A Monad Sandwich
We'll need the definition of an adjunction on the category of Haskell types, which we can strip down and borrow from my adjunctions package.
class (Functor f, Representable u) => Adjunction f u | f -> u, u -> f where leftAdjunct :: (f a -> b) -> a -> u b rightAdjunct :: (a -> u b) -> f a -> b
Here we can define our Adjunction by defining leftAdjunct and rightAdjunct, such that they witness an isomorphism from (f a -> b)
to (a -> u b)
Every Adjunction F -| G : C -> D
, gives rise to a monad GF on D and a Comonad FG on C.
In addition to this, you can sandwich an additional monad M on C in between GF to give a monad GMF on D:
and you can sandwich a comonad W on D in between F and G to yield the comonad FWG on C:
A Contravariant Comonad Sandwich
As was first shown to me me by Derek Elkins, this construction works even when you C is not the category of Haskell types!
Consider the Contravariant functor Op r
:
newtype Op a b = Op { getOp :: b -> a } instance Contravariant (Op a) where contramap f g = Op (getOp g . f)
We can view Op r
as a functor from Hask^op -> Hask
or as one from Hask -> Hask^op
.
We can define a notion of a contravariant adjunction F -| G : Hask^op -> Hask
.
Data.Functor.Contravariant.Adjunction
class (Contravariant f, Corepresentable g) => Adjunction f g | f -> g, g -> f where leftAdjunct :: (b -> f a) -> a -> g b rightAdjunct :: (a -> g b) -> b -> f a
Where, now, leftAdjunct
and rightAdjunct
witness the isomorphism from (f a < - b)
to (a -> g b)
, which means once you flip the arrow around both seem to be going the same way. Ultimately any contravariant adjunction on Hask is comprised of two isomorphic functors, each self-adjoint.
This gives rise to one notion of a comonad-to-monad transformer!
Control.Monad.Trans.Contravariant.Adjoint
But we can we do better?
An End as the Means
First, some boilerplate.
{-# LANGUAGE Rank2Types, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, UndecidableInstances #-} import Data.Monoid import Control.Comonad import Control.Applicative import Control.Comonad.Store.Class import Control.Comonad.Env.Class as Env import Control.Comonad.Traced.Class as Traced import Control.Monad.Reader.Class import Control.Monad.Writer.Class import Control.Monad.State.Class import Data.Functor.Bind
Our new comonad to monad transformer is given by
newtype Co w a = Co { runCo :: forall r. w (a -> r) -> r }
What we've done is added a quantifier to prevent the use of the type r, as we did when describing Codensity
and Ran
, categorically we've taken some kind of end. This idea came to me after an observation was made by Russell O'Connor that Conts (Store s) a
was pretty close to a continuation passing style version of State s
.
Now, we can start spitting out instances for this type.
instance Functor w => Functor (Co w) where fmap f (Co w) = Co (w . fmap (. f)) instance Comonad w => Monad (Co w) where return a = Co (`extract` a) Co k >>= f = Co (k .extend (\wa a -> runCo (f a) wa)) instance Comonad w => Applicative (Co w) where mf < *> ma = mf >>= \f -> fmap f ma pure a = Co (`extract` a)
In my break-out of category-extras, I've split off the semigroupoid structure of Kleisli-, co-Kleisli-, and static- arrow composition as Bind
, Extend
and Apply
respectively, so we can make use of slightly less structure and get slightly less structure in turn:
instance Extend w => Bind (Co w) where Co k >>- f = Co (k .extend (\wa a -> runCo (f a) wa)) instance Extend w => Apply (Co w) where mf < .> ma = mf >>- \f -> fmap f ma
From comonad-transformers to the mtl
We can look at how this transforms some particular comonads.
The comonadic version of State
is Store
. Looking at Co (Store s) a
Co (Store s) a ~ forall r. ((s -> a -> r, s) -> r) ~ forall r. (s -> a -> r) -> s -> r ~ forall r. (a -> s -> r) -> s -> r ~ Codensity ((->)s) a ~ State s a
This gives rise to a leap of intuition that we'll motivate further below:
instance ComonadStore s m => MonadState s (Co m) where get = Co (\w -> extract w (pos w)) put s = Co (\w -> peek s w ())
Sadly this breaks down a little for Writer
and Reader
as the mtl
unfortunately has historically included a bunch of extra baggage in these classes. In particular, in reader, the notion of local
isn't always available, blocking some otherwise perfectly good MonadReader
instances, and I've chosen not to repeat this mistake in comonad-transformers
.
instance ComonadEnv e m => MonadReader e (Co m) where ask = Co (\w -> extract w (Env.ask w)) local = error "local"
Ideally, local belongs in a subclass of MonadReader
.
class Monad m => MonadReader e m | m -> e where ask :: m a -> e class MonadReader e m => MonadLocal e m | m -> e where local :: (e -> e) -> m a -> m a
Similarly there is a lot of baggage in the MonadWriter
. The Monoid
constraint isnt necessary for the class itself, just for most instances, and the listen
and pass
members should be a member of a more restricted subclass as well to admit some missing MonadWriter
instances, but we can at least provide the notion of tell that is critical to Writer
.
instance (Monoid e, ComonadTraced e m) => MonadWriter e (Co m) where tell m = Co (\w -> Traced.trace m w ()) listen = error "listen" pass = error "pass"
But given the split out
instance Monad m => MonadWriter e m | m -> e where tell :: e -> m () instance MonadWriter e m => MonadListen e m | m -> e listen :: m a -> m (a, w) pass :: m (a, w -> w) -> m a
We could provide this functionality more robustly. (There is a similar subset of Comonad
s that can provide listen and pass analogues.)
While I am now the maintainer of the mtl, I can't really justify making the above corrections to the class hierarchy at this time. They would theoretically break a lot of code. I would be curious to see how much code would break in practice though.
Combinators Please!
There is a recurring pattern in the above code, so we can also improve this construction by providing some automatic lifting combinators that take certain cokleisli arrows and give us monadic values
lift0 :: Comonad w => (forall a. w a -> s) -> Co w s lift0 f = Co (extract < *> f) lift1 :: (forall a. w a -> a) -> Co w () lift1 f = Co (`f` ())
along with their inverses
lower0 :: Functor w => Co w s -> w a -> s lower0 (Co f) w = f (id < $ w) lower1 :: Functor w => Co w () -> w a -> a lower1 (Co f) w = f (fmap const w)
(The proofs that these are inverses are quite hairy, and lean heavily on parametricity.)
Then in the above, the code simplifies to:
get = lift0 pos put s = lift1 (peek s) ask = lift0 Env.ask tell s = lift1 (tell s)
Co-Density?
Co and Codensity are closely related.
Given any Comonad W, it is given rise to by the composition FG for some adjunction F -| G : Hask -> C
.
Considering only the case where C = Hask
for now, we can find that
Co w a ~ forall r. (f (g (a -> r)) -> r).
Since f -| g
, we know that g
is Representable
by f ()
, as witnessed by:
tabulateAdjunction :: Adjunction f u => (f () -> b) -> u b tabulateAdjunction f = leftAdjunct f () indexAdjunction :: Adjunction f u => u b -> f a -> b indexAdjunction = rightAdjunct . const
therefore
Co w a ~ f (g (a -> r)) -> r ~ f (f () -> a -> r) -> r
Since f is a left adjoint functor, f a ~ (a, f ())
by Sjoerd Visscher's elegant little split
combinator:
split :: Adjunction f u => f a -> (a, f ()) split = rightAdjunct (flip leftAdjunct () . (,))
which has the simple inverse
unsplit :: Adjunction f g => a -> f () -> f a unsplit a = fmap (const a)
so we can apply that to our argument:
Co w a ~ forall r. f (f () -> a -> r) -> r ~ forall r. (f () -> a -> r, f ()) -> r
and curry to obtain
Co w a ~ forall r. (f () -> a -> r) -> f () -> r
and swap the arguments
Co w a ~ forall r. (a -> f () -> r) -> f () -> r
then we can tabulate the two subtypes of the form (f () -> r)
Co w a ~ forall r. (a -> g r) -> g r
and so we find that
Co w a ~ Codensity g a
Finally,
Codensity g a ~ Ran g g a
but we showed back in my second article on Kan extensions that given f -| g that
Ran g g a ~ g (f a)
So Co w ~ Co (f . g) ~ (g . f)
, the monad given rise to by composing our adjunction the other way!
Comonads from Monads?
Now, given all this you might ask
Is there is a similar construction that lets you build a comonad out of a monad?
Sadly, it seems the answer in Haskell is no.
Any adjunction from Hask -> Hask^op
would require two functions
class (Contravariant f, Contravariant g) => DualContravariantAdjunction f g where leftAdjunct :: (f a -> b) -> g b -> a rightAdjunct :: (g b -> a) -> f a -> b
where both functors are contravariant.
Surmounting the intuitionistic impossibility of this, then given any such adjunction, there would be a nice coend we could take, letting us sandwich any Monad
in the middle as we did above.
There does exist one such very boring Contravariant Functor.
newtype Absurd a = Absurd (Absurd a) absurdity :: Absurd a -> b absurdity (Absurd a) = absurdity a instance Contravariant Absurd where contramap f (Absurd as) = Absurd (contramap f as) instance DualContravariantAdjunction Absurd Absurd where leftAdjunct _ = absurdity rightAdjunct _ = absurdity
We can safely sandwich IO within this adjunction from Hask -> Hask^op
to obtain a comonad.
newtype Silly m a = Silly { runSilly :: Absurd (m (Absurd a)) } instance Monad m => Extend (Silly m) where extend f (Silly m) = absurdity m instance Monad m => Comonad (Silly m) where extract (Silly m) = absurdity m
But for any more interesting such type that actually lets us get at its contents, we would be able to derive a circuitous path to unsafePerformIO
!
Since unsafePerformIO
should not be constructible without knowing IO
specifics, no useful DualContravariantAdjunction
s should exist.