length, head, teal, curry, uncurry, map, filter, .... Что объединяет все эти функции? Давайте посмотрим на реализацию некоторых из них:length :: [a] -> Int
length [] = []
length (_:xs) = 1 + length xs
head :: [a] -> a
head (x:_) = x
tail :: [a] -> [a]
tail (_:xs) = xs
curry :: ((a,b) -> c) -> (a -> b -> c)
curry f x y = f (x,y)
uncurry :: (a -> b -> c) -> ((a,b) -> c)
uncurry g (x,y) = g x ya, а у curry и uncurry еще b и c. Вместо конкретного типа данных (Int, Bool, Char,...) используется типизация. Пример на java:public class Test<A> {...}a, abc, aA101), а все конкретные типы (конструкторы) начинаются с большой буквы (например String, Int, Node). a может принимать любые типы, будь то Int, String или даже функции (например, length [f, g, h], где f, g, h — функции, которые имеют одинаковые типы). Стоит заметить, что тип b может также принимать любые типы, в том числе и тип параметра a.:t, например:Main>:t length
length :: [a] -> Int(+) 2 3 ->> 5(+) 2.5 3.85 ->> 6.35 (+) True False(+) [1,2,3] [3,2,1](+) :: a -> a -> a (+) :: Num a => a -> a -> a. То есть здесь вводится ограничение на тип вводимых (и выводимых) данных.a: Num a говорит, что тип a должен быть элементом класса Num. Такие классы типов очень важны в Haskell, так как они добавляют дополнительную защиту от ошибок при программировании, а также могут уменьшить количество написанного кода в разы. Это мы увидим в следующем примере.data Tree1 a = Nil | Node1 a (Tree1 a) (Tree1 a)data Tree2 a b = Leaf2 b | Node2 a b (Tree2 a b) (Tree2 a b)data Tree3 = Leaf3 String | Node3 String Tree3 Tree3type Lst a = [a]data Rope a b = Nil | Twisted b (Rope b a)size, которая независимо от структуры данных, выводила бы либо её глубину (для деревьев), либо длину (для списков), или одним словом — размер. Наивным решением было бы написать для каждого свою функцию:sizeT1 :: Tree1 a -> Int --считаем кол-во узлов
sizeT1 Nil = 0
sizeT1 (Node1 _ l r) = 1 + sizeT1 l + sizeT1 r
sizeT2 :: (Tree2 a b) -> Int --считаем кол-во элементов
sizeT2 (Leaf 2 _) = 1
sizeT2 (Node2 _ _ l r) = 2 + sizeT2 l + sizeT2 r
sizeT3 :: Tree3 -> Int --считаем длины элементов типа String
sizeT3 (Leaf3 m)= length m
sizeT3 (Node m l r) = length m + sizeT3 l +sizeT3 r
-- и для списков:
sizeLst :: [a] -> Int
sizeLst = length
sizeRope :: (Rope a b) -> Int
sizeRope Nil = 0
sizeRope (Twisted _ ls) = 1 + sizeRope lsclass Size a where
size :: a -> Inta (=под конкретные типы данных):instance Size (Tree1 a) where
size Nil = 0
size (Node1 _ l r) = 1 + size l + size r
instance Size (Tree2 a b) where
size (Leaf2 _) = 1
size (Node2 _ _ l r) = 2 + size l + size r
instance Size Tree3 where
size (Leaf3 m) = length m
size (Node3 m l r) = length m + size l + size r
instance Size [a] where
size = length
instance Size (Rope a b) where
size Nil = 0
size (Twisted _ ls) = 1 + size ls:t size, то увидим size :: Size a => a -> Int. Мы получили что хотели:size Nil ->> 0
size (Node1 "foo" (Node1 "bar" Nil Nil) Nil) ->> 2
size (Leaf 2 "foo") ->> 1
size (Node3 "foo" (Node3 "bar" (Leaf3 "abc") (Leaf "cba")) (Leaf "tst")) ->> 15 --3*5
size [1..5] ->> 5
size (Rope 2 (Rope ‘a’ (Rope 5 Nil))) ->> 3Size реализовал функцию size, которая применима лишь к значениям определённого типа (=структурам данных). В данном случае эта функция, как и другие предопределённые операторы (+), (*), (-), перегружена.size [(1,2), (3,4)] ->> 4
size [('a','b'), ('c','d'), ('e','f')] ->> 6 instance Size [(a,b)] where
size = (*2) . lengthinstance Size [a] where), нам более нельзя использовать другое определение, потому что, как уже говорилось ранее, тип a может быть любым типом, в том числе и (b,c), то есть. [a] == [(b,c)] Для решения данной проблемы можно использовать Overlapping Instances (англ. перекрывающие экзепляры класса). У этого решения есть и свои минусы (импорт одного из модулей может поменять значение программы, может вызвать сбивающие с толку ошибки, и так далее).Size, а вот неполная декларация класса в Haskell: (tv = type variable):class Name tv where
сигнатура включающая переменную tvtv (например, tv должна быть элементом класса Ord). Сигнатур может быть сколько угодно, но каждая из них должна включать (как входной и/или выходной параметр) переменную tv.deriving). Между классами существует тоже зависимость. Например, если мы знаем как сравнивать два элемента (класс Ord), то мы должны заранее уметь определять равен ли один элемент другому (класс Eq). Ведь для реализации оператора (>=) надо иметь реализованный оператор (==). Поэтому, можно сказать, что класс Ord зависит (наследует) от класса Eq. Вот более подробная схема зависимости классов:
Eq более детально. Как уже ранее было упомянуто, этот класс должен иметь две функции (==) и (/=):class Eq a where
(==), (/=) :: a -> a -> Bool --так как сигнатуры (==) и (/=) одинаковы, их можно записать в 1 строчку
x /= y = not (x == y)
x == y = not (x /= y)instance Eq Bool where
(==) True True = True
(==) False False = True
(==) _ _ = False instance (Eq a) => Eq (Tree2 a) where --накладываем условие на a, что этот тип может сравниваться
(==) (Leaf2 s) (Leaf2 t) = (s==t)
(==) (Node2 s t1 t2) (Node2 t u1 u2)= (s==t) && (t1 == u1) && (t2 == u2)
(==) _ _ = False(Tree3 a b) мы уже должны будем накладывать условие как на a, так и на b, то естьinstance (Eq a, Eq b) => Eq (Tree3 a b)=> называется контекстом, всё что правее этого символа должно быть либо базовым типом (Int,Bool,...), либо конструкторы типов (Tree a, [...],...). Оператор (==) является перегруженной функцией (ad-hoc полиморфия).(==) fac fib ->> False --где fac - факториал числа, а fib - число Фибоначчи
(==) (\x -> x+x) (\x -> 2*x) ->> True
(==) (+2) (2+) ->> Trueinstace Eq (Int -> Int) where
(==) f g = ... --где f и g являются какими-то функциямиderiving. То есть мы могли бы вполне спокойно написать:data Tree a = Nil | Node a (Tree a) (Tree a) deriving Eqinstance Eq a => Eq (Tree a) where ...). Но для любой функции, тогда придется накладывать условие на переменную a, что эта переменная является элементом класса Eq. Иногда, всё-таки приходится самому писать эти функции, так как «автоматическое» сравнение не всегда работает так как мы бы того хотели. Например для двоичного дереваdata Tree3 a b = Leaf3 bl | Node3 a b (Tree3 a b) (Tree3 a b) deriving Eqinstance (Eq a, Eq b) => Eq (Tree3 a b) where
(==) (Leaf3 q) (Leaf3 s) = (q==s)
(==) (Node3 _ q t1 t2) (Node3 _ s u1 u2) = (q==s)&&(t1==u1)&&(t2==u2)
(==) _ _ = False Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.
комментарии (3)