Подтвердить что ты не робот

В чем смысл ленивой/строгой версии Writer?

Почему в 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 не оцениваются, а сохраняются как thunks. В строгой версии они оцениваются, чтобы проверить, соответствуют ли они шаблонам кортежа, результат подается на <>. В обоих случаях параметр >>= не оценивается до тех пор, пока что-то фактически не потребует результирующего значения. Таким образом, я понимаю, что и ленивые, и строгие версии делают то же самое, за исключением того, что у них есть thunk в другом месте внутри определения >>=: lazy производит runWriterT thunks, strict производит <> thunks.

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

  • Правильно ли это, или я неправильно понимаю оценку здесь?
  • Можно ли выполнить строгую <> без написания моей собственной оболочки и экземпляра?
4b9b3361

Ответ 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.