Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are monads Writer m and Either e categorically dual?

I noticed there is a dual relation between Writer m and Either e monads. If m is a monoid, then

unit :: () -> m
join :: (m,m) -> m

can be used to form a monad:

return is composition: a -> ((),a) -> (m,a)
join is composition: (m,(m,a)) -> ((m,m),a) -> (m,a)

The dual of () is Void (empty type), the dual of product is coproduct. Every type e can be given "comonoid" structure:

unit :: Void -> e
join :: Either e e -> e

in the obvious way. Now,

return is composition: a -> Either Void a -> Either e a
join is composition: Either e (Either e a) -> Either (Either e e) a -> Either e a

and this is the Either e monad. The arrows follow exactly the same pattern.

Question: Is it possible to write a single generic code that will be able to perform both as Either e and as Writer m depending on the monoid given?

like image 412
sdcvvc Avatar asked Apr 22 '10 10:04

sdcvvc


People also ask

What are monads explain with example?

Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it. For example, you can create a type to wrap another one, in Haskell: data Wrapped a = Wrap a. To wrap stuff we define return :: a -> Wrapped a return x = Wrap x.

How do monads work?

A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.

What is the point of the monads?

monads are used to address the more general problem of computations (involving state, input/output, backtracking, ...) returning values: they do not solve any input/output-problems directly but rather provide an elegant and flexible abstraction of many solutions to related problems.

Are monads pure?

Monads are not considered pure or impure. They're totally unrelated concepts. Your title is kind of like asking how verbs are considered delicious. "Monad" refers to a particular pattern of composition that can be implemented on types with certain higher-kinded type constructors.


2 Answers

I would not say that these monads are categorically dual, but rather that they are both produced by the following construction: given a monoidal category (C, ⊗, 1) and an algebra A in C, consider the monad sending X to A ⊗ X. In the first case, C is Hask, ⊗ is ×, and an algebra is a monoid, and in the second case C is Hask, ⊗ is ∐ (Either), and an algebra is just a type (every type is an algebra w.r.t. ∐ in a unique way—this is what you refer to as a "comonoid", though that usually means something else, see below). As is customary, I am working in an imaginary world where ⊥ does not exist, so that × is actually a product and so on. It is probably possible to capture this common generalization using a suitable type class for monoidal categories (I am much too tired to understand what category-extras is trying to do in this regard at the moment) and thereby simultaneously define Writer and Either as monads (modulo newtypes, possibly).

As for the categorical dual of Writer m—well, it depends on what you want to consider as fixed, but the most likely candidate seems to be the comonad structure on (,) m without any conditions on m:

instance Comonad ((,) m) where
    coreturn (m, a) = a
    cojoin (m, a) = (m, (m, a))

(note that here is where we use that m is a comonoid, i.e., we have maps m → (), m → m × m).

like image 106
Reid Barton Avatar answered Oct 23 '22 13:10

Reid Barton


Here's the code:

{-# LANGUAGE FlexibleInstances, EmptyDataDecls, MultiParamTypeClasses,
FunctionalDependencies, GeneralizedNewtypeDeriving, UndecidableInstances #-}

import Control.Arrow (first, second, left, right)
import Data.Monoid

data Void
data Iso a b = Iso { from :: a -> b, to :: b -> a}

-- monoidal category (Hask, m, unit)
class MonoidalCategory m unit | m -> unit where
  iso1 :: Iso (m (m x y) z) (m x (m y z))
  iso2 :: Iso x (m x unit)
  iso3 :: Iso x (m unit x)

  map1 :: (a -> b) -> (m a c -> m b c)
  map2 :: (a -> b) -> (m c a -> m c b)

instance MonoidalCategory (,) () where
  iso1 = Iso (\((x,y),z) -> (x,(y,z))) (\(x,(y,z)) -> ((x,y),z))
  iso2 = Iso (\x -> (x,())) (\(x,()) -> x)
  iso3 = Iso (\x -> ((),x)) (\((),x) -> x)
  map1 = first
  map2 = second

instance MonoidalCategory Either Void where
  iso1 = Iso f g
         where f (Left (Left x)) = Left x
               f (Left (Right x)) = Right (Left x)
               f (Right x) = Right (Right x)

               g (Left x) = Left (Left x)
               g (Right (Left x)) = Left (Right x)
               g (Right (Right x)) = Right x
  iso2 = Iso Left (\(Left x) -> x)
  iso3 = Iso Right (\(Right x) -> x)
  map1 = left
  map2 = right

-- monoid in monoidal category (Hask, c, u)
class MonoidM m c u | m -> c u where
  mult :: c m m -> m
  unit :: u -> m

-- object of monoidal category (Hask, Either, Void)
newtype Eith a = Eith { getEith :: a } deriving (Show)

-- object of monoidal category (Hask, (,), ())
newtype Monoid m => Mult m = Mult { getMult :: m } deriving (Monoid, Show)

instance MonoidM (Eith a) Either Void where
  mult (Left x) = x
  mult (Right x) = x
  unit _ = undefined

instance Monoid m => MonoidM (Mult m) (,) () where
  mult = uncurry mappend
  unit = const mempty

instance (MonoidalCategory c u, MonoidM m c u) => Monad (c m) where
  return = map1 unit . from iso3
  x >>= f = (map1 mult . to iso1) (map2 f x)

Usage:

a = (Mult "hello", 5) >>= (\x -> (Mult " world", x+1))
                                 -- (Mult {getMult = "hello world"}, 6)
inv 0 = Left (Eith "error")
inv x = Right (1/x)
b = Right 5 >>= inv              -- Right 0.2
c = Right 0 >>= inv              -- Left (Eith {getEith="error"})
d = Left (Eith "a") >>= inv      -- Left (Eith {getEith="a"})
like image 29
sdcvvc Avatar answered Oct 23 '22 11:10

sdcvvc