Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a list using do syntax?

Tags:

haskell

monads

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?

like image 907
Sean Clark Hess Avatar asked May 12 '26 01:05

Sean Clark Hess


1 Answers

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.

like image 87
ehird Avatar answered May 14 '26 22:05

ehird



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!