I'm relatively new to Haskell. I'm creating a small api/dsl on top of happstack-lite, which will have an interface more similar to Sinatra, mostly to learn. As a part of this, I want to construct an array using do syntax (basically because it would be prettier than the msum [route, route, route] thing. The usage of the monad would look something like this:
someFunctionThatMakesStrings :: String
unwrapMyMonadAndGiveMeAList :: MyMonad _ -> [String]
makeAList :: [String]
makeAList = unwrapMyMonadAndGiveMeAList do
someFunctionThatMakesStrings
someFunctionThatMakesStrings
someFunctionThatMakesStrings
...
So makeAList would return a list with 3 strings. Notice that I would like to use functions inside it that aren't aware of the monad (they just return a string).
I can do this with Writer, but each function called has to be aware of the Writer monad, and it also seems like overkill (I don't need the return type tuple, and the way I got it to work involved lots of wrapping/unwrapping)
I tried using the list monad itself, but it clearly is intended for something different than this.
So which of my assumptions need to change, and then how would I make a new list-construction monad from scratch? How close can I get?
Writer is definitely what you want here; you can avoid "exposing" Writer to the outside at the cost of a little more overhead in the definitions themselves:
foo :: [String]
foo = execWriter $ do
tell otherList1
tell otherList2
tell otherList3
otherList1 :: [String]
otherList1 = execWriter $ do
...
i.e., you can keep the use of Writer local to each definition, but you have to wrap each list you want to use as a "source" in tell. The key thing here is to use execWriter, which discards the result element of the tuple (it's identical to snd . runWriter).
However, if you have a lot of definitions like this, I would recommend simply using Writer directly, and only applying execWriter in the place where you want the combined result; you can make the types a bit cleaner by defining a synonym like type Foo = Writer [String].
I'm not sure what advantage constructing your own list-creation monad would be; it would end up being identical to Writer [String].
The list monad is indeed irrelevant to what you want to do here.
As far as defining your own list-writing monad goes, it's pretty simple:
data ListWriter a = ListWriter a [String]
runListWriter :: ListWriter a -> (a, [String])
runListWriter (ListWriter a xs) = (a, xs)
execListWriter :: ListWriter a -> [String]
execListWriter = snd . runListWriter
instance Monad ListWriter where
return a = ListWriter a []
ListWriter a xs >>= f = ListWriter b (xs ++ ys)
where ListWriter b ys = f a
The only tricky part is (>>=), where we have to take only the value part of the left argument, feed it into the right hand argument, take it apart, and then combine the two lists inside, wrapping it back up with the result of the right hand side.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With