newtype Writer w a = Writer { runWriter :: (a,w) }
instance (Monoid w) => Monad (Writer w) where
return a = Writer (a,mempty)
(Writer (a,w)) >>= f = let (a',w') = runWriter $ f a in Writer (a',w `mappend` w')
I come from the ML/Isabelle world, where we have one name for a datatype, what is Writer here. This could be written as
datatype ('a, 'w) writerMonad = Writer ('a * 'w)
and in a function definition, I could write a pattern as
"myFunction (Writer (a,w)) = ..."
But what is runWriter in that scenario? What does it do?
As a side note, I really donât like naming (newtype) field selectors runXXX:
Itâs confusing if youâre not used to it (although thatâs quite a subjective argument)
The pattern is coincidental; runXXX refers to running/unpacking an effect (I think) and by sheer coincidence thatâs the only field of Writer m. Better use a common field name such as unXXX (commn for newtypes) or something like writerAction and use that to define an exported runWriter function
Case in point: If you export runXXX, you implicitly export âthe innardsâ of your newtype. Depending on your definition, this means you also export the ability to coerce away the newtype constructor. Not so if you have a separate function runWriter with a field selector writerAction that is not exported. I was wrong about this, see below. But the same applies to record update syntax, which doesnât make sense considering the name runWriter.
Although in practice, I suppose this hardly worth splitting hairs overâŚ
I think the convention is perfectly fine. Itâs confusing when youâre not quite used to the idea record accessors are âjust functionsâ, and thinking of them as such, but this idea is essential to writing idiomatic Haskell. Just look at GADT syntax, which highlights this even more:
data Writer w a where
runWriter :: Writer w a -> (a, w)
Using this notation, we are conceptually no longer saying "Writer is a data type, parametrized over two types w and a, that has one field called runWriter, whose type is (a, w)"; we are saying "there exists a type Writer, parametrized over two types w and a, for which a function runWriter exists, whose type is Writer w a -> (a, w)". Itâs literally the same thing, the difference is purely in the surface syntax, but the viewpoint is a different one.
This idea of data types and functions being the same thing, or at least isomorphic, permeates Haskell, and many common idioms can only be fully understood if you have developed an intuition for this. Is a list a data structure or a control construct? Yes! Is Functor an abstraction of data structures or control flow? Also yes! Whatâs up with the Data vs. Control namespaces? Nothing but historical cruft, really! Or look at DLists, which literally represent lists as functions. Or defunctionalization as a programming technique.
So while writing runWriter as a âfieldâ may be confusing to a beginner, that confusion cannot be avoided entirely, and IMO that makes it a weak argument against using it here.
Case in point: If you export runXXX, you implicitly export âthe innardsâ of your newtype. Depending on your definition, this means you also export the ability to coerce away the newtype constructor.
I donât think this is true: you can export runWriter without exporting the constructor.
Wha? where is that syntax valid? Itâs not a GADT declaration that I recognise. Neither does GHC.
Comes from lambda calculus, and is therefore in LISP, because neither of those have any concept of data constructors or pattern matching.
Not my thinking in Haskell it doesnât.
This fails to explain where a value of type Writer w a âcomes fromâ. And thatâs why itâs so puzzling for newbies. Much easier to think that applying a data constructor builds a value with components; pattern matching takes apart those components. So GADT syntax â even when itâs legit GADT syntax â fails to show that.
(Yes thereâs a convoluted story such that we can in lambda calculus build something isomorphic to a data constructor that is higher-order and takes an extra parameter thatâs an extractor function. Isomorphic does not mean " literally the same thing, the difference is purely in the surface syntax,". Youâre telling fairy-stories.)
Right, good point. Nevertheless, I think exporting a field selector that by coincidence is the implementation of the eliminator of an effect and hence naming it as such seems smart on first blush, but is just confusing the more you think about it. For example
f :: Writer w a -> Writer [Int] ()
f w = w { runWriter = ((), []) }
is not something that makes sense for the user to writeâŚ
Only in crazy Haskell (98) with a field name restricted to a single data type would that idea occur to anyone. Leaving aside the status of data constructors, if Haskell had records like nearly every other modern programming language [**], youâd have to think of record accessors as overloaded for every data type in which a field with that name might appear â and potentially at a different resulting field type.
[**] And I donât think this has anything to do with functional vs procedural/OO; or with strict vs lazy.
Ah, apologies: all this sidetracking into GADT syntax and components has missed your question. Writer is a newtype. (Not a data, as others and including me have started talking about.)
newtypes must have a single âcomponentâ; and because of that, the data constructor Writer isnât really a constructor/wrapper; and the field name runWriter isnât really an âextractorâ.
What we really have is a pair type (a,w) â yes same as ('a * 'w) â with a (not-very) magic cloak that stops it getting taken as a pair, and enables it to enter into the (very definitely) magic world of Monads.
runWriter slips off the cloak, once we step into the magic world.