#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
является aMonoid
, и в этом случае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
.