Давайте введем функцию, которая меняет местами аргументы другой функции:
--filp' :: (a -> b -> c) -> (b -> a -> c)
flip' :: (a -> b -> c) -> b -> a -> c
flip' f = \x y -> f y x
revminus = flip' (-)
revminus 2 3
flip' (-) 2 3
flip'' :: (a -> b -> c) -> b -> a -> c
flip'' f x y = f y x
flip'' (/) 2 10
1
1
5.0
--f = \(h:t) -> h + 1
--f [10, 20, 30]
Вывод: пользуйтесь flip
.
Функция применения $
:
($*) :: (a -> b) -> a -> b
f $* x = f x
addOne = (+1)
addOne 10
addOne $* 10
addOne $ 10
-- пример, где есть разница
addOne 10 / 2
addOne $ 10 / 2
addOne (10 / 2)
11
11
11
5.5
6.0
Получается, что $
иногда удобна, чтобы писать меньше скобок. Но будьте с ней аккуратны, у нее правая ассоциативность:
(+) (2*2) (3*3)
-- (+) $ (2*2) $ (3*3) -- пытается сначала посчитать (2*2) $ (3*3)
((+) $ (2*2)) $ (3*3) -- слишком много скобок
(*2) $ (+1) $ 3*3 -- сначала +1, потом *2
13
13
20
Еще одна функция высшего порядка: .
. Композиция функций, как в математике: $h = g \circ f$, тогда по определению, $h(x) = g(f(x))$. В Haskell есть аналогичная функция .
:
(.*) :: (b -> c) -> (a -> b) -> (a -> c)
-- g .* f = \x -> g (f x)
(g .* f) x = g $ f x
fun = (+1) . (*2)
fun 42
85
filter (\x -> x `mod` 2 == 0) [1..10]
filter ((==0) . (`mod` 2)) [1..10]
[2,4,6,8,10]
[2,4,6,8,10]
Введем для примера понятие человека, который может быть студентом или преподавателем
-- студент имя, курс, преподаватель только имя
-- deriving Show, заклинание, которое позволяет распечатывать значения
data Human = Student String Int | Lecturer String deriving Show
Это описание типа. Т.е. есть тип Человек, значения этого типа можно создать с помощью двух "конструкторов типа" Student
и Lecturer
. При вызове первого конструктора нужно указать String и Int. При вызове второго — только String:
human1 = Student "John" 1
human2 = Student "Mary" 5
human3 = Lecturer "Ilya"
human1 -- работает show human1
Student "John" 1
Получается, что конструкторы типов работают как функции:
:type Student
:type Lecturer
И их действительно можно использовать как функции:
map Lecturer ["Ilya", "Pavel Petrovich"]
[Lecturer "Ilya",Lecturer "Pavel Petrovich"]
Пример, в котором конструктор типа называется так же как и тип. Это часто случается, надо уметь не путать где тип, а где конструктор:
data Point = Point Int Int deriving Show
Point 1 2 -- использую конструктор типа
f :: Point -> Int -- здесь это тип
-- чтобы реализовать функцию, используется сопоставление с образцом
-- через конструкторы типа
f (Point x y) = abs x + abs y -- норма 1ой степени, здесь конструктор
f (Point 2 3)
Point 1 2
5
Пример функции для Human
:
getName :: Human -> String
getName (Student name _) = name -- номер курса не интересен, _
getName (Lecturer name) = name
getName human1
getName human2
getName human3
"John"
"Mary"
"Ilya"
Кстати, типы и конструкторы типов в Haskell должны начинаться с заглавной буквы.
Алгебраические типы могут быть параметризованы другим типом. Введём тип "Пара":
data Pair a = Pair a a deriving Show
p1 = Pair 2 3
p2 = Pair "ABC" "XYZ"
-- p3 = Pair 2 "ABC" -- не одинаковый тип
swap :: Pair a -> Pair a
swap (Pair x y) = Pair y x
swap p1
swap $ Pair "ABC" "XYZ"
inc :: Pair Int -> Pair Int
inc (Pair x y) = Pair (x + 1) (y + 1)
inc $ Pair 5 6
Pair 3 2
Pair "XYZ" "ABC"
Pair 6 7
Т.е. Pair
сам по себе как тип использовать нельзя, после него обязательно должен быть написан еще тип. Произвольный a
или конкретный, например, Int
.
Алгебраический тим можно определять через себя. Давайте заведем свой список:
-- список чисел
data List = Empty | Node Int List deriving Show
lst1 = Empty
lst2 = Node 10 Empty
lst3 = Node 20 lst2
lst33 = Node 20 (Node 10 Empty) -- аналогично предыдущему
lst4 = Node 10 (Node 20 (Node 30 Empty))
data List' = Nil | (:*) Int List' deriving Show
lst1' = Nil
lst2' = 10 :* Nil
lst3' = 10 :* (20 :* Nil)
length' :: List' -> Int
length' Nil = 0
length' (h :* t) = 1 + length' t
length' lst3'
2
А теперь объединим рекурсивные типы и параметризованные типы, сделаем список, в котором можно хранить значения произвольного типа.
data List a = Nil | (:*) a (List a) deriving Show
"abc" :* ("xyz" :* Nil) -- соответствует "списку" ["abc", "xyz"]
(:*) "abc" ((:*) "xyz" Nil)
Анонс:
take 15 [1..]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
ones = 1 : ones
take 15 ones
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
Будет задача. fib = ...
бесконечный список чисел Фибоначчи.