> {-# LANGUAGE MultiParamTypeClasses, ExplicitForAll, RankNTypes, FlexibleInstances, FlexibleContexts, TypeSynonymInstances #-} > import Data.Monoid > import Data.Functor.Identity > import Control.Monad.Writer

x

y

x `mappend` y

b

a

> class Free a b where > leaf :: a -> b

Monoid

a

> data FreeMonoid a = FreeMonoid (forall b. (Monoid b, Free a b) => b)

> instance Monoid (FreeMonoid a) where > mempty = FreeMonoid mempty > FreeMonoid a `mappend` FreeMonoid b = FreeMonoid (a `mappend` b)

e1

e2

> e1, e2 :: FreeMonoid Char > e1 = FreeMonoid (leaf 'a' `mappend` (leaf 'b' `mappend` leaf 'c')) > e2 = FreeMonoid ((leaf 'a' `mappend` leaf 'b') `mappend` leaf 'c')

FreeMonoid

mempty

FreeMonoid a

Leaf x1 `mappend` (Leaf x2 `mappend` (... mempty))

[]

Monoid

> instance Free a [a] where > leaf x = [x]

> iso1 :: FreeMonoid a -> [a] > iso1 (FreeMonoid x) = x > iso1' :: [a] -> FreeMonoid a > iso1' [] = FreeMonoid mempty > iso1' (a : as) = let FreeMonoid r = iso1' as > in FreeMonoid (leaf a `mappend` r)

Monoid

mappend

mempty

> test1 :: [(Int, Int, Int)] > test1 = do > a <- return 3 `mappend` return 4 > b <- return 4 `mappend` return 5 > c <- return 5 `mappend` return 6 > if a*a+b*b==c*c then return (a, b, c) else mempty

> class Monoid m => MSet m s where > act :: m -> s -> s > data FreeMSet w a = FreeMSet (forall b. (MSet w b, Free a b) => b) > instance Monoid w => MSet w (FreeMSet w a) where > m `act` FreeMSet b = FreeMSet (m `act` b)

FreeMSet

FreeMSet

m `act` (leaf x)

> instance Monoid w => MSet w (Writer w a) where > act w1 m = let (a, w2) = runWriter m in WriterT (Identity (a, w1 `mappend` w2)) > instance Monoid w => Free a (Writer w a) where > leaf x = return x

FreeMSet

> iso2 :: Monoid w => FreeMSet w a -> Writer w a > iso2 (FreeMSet x) = x > iso2' :: Writer w a -> FreeMSet w a > iso2' m = let (a, w) = runWriter m in FreeMSet (act w (leaf a))

> test2 :: Writer String Int > test2 = do > act "foo" (return ()) > a <- return 2 > act "bar" (return ()) > b <- return (10*a) > return b

> data FreeMMonoid w a = FreeMMonoid (forall b. (Monoid b, MSet w b, Free a b) => b) > instance Monoid w => Monoid (FreeMMonoid w a) where > mempty = FreeMMonoid mempty > FreeMMonoid a `mappend` FreeMMonoid b = FreeMMonoid (a `mappend` b) > instance Monoid w => MSet w (FreeMMonoid w a) where > m `act` FreeMMonoid b = FreeMMonoid (m `act` b)





mappend

act

m `act` (x `mappend` y)

act

mappend

(m `act` x) `mappend` (m `act` y)

m `act` (x `mappend` y) == (m `act` x) `mappend` (m `act` y)

FreeMMonoid

act

act m1 (leaf x1) `mappend` (act m2 (leaf x2) `mappend` (... mempty)

WriterT

[]

> instance Monoid w => Monoid (WriterT w [] a) where > mempty = WriterT [] > WriterT xs `mappend` WriterT ys = WriterT (xs ++ ys) > instance Monoid w => MSet w (WriterT w [] a) where > m `act` WriterT xs = WriterT $ map (\(x, w) -> (x, m `mappend` w)) xs > instance Monoid w => Free a (WriterT w [] a) where > leaf x = return x

> iso3 :: Monoid w => FreeMMonoid w a -> WriterT w [] a > iso3 (FreeMMonoid x) = x > iso3' :: Monoid w => WriterT w [] a -> FreeMMonoid w a > iso3' m = let xws = runWriterT m in FreeMMonoid $ > foldr mappend mempty $ map (\(x, w) -> act w (leaf x)) xws

WriterT (Product Float) []

> coin :: (Monoid a, MSet (Product Float) a, Free Bool a) => a > coin = act (Product 0.5 :: Product Float) (leaf False) > `mappend` > act (Product 0.5 :: Product Float) (leaf True)

True

> test3 :: WriterT (Product Float) [] (Bool, Bool) > test3 = do > coin1 <- coin > coin2 <- coin > if coin1>coin2 then mempty else return (coin1, coin2)

MSet

In an earlier post I talked about how monads arise from free algebras. Let me recap a bit.In Part 1 I described algebras. They're sets with operations on them satisfying some laws. We can build new elements of an algebra from old ones by using its operations. Eg. ifandare in an algebra thenmust be in it too. Starting with a bunch of symbols, thought of as leaves, we can consider the set of all expressions trees we can build from them. If we consider pairs of trees to be equivalent if the laws say the corresponding expressions are equal, then the set of trees itself forms an algebra known as a free algebra (for the given theory).Let's start with some code. This type class says that the typehas leaves of typeNow we can make the type of all trees built fromoperations and including all leaves of typeAnd we have:Unfortunately elements likeandtwo ought to be equal but Haskell doesn't know this:Instead we can manually construct a type that does respect equality in monoids. Elements ofare binary trees with a `mappend` at each node. Associativity means that we can always replace a tree with an equivalent one where the left branch is a leaf. We can also use the laws to eliminate any occurrence of. So every element ofis equivalent to one of the form:In other words, free monoids are lists. We can make this explicit. The standard prelude already makesan instance ofso we just need:Here's the isomorphism (modulo tree equivalence):As I talked about in that earlier article , free algebras give monads and the trees representing expressions in the algebra can be thought of as abstract syntax trees for domain specific languages. In this case it's the usual list monad. So thetype class gives us a language for talking about non-determinism. The operationgives us a way to "fork" a process andgives as a way to "kill a thread". Here's an example using non-determinism to search for some Pythagorean triples:We can do exactly the same for-sets.Again we have the problem thatdoesn't automatically make equivalent elements equal. But it's not hard to see that every element ofis equivalent to one of the form:So the free-set on the set of variablesis simply the set of pairs. This is the basis of Haskell's writer monad:Here's the isomorphism (again treating equivalent elements ofas equal):And now the-set operation gives us an interface to an effect. This time the side effect of accumulating in a monoid:And now we can finally combine the two effects of non-determinism and accumulation. We make the free algebra that is both a monoid and an-set:Again we have the problem that equivalent elements aren't recognised as equal so we have to manually find a suitable type. For this we need to use the compatibility notion I introduced in Part 1. We can take 2 variablesandand write them in a 1 by 2 array:Applyhorizontally andvertically to get:Now applyvertically and thenhorizontally to get:The law we want is:Given an arbitrary tree inwe can use this law to "push" all occurrences ofinwards. Ultimately every element can be written uniquely in the form:We can then use the same argument as above to show that we end up with a list of pairs of elements of. This is exactly what we get if we apply themonad transformer to. Here are the relevant instances:Here's the isomorphism though we won't use it:The monadis in fact the probability monad . Here's an example of its use:Compute unnormalised conditional probability distribution on a pair of coin tosses given that first coin can't beunless second one is:(Compare with Eric Kidd's article that also 'refactors' probability theory.)Something miraculous just happened though it may have been lost in the details. We combined the list monad and the writer monad to get a new monad. We did it without using monad transformers and without specifying an order for the two monads. It just so happens in this case that the result was the same as using a monad transformer.We can try other products of theories. It's tricky to deal with a theory combined with itself because repeating a type class in a context doesn't do anything. We need to make another type class that looks exactly likebut with different names. The result is that the product of the theory of-sets and the theory of-sets is the theory of-sets. This agrees with what we'd get from using monad transformers. It also agrees with intuition.-sets correspond to the effect of accumulating data in a monoid. The product theory corresponds to using two accumulators simultaneously.(This makes me think type classes should take as arguments the name of the operations within them. That way a type can be an instance of the same type class in multiple ways. Compare with Agda modules.)This example illustrates why we can't expect a programming language to use the above method to combine theories. If an algebra has two multiplication operators with identities on it, and the two operators are compatible, then something surprising happens. The multiplications turn out to be the same operation. What's more, the operation is commutative. So the product of the theory of monoids with itself is the theory of commutative monoids. A free commutative monoid is a multiset. Multisets require a very different implementation to lists and I doubt any automatic algebra combiner in the near future could discover one. (The Eckmann-Hilton argument also appears here .)To form the product of two theories we add in extra laws to ensure commutativity. If we don't add in such laws we get the sum of two theories. For the example theories I used here these theories can lead to quite complex types. For example the sum of the theory of-sets and-sets is, I think, the theory of-sets whereis the "free product" of monoids. I this is a bit of a messy object from the perspective of types. Other effects, however, may behave nicely with respect to. I haven't yet investigated.If you don't mind computing the relevant types by hand there are perfectly good alternative to monad transformers for combining effects. But it seems very difficult to automatically combine theories. In fact, I expect finding canonical forms for the elements of free algebras for a product theory isn't even computable. So this approach isn't going to replace monad transformers any time soon.Make a multiplication table showing the result of forming the product of algebras for lots of useful effects.

Labels: category theory, haskell, lawvere theories