Почему не ограничен подклассом Enum в Haskell

9

Кажется, что у любого ограниченного экземпляра должна быть нормальная реализация Enum. Лично я не могу придумать контрпример, хотя, если кто-то придумает такой, который не является патологическим, я пойму, почему это не так.

Из :iэтих двух классов типов кажется, что единственное исключение в стандартной библиотеке в настоящее время относится к кортежам, которые являются ограниченными, но не перечислимыми. Однако любой ограниченный кортеж также должен быть вменяемым Enumerable, просто увеличивая последний элемент, а затем оборачиваясь, когда он достигает maxBound.

Это изменение, вероятно, также будет включать добавление predBи nextBили что-то в этом роде в Bounded для безопасного / циклического способа обхода значений Enum. В этом случае toEnum 0 :: (...)будет равно(toEnum 0, toEnum 0, ...) :: (...)

точка с запятой
источник
3
На самом деле не могу ответить на этот вопрос авторитетно, но рассмотрим диапазон всех действительных чисел от 0 до 1. Он имеет четкие нижние и верхние границы, но имеет бесчисленно бесконечные члены.
Довал
@ Довал, это справедливо. Однако то же самое можно сказать обо всех действительных числах в целом (бесчисленные бесконечные члены), но Double/ Floatи все подобные типы реализуются в Enumлюбом случае, они просто создают succ = (+ 1)и fromEnum = truncate. Путь Haskell действительно имеет смысл с точки зрения практичности, так как в противном случае [0, 0.5 ..] и тому подобное не сработает, поэтому, похоже, Haskell не беспокоится о счетности, когда дело доходит до Enums.
точка с запятой
1
Я не знал, что succэто (+1). Это странно, потому что Doubleи Floatне имеют бесконечную точность и , таким образом , являются перечислимы - succвозможно, было определено как +1 ULP .
Доваль
2
@Doval Я думаю, что причиной этого было то, что основная команда Haskell хотела, чтобы [1 ..] означало то же самое с Doubles, что и с Ints.
точка с запятой
@semicolon удваивается, а числа с плавающей точкой не являются действительными числами (например, нельзя хранить PI в двойном числе без потери точности), поэтому они перечислимы
jk.

Ответы:

8

Мне нравится один практический пример из мира языков программирования: набор типов в ОО-системе ограничен и дискретен, но не перечислим, а частично упорядочен, но не полностью упорядочен.

