В чем разница между pure и mempty в Haskell?

#haskell #applicative #monoids

#haskell #аппликативный #моноиды

Вопрос:

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

 appendIfEven :: (Applicative f, Monoid (f a), Integral a) => a -> f a -> f a
  

Моим ответом был следующий фрагмент кода

 appendIfEven :: (Applicative f, Monoid (f a), Integral a) => a -> f a -> f a
appendIfEven x ms = if x `mod` 2 == 0 then mempty x `mappend` ms else ms
  

Haskell может скомпилировать мой код, но он не работает должным образом. После некоторых экспериментов я переключил mempty на pure :

 appendIfEven :: (Applicative f, Monoid (f a), Integral a) => a -> f a -> f a
appendIfEven x ms = if x `mod` 2 == 0 then pure x `mappend` ms else ms
  

Который работает нормально. Но почему? не должен ли pure == mempty ? (В данном контексте)
Кажется, это не важно для моего преподавателя. Однако мне бы очень хотелось немного больше узнать о Haskell и где я ошибаюсь….

Комментарии:

1. Ну, во-первых, у них есть разные типы. Как они могут быть одним и тем же, если у них разные типы?

2. Он компилируется , потому a -> b что имеет Monoid экземпляр до тех пор , пока также b является a Monoid , и в этом случае mempty = const mempty .

3. у @user253751 a -> b есть Monoid экземпляр, и в этом случае mempty он сам также является функцией.

Ответ №1:

Вы непреднамеренно использовали неожиданный моноид, который, кстати, заставил ваш код скомпилироваться.

Когда вы писали mempty x `mappend` ms , ms значение имеет тип f a , который является моноидом. Это вынуждает mempty x иметь один и тот же тип, поскольку mappend требуется два аргумента одного и того же типа. Следовательно, мы должны иметь

 mempty x :: f a
  

что подразумевает, поскольку x :: a ,

 mempty :: a -> f a
  

Странно, правда?

Ну, бывает, что в библиотеках есть экземпляр

 instance Monoid t => Monoid (u -> t) where
   mempty = _ -> mempty
   ...
  

который вы непреднамеренно использовали, передавая x mempty , поскольку t может быть f a , и u может быть a в приведенном выше примере. Haskell разрешил ограничение monoid, чтобы использовать этот экземпляр.

Это также означает, что

 mempty x `mappend` ms
  

это то же самое, что

 (_ -> mempty) x `mappend` ms  -- this is the mempty for (f a), instead!
  

что совпадает с

 mempty `mappend` ms   -- this is the mempty for (f a), instead!
  

что совпадает с

 ms
  

Следовательно, ваш код полностью игнорирует x аргумент.

Напротив pure x x , в общем случае зависит от, поэтому конечный результат может сильно отличаться.

Ответ №2:

Рассмотрите возможность применения этого к спискам.

 mempty == []

pure 5 = [5]
  

Они не очень похожи на меня. В частности, mempty x должно быть плохо типизировано.

В основном mempty дает вам пустую вещь, тогда pure x как дает вам вещь с x в ней.

Комментарии:

1. mempty определяется Monoid b => a -> b для быть const mempty .

2. @chepner Да, у меня было ощущение, что компилятор не жалуется, потому что подходящий экземпляр либо существует, либо теоретически может существовать. Все равно это не делает его разумным выражением (если вы действительно не используете такой экземпляр).

3. В надлежащем контексте это может иметь смысл использовать mempty как функцию. Вместо этого, IMO, нет смысла немедленно применять его, mempty x поскольку он эквивалентен (в том же контексте) mempty .

4. экземпляр функций — это то, что делает возможным знаменитый трюк comparing snd <> comparing fst с (f <> g) x y = (f x <> g x) y = f x y <> g x y .