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

Почему (>>) не определяется как (*>)?

В настоящее время 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).

4b9b3361

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

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