3

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?

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

7 Comments

This is great! There is no advantage to constructing my own, I suppose, but since I haven't been able to make one at all, I'd like to at least be able to do it to learn.
When I tried to make one from scratch, I kept getting errors saying that a wasn't equal to b when I tried to write the definition of >>= to ++ the list from the first part to the list from the second. So I really would be interested to see how it would look
@SeanClarkHess: I've added an example implementation of a Writer for lists.
Is it possible to remove the "a" (fst) from the ListWriter? It seems like I don't need it, since the functions don't actually need to pass values to each other -- they just need to create independent items for the list. In other words, I'd never use <- in my do block for this monad. This doesn't work at all though: gist.github.com/1608750. I get: "Kind mis-match. The first argument of Monad' should have kind * -> ', but ListWriter' has kind '"
@SeanClarkHess: Monads have to have a type parameter; that's pretty much the whole point :) Doing away with it gives you a Monoid; of course, that doesn't help you, because you basically want to reap the benefits of do-notation for a monoid. In fact, what Writer does is transform a monoid into a monad. You could keep the type parameter but not include an actual a value in ListWriter, passing undefined to f in (>>=), but it'd be a horrible hack, and not save you any typing.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.