Почему в Haskell есть две разные монады типа Writer? Интуитивно для меня чтение «монады строгого писателя» означает, что <> является строгим, так что в журнале нет накопления преобразователей. Однако, глядя на исходный код, оказывается, что это не так:

-- Lazy Writer
instance (Monoid w, Monad m) => Monad (WriterT w m) where
-- ...
m >>= k  = WriterT $ do
    ~(a, w)  <- runWriterT m
    ~(b, w') <- runWriterT (k a)
    return (b, w <> w')

В строгой версии шаблоны не являются неопровержимыми, т.е. отсутствуют ~. Итак, что происходит выше, так это то, что m и k a не оцениваются, а сохраняются как преобразователи. В строгой версии они оцениваются, чтобы проверить, соответствуют ли они шаблонам кортежа, результат передается в <>. В обоих случаях >>= не оценивается, пока что-то действительно не потребует результирующего значения. Насколько я понимаю, и ленивая, и строгая версии делают одно и то же, за исключением того, что у них преобразователь находится в другом месте внутри определения >>=: lazy создает преобразователи runWriterT, строгий производит <> дубинки.

Это оставляет мне два вопроса:

  1. Верно ли приведенное выше, или я неправильно понимаю оценку здесь?
  2. Могу ли я выполнить строгий <> без написания собственной оболочки и экземпляра?
15
David 1 Фев 2013 в 15:00
3
На вопрос 1 ответ находится на stackoverflow.com/questions/13186512/…
 – 
Joachim Breitner
1 Фев 2013 в 16:27

1 ответ

Лучший ответ

Ваше первое наблюдение верно, но это различие между тем, какие переходы создаются, важно.

Lazy и Strict не о строгости типа журнала, а о строгости в паре.

Они возникают из-за того, что у пары в Haskell есть два возможных способа ее обновить.

bimap f g (a,b) = (f a, g b)

Или

bimap f g ~(a,b) = (f a, g b)

Последний такой же, как

bimap f g p = (f (fst p), g (snd p))

Разница между этими двумя параметрами заключается в том, что при передаче аргументов в bimap в первом случае пара принудительно принудительно устанавливается немедленно.

В последнем случае пара не подвергается немедленному принудительному использованию, но вместо этого я передаю вам обратно (,), заполненное двумя нестрогими вычислениями.

Это значит, что

fmap f _|_ = _|_ 

В первом случае но

fmap f _|_ = (_|_, _|_)

Во втором случае более ленивой пары!

Оба верны при разных интерпретациях концепции пары. Тебя навязывают, притворяясь, что пара - это пара в категорическом смысле, что у нее нет никаких интересных _|_ как таковых. С другой стороны, интерпретация предметной области как нестрогая. насколько это возможно, чтобы вы могли завершить как можно больше программ, переводит вас на версию Lazy.

(,) e - вполне допустимый Writer, так что это характеризует проблему.

Причина такого различия заключается в том, что оно имеет значение для завершения многих экзотических программ, которые проходят через монаду фиксированную точку. Вы можете отвечать на вопросы о некоторых циклических программах с участием государства или писателя, если они ленивы.

Обратите внимание, что ни в одном из случаев аргумент log не является строгим. Приняв на себя строгость, вы теряете надлежащую ассоциативность и технически перестаете быть Monad. знак равно

Поскольку это не монада, мы не поставляем ее в mtl!

Таким образом, мы можем ответить на ваш второй вопрос:

Однако есть некоторые обходные пути. Вы можете построить поддельный Writer поверх State. По сути, притворитесь, что вам не преподносят аргумент от государства. и просто отобразите состояние, как если бы вы tell. Теперь вы можете делать это строго, потому что это не происходит за вашей спиной как часть каждого связывания. State просто проходит через неизмененное состояние между действиями.

shout :: Monoid s => s -> Strict.StateT s m ()
shout s' = do
   s <- get
   put $! s <> s'

Это, однако, означает, что вы заставляете всю вашу монаду State получать выходные данные и не можете лениво создавать части Monoid, но вы получаете что-то, что операционно ближе к тому, что ожидал бы строгий программист. Интересно, что это работает даже с Semigroup, потому что единственное использование mempty эффективно в начале, когда вы runState.

17
Edward Kmett 1 Фев 2013 в 18:51
1
Наблюдение за ленивыми парами: обычный (,) не является товаром в категорическом смысле из-за лишнего дна. Если бы в Haskell не было seq, можно было бы просто определить модуль с типом data Pair a b = Pair a b, который не раскрывает конструктор, но предоставляет функции для создания этих пар и получения первого и второго значений. Модульность заставит его вести себя с точки зрения наблюдения как реальный продукт (IMO, что достаточно хорошо). Или используйте церковную кодировку. Однако у нас есть seq, а у строгих языков не может быть продуктов, поэтому нам не повезло, когда дело касается математической точности.
 – 
Philip JF
2 Фев 2013 в 01:02
Можете ли вы привести пример, где может быть полезна версия Strict? Я не могу понять, почему он может быть предпочтительнее Ленивого.
 – 
Tom Ellis
10 Фев 2013 в 15:18