Создать список с использованием синтаксиса do? - PullRequest
3 голосов
/ 13 января 2012

Я относительно новичок в Хаскеле. Я создаю небольшой API / DSL поверх happstack-lite , который будет иметь интерфейс, более похожий на Sinatra, в основном для изучения. В качестве части этого я хочу создать массив с использованием синтаксиса do (в основном потому, что он будет красивее, чем msum [route, route, route]. Использование монады будет выглядеть примерно так:

someFunctionThatMakesStrings :: String

unwrapMyMonadAndGiveMeAList :: MyMonad _ -> [String]

makeAList :: [String]
makeAList = unwrapMyMonadAndGiveMeAList do
    someFunctionThatMakesStrings
    someFunctionThatMakesStrings
    someFunctionThatMakesStrings
    ...

Таким образом, makeAList возвращает список с 3 строками. Обратите внимание, что я хотел бы использовать внутри него функции, которые не знают о монаде (они просто возвращают строку).

Я могу сделать это с Writer, но каждая вызываемая функция должна знать о монаде Writer, и это также выглядит как перебор упаковки / распаковки)

Я пытался использовать саму монаду списка, но она явно предназначена для чего-то иного.

Итак, какие из моих предположений нужно изменить, и тогда как мне создать новую монаду построения списка с нуля? Как близко я могу получить?

1 Ответ

6 голосов
/ 13 января 2012

Writer определенно то, что вы хотите здесь;вы можете избежать "разоблачения" Writer извне, потратив немного больше накладных расходов на сами определения:

foo :: [String]
foo = execWriter $ do
  tell otherList1
  tell otherList2
  tell otherList3

otherList1 :: [String]
otherList1 = execWriter $ do
  ...

т.е. вы можете оставить использование Writer локальным для каждого определения, но выОберните каждый список, который вы хотите использовать в качестве «источника» в tell.Главное здесь - использовать execWriter, который отбрасывает элемент результата кортежа (он идентичен snd . runWriter).

Однако, если у вас много таких определений, я бы порекомендовал простоиспользование Writer напрямую и только execWriter в том месте, где вы хотите получить комбинированный результат;Вы можете сделать типы немного чище, определив синоним, как type Foo = Writer [String].

Я не уверен, каким преимуществом будет создание вашей собственной монады создания списка;в конечном итоге он будет идентичен Writer [String].

Монада списка действительно не имеет отношения к тому, что вы хотите здесь сделать.


Что касается определения вашей собственной монады написания спискаидет, это довольно просто:

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

Единственная сложная часть - (>>=), где мы должны взять только часть значения левого аргумента, передать его в аргумент правой руки, разобрать его,а затем объедините два списка внутри, обернув его обратно с результатом правой части.

...