Почему в 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 ответ
Ваше первое наблюдение верно, но это различие между тем, какие переходы создаются, важно.
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
.
(,)
не является товаром в категорическом смысле из-за лишнего дна. Если бы в Haskell не было seq
, можно было бы просто определить модуль с типом data Pair a b = Pair a b
, который не раскрывает конструктор, но предоставляет функции для создания этих пар и получения первого и второго значений. Модульность заставит его вести себя с точки зрения наблюдения как реальный продукт (IMO, что достаточно хорошо). Или используйте церковную кодировку. Однако у нас есть seq
, а у строгих языков не может быть продуктов, поэтому нам не повезло, когда дело касается математической точности.
Похожие вопросы
Связанные вопросы
Новые вопросы
haskell
Haskell — это чисто функциональный язык программирования со строгой статической типизацией, отложенными вычислениями, обширной поддержкой параллелизма и параллелизма, а также уникальными возможностями абстракции.