Поиск…


Вступление

Typeclasses в Haskell - это средство определения поведения, связанного с типом отдельно от определения этого типа. Если, скажем, в Java, вы определяете поведение как часть определения типа - то есть в интерфейсе, абстрактном классе или конкретном классе - Haskell сохраняет эти две вещи отдельно.

В base пакете Haskell уже определено несколько типов типов. Связь между ними проиллюстрирована в разделе «Примечания» ниже.

замечания

Следующая диаграмма, взятая из статьи Typeclassopedia, показывает взаимосвязь между различными классами в Haskell.

Отношения между стандартными типами классов Haskell, рис. 1, опубликованные в Typeclassopedia.

Возможно и класс 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 - a
    
  • abs :: Num a => a -> a . Функция абсолютного значения всегда дает неотрицательный результат той же величины

      abs (-a) ≡ abs a
      abs (abs a) ≡ abs a
    

    abs 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 элементы, что математически не имеет смысла.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow