Про функцию говорят, что она обладает полиморфным поведением, если она может быть вызвана на значениях разных типов. Например, оператор сложения эта функция, которая может быть вызвана на значениях типа Int, возвращая результат типа Int; она также может быть вызвана на значениях типа Double, возвращая результат типа Double. Таким образом сложение это полиморфный оператор. Выделяют два типа полиморфных функций:
- параметрический полиморфизм - характеризуется тем, что код функций одинаков для всех типов на которых мы можем вызывать эту функцию;
- специальный полиморфизм - предполагает, что для каждого типа, для которых вызов этой функции допустим имеется своя собственная реализация.
Пример с оператором сложения это как раз пример специального полиморфизма потому что на низком уровне сложение значений целочисленных и сложение значений с плавающей точкой это разные функции и код.
Параметрический полиморфизм
Функция параметрически полиморфна, если она может вызываться на произвольном типе данных. Она никак не использует информацию о данном конкретном типе данных. Тип на котором эта функция вызывается обозначается просто типовой переменной.
Пример параметрически полиморфной функции:
Пример параметрически полиморфной функции:
Prelude> let id x = x Prelude> :t id id :: t -> t Prelude> id True True Prelude> id 5 5 Prelude> (id id) 4 4 Prelude> :t id True id True :: Bool Prelude> :t (id id) (id id) :: t -> t
На принимаемое значение функции id не накладывается никаких ограничений. Что мы приняли в качестве аргумента, то и возвращаем. Все конкретные типы начинаются с большой буквы. Здесь у нас тип произволен и используется переменная типа t, вместо неё может быть подставлен любой тип. В примере с выражением (id id) тип внешней функции будет (t -> t) -> (t -> t). Таким образом у внутренней id тип t -> t, а у внешней (t -> t) -> (t -> t). Дальше происходит применение внешней функции id к внутренней функции id. При применении функция становится арности на единицу меньше. Внешняя id оказывается функцией двух аргументов: (t -> t) -> t -> t. К одному своему аргументу возвращает функцию одного аргумента.
Пример параметрически полиморфной функции двух аргументов:
Здесь у нас опять присутствуют не конкретные типы, а переменные типа: t1 и t. Типы t1 и t не важны. Но это могут быть два разных типа. Здесь у нас тип возвращаемого значения это тип первого аргумента функции. Имя переменной типа совершено не важно.
В результате выражения const True получается функция одного аргумента. Тип этого аргумента совершено не известен. Потому что это тот аргумент, который игнорируется, а значение возвращаемое этой функцией наоборот известно. Функция const всегда возвращает ту константу, которая передана ей в качестве первого аргумента.
Константа undefined подходит для любого типа. В любом месте, где у нас присутствует выражение произвольного типа мы можем подставить константу undefined. При этом выражение будет правильно типизировано.
Про константу undefined говорят, что она в Haskell населяет любой допустимый тип. Естественно undefined не единственный способ создать объект, который населяет любой тип. Функция error с переданными туда строкой ведет себя точно также как undefined, ну и поэтому она тоже населяет любой тип. Она тоже является константой произвольного типа.
Эти две функции естественно прерывают выполнение программы и поэтому они отличны от многих других функций. Только с помощью прерывания исполнения программы мы можем создавать объекты наивысшей степени полиморфизма. Даже у функции типа id она уже имеет стрелочный тип, поэтому функция id не может использоваться в контексте, где стрелочный тип недопустим.
Функция getSecondFrom, полиморфна по каждому из трех аргументов, полностью игнорирует первый и третий аргумент, и возвращает второй:
Две функции одинаковой арности считаются разными, если существует набор значений их аргументов, на котором они дают разные результирующие значения. Можно реализовать 3 разных всегда завершающихся функции с типом a -> a -> b -> a -> a.
Пример параметрически полиморфной функции двух аргументов:
Prelude> let k x y = x Prelude> k 42 True 42 Prelude> k 42 55 42 Prelude> :t k k :: t1 -> t -> t1
Prelude> :t const const :: a -> b -> a Prelude> :t const True const True :: b -> Bool
Константа undefined подходит для любого типа. В любом месте, где у нас присутствует выражение произвольного типа мы можем подставить константу undefined. При этом выражение будет правильно типизировано.
Prelude> undefined *** Exception: Prelude.undefined Prelude> :t undefined undefined :: t
Prelude> error "Hello, World!" *** Exception: Hello, World! Prelude> :t error "Hello, World!" error "Hello, World!" :: t Prelude> :t error error :: [Char] -> a
Функция getSecondFrom, полиморфна по каждому из трех аргументов, полностью игнорирует первый и третий аргумент, и возвращает второй:
getSecondFrom :: a -> b -> c -> b getSecondFrom a b c = b
Две функции одинаковой арности считаются разными, если существует набор значений их аргументов, на котором они дают разные результирующие значения. Можно реализовать 3 разных всегда завершающихся функции с типом a -> a -> b -> a -> a.
foo :: a -> a -> b -> a -> a
- foo p1 p2 p3 p4 = p1
- foo p1 p2 p3 p4 = p2
- foo p1 p2 p3 p4 = p3
Можно ограничить степень полиморфизма функции с помощью явного указания её типа.
module Demo where {- Мономорфная функция: -} mono :: Char -> Char mono x = x {- Частичное ограничение полиморфизма функции -} {- Функция мономорфна 1 аргументу и полиморфна по 2 -} semiMono :: Char -> a -> Char semiMono x y = x
Prelude> let semiMono x y = x Prelude> :t semiMono semiMono :: t1 -> t -> t1
Специальный полиморфизм
Функция по-прежнему может вызываться на разных типах данных. Но каждый тип данных, который использует специальный полиморфизм должен обеспечивать реализацию соответствующего интерфейса для того чтобы функция могла его вызвать. Этот интерфейс называется классом типов. Класс типов - описывает интерфейс целиком. Есть еще одно понятие, реализация класс типов, реализация представителя, тип данных должен объявлять представителя класс типов, т.е. имплементировать соответствующий интерфейс и после этого уже можно передавать такой тип данных в специально полиморфную функцию.Наличие интерфейсов в типе функций:
Prelude> :t 7 7 :: Num a => a
Оператор сложения определен следующим образом:
Prelude> :t (+) (+) :: Num a => a -> a -> a
Оператор больше выглядит следующим образом:
Prelude> :t (>) (>) :: Ord a => a -> a -> Bool
Более сложно устроенный контекст:
Prelude> :t (> 7) (> 7) :: (Num a, Ord a) => a -> Bool
Пары сравниваются лексикографически. Первый и второй элемент пары могут относиться к разным числам, например Int и Double.
Prelude> :t (> (1,2)) (> (1,2)) :: (Num t, Num t1, Ord t, Ord t1) => (t, t1) -> Bool
Классы типов
Класс типов задает интерфейс, который конкретные типы могут реализовывать. Сам по себе класс типов представляет собой именованный набор имен функций с сигнатурами параметризованный общим типовым параметром.Пример класса типов Eq (взят из стандартной библиотеки):
module Demo where -- Eq - это имя класса типов. -- a - это типовой параметр, который параметризует этот класс типов. class Eq a where -- Дальше идут перечисления сигнатур функций. Они должны обязательно начинаться с ненулевого отступа. -- Здесь не задаются реализации, а присутствует только интерфейс. -- В случает специального полиморфизма должна быть своя реализация для каждого конкретного типа, который будет подставлен в месте a. (==) :: a -> a -> Bool -- функция сравнения на равенство элементов типа a (/=) :: a -> a -> Bool -- функция сравнения на неравенство
Prelude> :t (==) (==) :: Eq a => a -> a -> Bool Prelude> :t (== 42) (== 42) :: (Eq a, Num a) => a -> Bool Prelude> :t (== 'x') (== 'x') :: Char -> Bool Prelude> :t elem elem :: (Eq a, Foldable t) => a -> t a -> Bool
Конкретный тип называется представителем класса типов, если для него реализованы все функции, которые объявлены в классе типов. Иногда вместо слова представитель используют слово экземпляр (instance).
-- Объявление представителя класса типов. instance Eq Bool where True == True = True False == False = True _ == _ = False x /= y = not (x == y)
Haskell допускает возможность сделать реализацию по умолчанию в непосредственно объявлении класса типа.
class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x == y) -- реализация по умолчанию instance Eq Bool where True == True = True False == False = True _ == _ = False
Можно давать циклические реализации по умолчанию.
class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x == y) x == y = not (x /= y)
Представителем класса типов можно объявлять не только конкретный тип, но и некоторые полиморфные типы. Например, у нас есть полиморфный тип двухэлементного кортежа - пары. Две пары можно определять на предмет равенства, они равны, если равны их первые и вторые элементы. Реализация представителя класса типов Eq для пары:
-- Полиморфные ограничения появляются в виде контекста. instance (Eq a, Eq b) => Eq (a, b) where p1 == p2 = fst p1 == fst p2 && snd p1 == snd p2 -- Списки могут сравниваться на равенство, если их элементы сравнимы на равенство. -- instance Eq a => Eq [a] where -- реализация -- списки равны, когда равны их длины и они совпадают поэлементно
Класс типов Printable, предоставляет один метод toString — функцию одной переменной, которая преобразует значение типа, являющегося представителем Printable, в строковое представление.
class Printable p where toString :: p -> [Char] instance Printable Bool where toString True = "true" toString False = "false" instance Printable () where toString () = "unit type" instance (Printable a, Printable b) => Printable (a, b) where toString x = "(" ++ toString (fst x) ++ "," ++ toString (snd x) ++ ")"
GHCi> toString True "true" GHCi> toString False "false" GHCi> toString () "unit type" GHCi> toString (False,()) "(false,unit type)" GHCi> toString (True,False) "(true,false)"
Комментариев нет:
Отправить комментарий