Haskell Language
Типы классов
Поиск…
Вступление
Typeclasses в Haskell - это средство определения поведения, связанного с типом отдельно от определения этого типа. Если, скажем, в Java, вы определяете поведение как часть определения типа - то есть в интерфейсе, абстрактном классе или конкретном классе - Haskell сохраняет эти две вещи отдельно.
В base пакете Haskell уже определено несколько типов типов. Связь между ними проиллюстрирована в разделе «Примечания» ниже.
замечания
Следующая диаграмма, взятая из статьи Typeclassopedia, показывает взаимосвязь между различными классами в Haskell.
Возможно и класс Functor
В Haskell типы данных могут иметь аргументы, подобные функциям. Возьмите тип Maybe например.
Maybe , это очень полезный тип, который позволяет нам представить идею неудачи или ее возможности. Другими словами, если есть вероятность, что вычисление не удастся, мы будем использовать тип Maybe . Maybe похож на обертку для других типов, предоставляя им дополнительную функциональность.
Его фактическое заявление довольно просто.
Maybe a = Just a | Nothing
Это говорит о том, что « Maybe происходит в двух формах: « Just , который представляет успех, и « Nothing , который представляет собой неудачу. Just принимает один аргумент, который определяет тип Maybe , и Nothing принимает. Например, значение Just "foo" будет иметь тип Maybe String , который является строковым типом, завернутым в дополнительную функциональность Maybe . Значение Nothing имеет тип Maybe a где a может быть любым типом.
Эта идея обертывания типов, чтобы дать им дополнительную функциональность, очень полезна и применима к более чем просто Maybe . Другие примеры включают типы Either , IO и типы списков, каждый из которых обеспечивает различные функциональные возможности. Однако есть некоторые действия и способности, которые являются общими для всех этих типов обертки. Наиболее заметным из них является возможность изменения инкапсулированного значения.
Общепринято думать о таких типах ящиков, которые могут иметь значения, размещенные в них. Различные блоки содержат разные значения и делают разные вещи, но ни одна из них не полезна без возможности доступа к содержимому внутри.
Чтобы инкапсулировать эту идею, Haskell поставляется со стандартным классом типа Functor . Он определяется следующим образом.
class Functor f where
fmap :: (a -> b) -> f a -> f b
Как видно, класс имеет одну функцию fmap из двух аргументов. Первый аргумент - это функция от одного типа, a , от другого, b . Второй аргумент - это функтор (тип-оболочка), содержащий значение типа a . Он возвращает функтор (тип-обертку), содержащий значение типа b .
Проще говоря, fmap принимает функцию и применяется к значению внутри функтора. Это единственная функция, необходимая для того, чтобы тип был членом класса Functor , но он чрезвычайно полезен. Функции, работающие на функторах с более конкретными приложениями, можно найти в стилях Applicative и Monad .
Наследование типа класса: класс типа Ord
Haskell поддерживает понятие расширения класса. Например, класс Ord наследует все операции в Eq , но, кроме того, имеет функцию compare которая возвращает Ordering между значениями. Ord может также содержать общие операторы сравнения порядка, а также метод min и max метод.
Обозначение => имеет то же значение, что и в сигнатуре функции, и требует, чтобы тип a реализовал Eq , чтобы реализовать Ord .
data Ordering = EQ | LT | GT
class Eq a => Ord a where
compare :: Ord a => a -> a -> Ordering
(<) :: Ord a => a -> a -> Bool
(<=) :: Ord a => a -> a -> Bool
(>) :: Ord a => a -> a -> Bool
(>=) :: Ord a => a -> a -> Bool
min :: Ord a => a -> a -> a
max :: Ord a => a -> a -> a
Все методы следующие compare можно извлечь из него в ряде способов:
x < y = compare x y == LT
x <= y = x < y || x == y -- Note the use of (==) inherited from Eq
x > y = not (x <= y)
x >= y = not (x < y)
min x y = case compare x y of
EQ -> x
LT -> x
GT -> y
max x y = case compare x y of
EQ -> x
LT -> y
GT -> x
Типовые классы, которые сами расширяют Ord должны реализовать по крайней мере либо метод compare либо метод (<=) , который создает направленную решетку наследования.
уравнение
Все базовые типы данных (например, Int , String , Eq a => [a] ) из Prelude, за исключением функций и IO имеют экземпляры Eq . Если тип создает экземпляр Eq это означает, что мы знаем, как сравнивать два значения для значения или структурного равенства.
> 3 == 2
False
> 3 == 3
True
Необходимые методы
-
(==) :: Eq a => a -> a -> Booleanили(/=) :: Eq a => a -> a -> Boolean(если только один реализован, другой по умолчанию - отрицание определенный)
Определяет
-
(==) :: Eq a => a -> a -> Boolean -
(/=) :: Eq a => a -> a -> Boolean
Прямые суперклассы
Никто
Известные подклассы
Ord
Типичные экземпляры Ord включают, например, Int , String и [a] (для типов a где есть экземпляр Ord a ). Если тип создает экземпляр Ord это означает, что мы знаем «естественный» порядок значений этого типа. Обратите внимание: часто существует множество возможных вариантов «естественного» упорядочения типа, и Ord заставляет нас это одобрить.
Ord предоставляет стандартные (<=) , (<) , (>) , (>=) операторы, но интересно их определяет, используя собственный тип алгебраических данных
data Ordering = LT | EQ | GT
compare :: Ord a => a -> a -> Ordering
Необходимые методы
-
compare :: Ord a => a -> a -> Orderingили(<=) :: Ord a => a -> a -> Boolean(методcompareпо умолчанию используется(<=)в его реализации)
Определяет
-
compare :: Ord a => a -> a -> Ordering -
(<=) :: Ord a => a -> a -> Boolean -
(<) :: Ord a => a -> a -> Boolean -
(>=) :: Ord a => a -> a -> Boolean -
(>) :: Ord a => a -> a -> Boolean -
min :: Ord a => a -> a -> a -
max :: Ord a => a -> a -> a
Прямые суперклассы
Monoid
Типы экземпляров Monoid включают списки, числа и функции с возвращаемыми значениями Monoid . Чтобы создать экземпляр Monoid тип должен поддерживать ассоциативную двоичную операцию ( mappend или (<>) ), которая объединяет его значения и имеет специальное «нулевое» значение ( mempty ), так что объединение значения с ним не изменяет это значение:
mempty <> x == x
x <> mempty == x
x <> (y <> z) == (x <> y) <> z
Интуитивно, типы Monoid являются «подобными спискам», поскольку они поддерживают добавление значений вместе. В качестве альтернативы, типы Monoid можно рассматривать как последовательности значений, для которых мы заботимся о порядке, но не о группировании. Например, двоичное дерево является Monoid , но, используя операции Monoid мы не можем наблюдать его ветвящуюся структуру, а только обход его значений (см. Foldable и Traversable ).
Необходимые методы
-
mempty :: Monoid m => m -
mappend :: Monoid m => m -> m -> m
Прямые суперклассы
Никто
Num
Самый общий класс для типов чисел, точнее для колец , то есть чисел, которые можно добавлять и вычитать и умножать в обычном смысле, но не обязательно разделять.
Этот класс содержит как интегральные типы ( Int , Integer , Word32 и т. Д.), Так и дробные типы ( Double , Rational , также комплексные числа и т. Д.). В случае конечных типов семантика обычно понимается как модульная арифметика , т. Е. С избыточным и нижним потоком .
Обратите внимание, что правила для числовых классов гораздо менее строго соблюдаются, чем законы монады или моноида, или правила сравнения равенства . В частности, числа с плавающей запятой обычно подчиняются законам только в приблизительном смысле.
Методы
fromInteger :: Num a => Integer -> a. конвертировать целое число в общий тип номера (при необходимости обертывать диапазон). Литералы номера Haskell можно понимать как мономорфный литералIntegerс общим преобразованием вокруг него, поэтому вы можете использовать литерал5как в контекстеIntи в параметреComplex Double.(+) :: Num a => a -> a -> a. Стандартное дополнение, обычно понимаемое как ассоциативное и коммутативное, т. Е.a + (b + c) ≡ (a + b) + c a + b ≡ b + a(-) :: Num a => a -> a -> a. Вычитание, которое является обратным добавлению:(a - b) + b ≡ (a + b) - b ≡ a(*) :: Num a => a -> a -> a. Умножение, ассоциативная операция, которая является дистрибутивной над добавлением:a * (b * c) ≡ (a * b) * c a * (b + c) ≡ a * b + a * cдля наиболее распространенных экземпляров умножение также является коммутативным, но это определенно не является требованием.
negate :: Num a => a -> a. Полное имя оператора унарного отрицания.-1- синтаксический сахар дляnegate 1.-a ≡ negate a ≡ 0 - aabs :: Num a => a -> a. Функция абсолютного значения всегда дает неотрицательный результат той же величиныabs (-a) ≡ abs a abs (abs a) ≡ abs aabs a ≡ 0должно произойти только, еслиa ≡ 0.Для реальных типов ясно, какие неотрицательные средства: у вас всегда есть
abs a >= 0. Сложные и т. Д. Типы не имеют четко определенного порядка, однако результатabsвсегда должен лежать в вещественном подмножестве ‡ (т. Е. Дать число, которое также может быть записано в виде единственного литерала без отрицания).signum :: Num a => a -> a. Функция знака, в соответствии с именем, дает только-1или1, в зависимости от знака аргумента. Собственно, это верно только для ненулевых действительных чисел; в общихsignumлучше понимать как нормализующая функцию:abs (signum a) ≡ 1 -- unless a≡0 signum a * abs a ≡ a -- This is required to be true for all Num instancesОбратите внимание, что в разделе 6.4.4 отчета Haskell 2010 явно требуется, чтобы это последнее равенство выполнялось для любого действительного экземпляра
Num.
Некоторые библиотеки, в частности линейные и hmatrix , имеют гораздо более слабое понимание того, для чего предназначен класс Num : они рассматривают его как способ перегрузить арифметические операторы . Хотя это довольно просто для + и - , это уже становится проблематичным с * и тем более с другими методами. Например, должно * означать матричное умножение или умножение по элементам?
Вероятно, это плохая идея для определения таких не числовых экземпляров; рассмотрите специальные классы, такие как VectorSpace .
† В частности, «негативы» неподписанных типов обернуты вокруг большого положительного значения, например (-4 :: Word32) == 4294967292 .
‡ Это широко не выполняется: типы векторов не имеют реального подмножества. Спорные Num состояния для таких типов обычно определяют abs и signum элементы, что математически не имеет смысла.
