Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell -- Understanding the writer type declaration

I am learning about monads in the book 'Learn You a Haskell for Great Good!' by Miran Lipovaca. I am reading about how the Control.Monad.Writer module exports the Writer w a type along with its Monad instance and some useful functions for dealing with values of this type.

It gives a Writer type declaration example.

For the following code:

-- Writer w a corresponding to (a, w)  <--- the order is reversed
newtype Writer w a = Writer { runWriter :: (a, w) } -- <--- (a, w)

Here, why is the pair type order reversed?

Can I make the declaration of Writer type like this?

-- Writer w a corresponding to (w, a) <--- same order with Writer w a
newtype Writer w a = Writer { runWriter :: (w, a) } -- <--- (w, a)

If I took the second choice of Writer type declaration, Is there anything bad influence in writing Haskell?

In order to find out Writer Monad, I have tried reading Haskell Writer Monad Source Code (From https://hackage.haskell.org/package/transformers-0.6.0.4/docs/src/Control.Monad.Trans.Writer.Strict.html#WriterT)

newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

The pair type order is also reversed. Why can't define it like this?

newtype WriterT w m a = WriterT { runWriterT :: m (w, a) }

Is there any reason for this?

Thanks for your help.

like image 728
qwq_890 Avatar asked Aug 04 '26 22:08

qwq_890


1 Answers

As evidenced by the swap function, a tuple (a, b) is isomorphic to (b, a), so yes, you can trivially change the order of the elements of the tuple. It makes no difference in capability.

There are often historical reasons why certain APIs look as they do. I'm not aware of the exact history behind the Writer monad, but in general, if you imagine that you're coming from an imperative/procedural/object-oriented background, an API equivalent to something Writer-based would be a procedure that returns a value but also changes some mutable state (a global variable, or a mutable input argument).

In C#, for example, it might look like this:

public int Foo(ICollection<string> log)

where the return value is an int, while the writable resource is a collection of strings.

Once you decide to get rid of mutation, you have to instead return a new version of the log, but since the method already returns an int, you'll have to return a tuple. Should you make the 'original' return type the first or the second element in the tuple?

It seems intuitive to make the 'original' return type the first element. Thus, in the above example, you may instead have an API like this (now in Haskell syntax):

foo :: [String] -> (Int, [String])

or, more generally,

(a, w)

When it comes to types that have Functor and Monad instances, however, Haskell requires that the type variable that is 'engaged' in fmap, >>=, etcetera is the rightmost type variable.

That's the reason that WriterT w m a is defined with the a farthest to the right, instead of, say, WriterT w a m or WriterT a w m.

Given that the a must be farthest to the right, a follow-up question might be: Why is it WriterT w m a instead of WriterT m w a?

That's because by putting the 'monad' type variable m to the right of the Writer type variable w, you can partially apply the Identity monad to WriterT to define Writer:

type Writer w = WriterT w Identity

It may be a good exercise to see if you can swap (a, w) with (w, a) and see if you can still implement all the instances.

like image 168
Mark Seemann Avatar answered Aug 08 '26 10:08

Mark Seemann



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!