Почему (>>) не определяется как (*>)?
В настоящее время GHC реализует >>
как
(>>) :: m a -> m b -> m b
m >> k = m >>= \_ -> k
Почему бы не сделать следующее:
(>>) :: m a -> m b -> m b
m >> k = m *> k
Прямо сейчас, я думаю, что >>=
делает что-то *>
не делает.
Но все работает грамматически (как, например, по типу), поэтому очень трудно понять, почему это не сработает.
Возможно, экземпляр monad выполняет некоторые вычисления, которые нет в аппликативном экземпляре, но я думаю, что это нарушит семантику этого типа.
Обновление. Я получаю только один ответ SO, как принято, но ответ dfeuer очень проницателен (особенно для людей, которые, как и я, относительно неопытны в Haskell).
Ответы
Ответ 1
Согласно комментарию в исходный код, он предотвращает случайное создание рекурсивной привязки, если они переопределяют *>
как >>
в их экземпляре Applicative
.
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \_ -> k -- See Note [Recursive bindings for Applicative/Monad]
В записке говорится:
Примечание. Рекурсивные привязки для аппликативного/монада
В оригинальном заявлении Аппликативного/Монада указано, что после реализации назначенная реализация ( → ) станет
(>>) :: forall a b. m a -> m b -> m b
(>>) = (*>)
по умолчанию. Возможно, вы склонны изменить это, чтобы отразить заявленное предложение, но вы действительно не должны! Зачем? Потому что люди склонны определите такие случаи наоборот: в частности, это совершенно законно определить экземпляр аппликативного (*>)
в термины (>>)
, что приведет к бесконечному циклу для значения по умолчанию реализация Monad! И люди делают это в дикой природе.
Это превратилось в неприятную ошибку, которая была сложной для отслеживания, и, скорее, чем устранить его повсюду вверх по течению, проще просто сохранить оригинал по умолчанию.
Ответ 2
Ответ 4castle прав, конечно, но есть еще одна вещь, которую следует учитывать. Не каждый экземпляр Monad
поддерживает экземпляр Applicative
более эффективный, чем liftA2 = liftM2
. То есть
liftA2 f xs ys = xs >>= \x -> ys >>= \y -> pure (f x y)
Используя значение по умолчанию (*>) = liftA2 (flip const)
, вы получите
xs *> ys = xs >>= \ _ -> ys >>= \y -> pure y
С другой стороны, определение (>>)
по умолчанию дает
xs >> ys = xs >>= \ _ -> ys
Как вы можете видеть, это использует только одно связывание, где другое использует два. По закону монад-идентичности они эквивалентны, но компилятор не знает законы монады. Таким образом, подход, который вы предлагаете, вероятно, заставит оптимизатор работать усерднее и даже может помешать ему создать лучший код в некоторых случаях.