Рассматриваемый частичный порядок является отношением подтипов <:. Верхняя граница будет тогда верхним типом (который вызывает C # objectи Scala Any), а нижняя граница будет нижним типом (Scala Nothing; у C # / Java нет эквивалента, о котором можно говорить).

Однако нет способа перечислить все типы в системе типов, поэтому вы не можете написать instance Enum Type. Это должно быть понятно: пользователи могут писать свои собственные типы, поэтому нет никакого способа узнать, кем они будут заранее. Вы можете перечислить все типы в любой данной программе, но не во всей системе.

Точно так же (согласно определенному разумному определению подтипа) <:рефлексивно, транзитивно и антисимметрично, но не полностью . Есть пары типов, которые не связаны с <:. ( Catи Dogоба являются подтипами Animal, но ни один не является подтипом другого.)


Предположим, что мы пишем компилятор для простого языка OO. Вот представление типов в нашей системе:

data Type = Bottom | Class { name :: String, parent :: Type } | Top

И определение отношения подтипа:

(<:) :: Type -> Type -> Bool
Bottom <: _ = True
Class _ _ <: Bottom = False
Class n t <: s@(Class m _)
    | n == m = True  -- you can't have different classes with the same name in this hypothetical language
    | otherwise = t <: s  -- try to find s in the parents of this class
Class _ _ <: Top = True
Top <: Top = True
Top <: _ = False

Это также дает нам отношение супертипирования.

(>:) :: Type -> Type -> Bool
t >: s = s <: t

Вы также можете найти наименьшую верхнюю границу двух типов,

lub :: Type -> Type -> Type
lub Bottom s = s
lub t Bottom = t
lub t@(Class _ p) s@(Class _ q) =
    | t >: s = t
    | t <: s = s
    | p >: s = p
    | t <: q = q
    | otherwise = lub p q
lub Top _ = Top
lub _ Top = Top

Упражнение: покажите, что Typeобразует ограниченный полный набор в два пути, под <:и под >:.

Бенджамин Ходжсон
источник
Огромное спасибо! Это полностью отвечает на мой вопрос, а также отвечает на вопрос о Ord. У Eq будут похожие проблемы? Где не уравниваемый тип может иметь maxBound или minBound. В этом случае должен ли Cat == Dog просто возвращать false, так как они являются разными классами, или это будет неразрешимо из-за того, что позиция дерева не ставит ни выше, ни ниже другого?
точка с запятой
Порядок подразумевает равенство - просто определите x == y = x <= y && y <= x. Если бы я проектировал Posetкласс, я бы имел class Eq a => Poset a. Быстрый Google подтверждает, что другие люди имели ту же идею .
Бенджамин Ходжсон
Извините, мой вопрос был неоднозначным. Я имел в виду, подразумевал ли Bounded уравнение, даже если оно не подразумевает Орд.
точка с запятой
@semicolon Опять же, нет никакой связи между двумя классами. Рассмотрим data Bound a = Min | Val a | Max, дополняющая типа aс +∞и -∞элементами. По построению Bound aвсегда можно сделать экземпляр , Boundedно это было бы только equatable если основной тип aявляется
Бенджамин Ходжсон
Хорошо, достаточно справедливо. Я предполагаю, что одним примером могут быть функции, которые принимают и возвращают значения типа Double, где const (1/0)есть maxBoundи const (negate 1/0)есть, minBoundно \x -> 1 - xи \x -> x - 1несопоставимы.
точка с запятой
4

Это потому, что операции независимы, поэтому связывание их вместе с отношениями подкласса на самом деле ничего не покупает. Скажем, вы хотели создать собственный тип, который реализован Bounded, возможно, Doublesограниченный между максимумом и минимумом, но вам не нужно было выполнять какие-либо Enumоперации. Если бы вы Boundedбыли подклассом, вам все Enumравно пришлось бы реализовать все функции, просто чтобы его скомпилировать.

На самом деле не имеет значения, есть ли разумная реализация Enumили любое другое количество классов типов. Если вам это на самом деле не нужно, вы не должны быть вынуждены это применять.

Сравните это с, скажем, Ordи Eq. Там Ordоперации зависят от Eqних, поэтому имеет смысл требовать подкласса, чтобы избежать дублирования и обеспечить согласованность.

Карл Билефельдт
источник
1
В этих случаях это часть определения. Все монады также являются аппликативами и функторами по определению, поэтому вы не можете выполнить «контракт» монады, не выполнив другие. Я недостаточно знаком с математикой, чтобы знать, фундаментальные ли это отношения или навязанное определение, но в любом случае, мы застряли с этим сейчас.
Карл Билефельдт
6
@semicolon В документацииBounded говорится, что «Ord не является суперклассом Bounded, поскольку типы, которые не являются полностью упорядоченными, также могут иметь верхнюю и нижнюю границы».
Бенджамин Ходжсон
1
@BenjaminHodgson Даже не думал о частично упорядоченных типах. +1 за непатологический пример и за цитирование документации.
Доваль
1
@semicolon Примером частичного упорядочения из мира компьютеров может быть подтип на ОО-языках. Запись <:для является подтипом , ∀ T S. T <: S ∨ S <: Tне имеет места (например, int !<: bool ∧ bool !<: int). Скорее всего, вы столкнулись бы с этим, если бы писали компилятор.
Бенджамин Ходжсон
1
@ BenjaminHodgson ах хорошо. Так, например, если A является суперклассом B и C, а D является подклассом B и C, то B и C несопоставимы, а A и D являются max / min?
точка с запятой