Ответ 1
Чтобы понять, что говорится в статье, найдите момент, чтобы рассмотреть класс Monoid
в Haskell. Моноид - это любой тип, T
, который имеет функцию mappend :: T -> T -> T
и элемент идентичности empty :: T
, для которого выполняется следующее.
a `mappend` (b `mappend` c) == (a `mappend` b) `mappend` c
a `mappend` mempty == mempty `mappend` a == a
Существует много типов Haskell, которые соответствуют этому определению. Одним из примеров, который сразу возникает на ум, являются целые числа, для которых мы можем определить следующее.
instance Monoid Integer where
mappend = (+)
mempty = 0
Вы можете подтвердить, что все требования сохранены.
a + (b + c) == (a + b) + c
a + 0 == 0 + a == a
Действительно, эти условия сохраняются для всех чисел над сложениями, поэтому мы можем также определить следующее.
instance Num a => Monoid a where
mappend = (+)
mempty = 0
Итак, теперь в GHCi мы можем сделать следующее.
> mappend 3 5
8
> mempty
0
В частности наблюдательные читатели (или те, у кого есть опыт в математике), вероятно, уже заметили, что мы можем также определить экземпляр Monoid
для чисел по умножению.
instance Num a => Monoid a where
mappend = (*)
mempty = 1
a * (b * c) == (a * b) * c
a * 1 == 1 * a == a
Но теперь компилятор сталкивается с проблемой. Какое определение mappend
следует использовать для чисел? Значит ли mappend 3 5
8
или 15
? Невозможно решить. Вот почему Haskell не разрешает несколько экземпляров одного класса. Однако этот вопрос все еще стоит. Какой экземпляр Monoid
Num
следует использовать? Оба они абсолютно верны и имеют смысл при определенных обстоятельствах. Решение состоит в том, чтобы не использовать ни один из них. Если вы посмотрите Monoid
в Hackage, вы увидите, что нет экземпляра Monoid
Num
или Integer
, Int
, Float
или Double
. Вместо этого существуют Monoid
экземпляры Sum
и Product
. Sum
и Product
определяются следующим образом.
newtype Sum a = Sum { getSum :: a }
newtype Product a = Product { getProduct :: a }
instance Num a => Monoid (Sum a) where
mappend (Sum a) (Sum b) = Sum $ a + b
mempty = Sum 0
instance Num a => Monoid (Product a) where
mappend (Product a) (Product b) = Product $ a * b
mempty = Product 1
Теперь, если вы хотите использовать число как Monoid
, вы должны обернуть его либо типом Sum
, либо Product
. Какой тип используется, определяет, какой экземпляр Monoid
используется. В этом суть того, что статья пыталась описать. В систему типов Haskell нет системы, которая позволяет вам выбирать между несколькими задачами. Вместо этого вы должны прыгать через обручи, обертывая и разворачивая их в скелетных типах. Теперь, независимо от того, считаете ли вы эту проблему, большая часть того, что определяет, предпочитаете ли вы Haskell или ML.
ML оборачивается этим, позволяя определять несколько "экземпляров" того же класса и типа в разных модулях. Затем, какой модуль вы импортируете, определяет, какой "экземпляр" вы используете. (Строго говоря, у ML нет классов и экземпляров, но у него есть подписи и структуры, которые могут действовать почти одинаково. Для более глубокого сравнения прочитайте этот документ).