(ML) модули vs (Haskell) типы классов
согласно Харперу (https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/), похоже, что классы типов просто не предлагают тот же уровень абстракции, что и модули, и мне трудно точно понять, почему. И в этой ссылке нет примеров, поэтому мне трудно увидеть ключевые различия. Существуют также другие статьи о том, как переводить между модулями и классами типов (http://www.cse.unsw.edu.au / ~chak / статьи / модули-классы.pdf), но это на самом деле не имеет ничего общего с реализацией с точки зрения программиста (он просто говорит, что нет ничего, что один может сделать, что другой не может эмулировать).
в частности, в первая ссылка:
во-первых, они настаивают на том, что тип может реализовать класс типа точно одним способом. Например, согласно философии типа классы, целые числа могут быть упорядочены точно одним способом (обычный порядок), но, очевидно, есть много упорядочений (скажем, по делимости), представляющих интерес. Во-вторых, они смешивают две отдельные проблемы: указание того, как тип реализует класс типа и указание того, когда такая спецификация должна использоваться во время вывода типа.
Я тоже не понимаю. Тип может реализовать класс типа более чем 1 способом в ML? Как бы вы упорядочили целые числа по делимость на примере без создания нового типа? В Haskell вам нужно будет сделать что-то вроде использования данных и иметь instance Ord
предложить альтернативный заказ.
а второй, разве они не различны в Хаскелле? Указание "когда такая спецификация должна использоваться во время вывода типа" может быть сделано примерно так:
blah :: BlahType b => ...
где BlahType-класс, используемый во время вывода типа, а не реализующий класс. Тогда как, "как тип реализует класс типа " выполняется с помощью instance
.
может ли кто-нибудь объяснить, что ссылка действительно пытается сказать? Я просто не совсем понимаю, почему модули будут менее ограничительными, чем классы типов.
1 ответов
чтобы понять, что говорится в статье, найдите время, чтобы рассмотреть Monoid
typeclass в 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
тип. Какой тип вы используете, определяет, какой экземпляр. В этом суть того, что статья пыталась описать. В Haskell нет встроенной системы система typeclass которая позволяет вам выбрать между множественными intances. Вместо этого вы должны прыгать через обручи, обертывая и разворачивая их в типах скелетов. Теперь, считаете ли вы это проблемой, это большая часть того, что определяет, предпочитаете ли вы Haskell или ML.
ML обходит это, позволяя нескольким "экземплярам" одного и того же класса и типа определяться в разных модулях. Затем, какой модуль вы импортируете, определяет, какой "экземпляр" вы используете. (Собственно говоря, ML не имеет классов и экземпляров, но у него есть подписи и структуры, которые могут действовать почти одинаково. Для amore в сравнении глубины, читать этой статье).