(ML) Модули vs (Haskell) Тип Классы

Согласно Харперу (https://existentialtype.wordpress.com/2011/04/16/modules-matter-most/), кажется, что Type Classes просто не предлагают тот же уровень абстракции, который предлагают Модули, и я "Мне трудно понять, почему. И в этой ссылке нет примеров, поэтому мне трудно увидеть ключевые отличия. Существуют также другие статьи о том, как переводить между модулями и классами типов ( http://www.cse.unsw.edu.au/~chak/papers/modules-classes.pdf), но это не имеет ничего общего с реализация в перспективе программиста (он просто говорит, что нет ничего, что можно было бы сделать, чтобы другой не мог эмулировать).

В частности, в первая ссылка:

Во-первых, они настаивают на том, что тип может реализовать класс типа одним способом. Например, согласно философии классов типов, целые числа можно упорядочить точно одним способом (обычным порядком), но, очевидно, существует много упорядочений (например, делимости), представляющих интерес. Во-вторых, они смешивают две отдельные проблемы: указание того, как тип реализует класс типа и указывает, когда такая спецификация должна использоваться во время вывода типа.

Я тоже не понимаю. Тип может реализовать класс типа более чем одним способом в ML? Как бы у вас были целые числа, упорядоченные по делимости на примере, без создания нового типа? В Haskell вам нужно будет сделать что-то вроде использования данных и предложить instance Ord альтернативный порядок.

И второй, разве не два отличаются в Haskell? Указание "когда такая спецификация должна использоваться во время вывода типа" может быть выполнена следующим образом:

blah :: BlahType b => ...

где BlahType - это класс, используемый во время вывода типа, а НЕ - для класса реализации. Принимая во внимание, что "как тип реализует класс типа" выполняется с помощью instance.

Может кто-нибудь объяснить, что ссылка действительно пытается сказать? Я просто не совсем понимаю, почему модули будут менее ограничительными, чем Type Classes.

Ответы

Ответ 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 нет классов и экземпляров, но у него есть подписи и структуры, которые могут действовать почти одинаково. Для более глубокого сравнения прочитайте этот документ).