Функциональное программирование
Программировать можно без боли
Функциональное программирование – метапрограммирование над эффектами.
Эффекты?
class Cafe {
Coffee buyCoffee(CreditCard cc, Payments p) {
Coffee cup = new Coffee();
p.charge(cc, cup.price);
return cup;
}
}
class Cafe {
Pair<Coffee, Charge> buyCoffee(CreditCard cc) {
Coffee cup = new Coffee();
return new Pair(cup, cup.price);
}
}
Функциональное программирование:
функции – это тоже данные.
Int -> Bool (функция) – ровно такие же данные как и Int. Их также можно складывать в структуры, передавать как аргументы и т. п.
static <T> T f(T x)
static <T> T g(T x)
|
|
|
|
|
f x = x * 2
g x = x - 3
(f . g) 5
-- 4
Чисто функциональный
Все что функция может сделать – посмотреть на свои аргументы это вернуть значение.
Referential transparency
(Любую константу можно заменить на само значение)
int x = 8;
int y = x++;
System.out.println(y + " might be the same as " + y);
int x = 8;
System.out.println(x++ + " might be the same as " + x++);
Immuatble
Совсем immutable
Можно не думать о порядке выражений.
Нет переменных, циклов и условных переходов
Зато есть константы и нормальная рекурсия (А переходов вообще нет)
Очень ленивый
xs = [4, 1, 3, 2]
xs' = sort xs
print xs
xs' не нужен чтобы распечатать xs, поэтому сортироваться ничто не будет. (Зачем делать то, что можно не делать)
Если хотите быстро и просто потыкаться, тут есть интерктивная штука:
haskell.org
add2 :: Int -> Int
add2 x = x + 2
Все функции и константы всегда обозначаются словами с маленькой буквы без пробелов.
(Константы это просто функции с нулем аргументов.)
Pattern matching
fixBuz :: Int -> String
fixBuz 3 = "Fiz"
fixBuz 5 = "Buz"
fixBuz 15 = "FizBuz"
fixBuz _ = "Some other number"
Так матчить можно произвольные структуры произвольного уровня вложенности.
_ – специальное название константы, которое говорит что вам все равно что в ней лежит.
Произведение типов
(обычные поля структур)
data PersonType = Person String Int
vasya :: PersonType
vasya = Person "Vasya" 8
-- тип можно не укзывать
petya = Person "Petya" 5
getName :: PersonType -> String
getName (Person name _) = name
greetPerson :: PersonType -> String
greetPerson p = "Hello, " ++ getName p
greetPerson petya
-- "Hello, Petya"
vasya = Person "Vasya" 8
petya = Person "Petya" 5
matchVasya :: PersonType -> Bool
matchVasya (Person "Vasya" _) = True
matchVasya _ = False
matchVasya vasya
-- True
matchVasya petya
-- False
data Foo = Bar
qux :: Foo
qux = Bar
Foo – тип структуры. Bar – конструктор структуры.
Тут у Foo всего одно значение Bar.
Еще немного функций
greetPerson :: PersonType -> String
greetPerson p = "Hello, " ++ getName p
greetPerson :: PersonType -> String
greetPerson p = "Hello, " ++ name
where
name = getName p
greetPerson :: PersonType -> String
greetPerson p = "Hello, " ++ name
where
getName' (Person name _) = name
name = getName' p
data PersonType = Person String Int
getName :: PersonType -> String
getName (Person name _) = name
greetName :: String -> String
greetName name = "Hello, " ++ name
greetPerson :: PersonType -> String
greetPerson p = greetName (getName p)
greetPerson :: PersonType -> String
greetPerson = greetName . getName
greetPerson petya
-- "Hello, Petya"
(greetName . getName) petya
-- "Hello, Petya"
Суммы типов
x :: Bool
x = True
y = False
ifThenElse :: (Bool, a, a) -> a
ifThenElse (True, a, _) = a
ifThenElse (False, _, b) = b
ifThenElse (True, "Hello", "World")
-- "Hello"
ifThenElse (False, "Hello", "World")
-- "World"
data CircleType = Circle Double Double Double
data RectangleType = Rectangle Double Double Double Double
data Shape =
CircleShape CircleType | RectangleShape RectangleType
surface :: Shape -> Double
surface (CircleShape (Circle _ _ r)) =
pi * r ^ 2
surface (RectangleShape (Rectangle x1 y1 x2 y2)) =
(abs (x2 - x1)) * (abs (y2 - y1))
shape = CircleShape (Circle 0 0 2)
surface shape
-- 12.566370614359172
otherShape = RectangleShape (Rectangle 1 2 3 4)
surface otherShape
-- 4.0
Лямбда-выражения
λ – \ (λ печатать тяжело)
foo :: (Int -> Int) -> Int
Давайте придумаем синтаксис для функции нескольких аргументов!
x, y :: Int
x = 42
y = 69
xPlusY :: Int
xPlusY = add x y
Применение функции – лево-ассоциативно
add :: Int -> (Int -> Int)
add :: Int -> (Int -> Int)
Тип -> – право-ассоциативный
Любая функция берет строго один аргумент.
Функция нескольких аргументов все равно берет строго одтн аргумент и возвращает функцию, которая берет следующий.
(И из-за того, что применение функции лево-ассоциативно, вызов таких не трубует особого синтаксиса.)
Currying
add :: Int -> Int -> Int
add a b = a + b
add :: Int -> (Int -> Int)
Funny fact
Оператор (например +) – функция, название которой не содержит буквы и цифры.
Funny fact 2
Оператор можно превратить в функцию, окружив его скобками.
add :: Int -> Int -> Int
add x y = x + y
Funny fact 3
Функцию можно превратить в оператор, окружив ее обратными кавычками.
add :: Int -> Int -> Int
add x y = x + y
Односвязный список
data IntList = Nil | Cons Int IntList
Cons :: Int -> IntList -> IntList
Nil :: IntList
nums :: IntList
nums = 1 `Cons` (2 `Cons` (3 `Cons` Nil))
sum (Cons x xs) = x + sum xs
sum Nil = 0
sum (Cons x xs) = x + sum xs
take :: Int -> IntList -> IntList
take _ Nil = Nil
take 0 _ = Nil
take n (Cons x xs) = Cons x (take (n - 1) xs)
nums :: IntList
nums = 1 `Cons` (2 `Cons` (3 `Cons` Nil))
take 2 nums
-- Cons 1 (Cons 2 Nil)
take 1029 nums
-- Cons 1 (Cons 2 (Cons 3 Nil))
take 0 nums
-- Nil
repeat n = Cons n (repeat n)
repeat 8
-- Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (Cons 8 (...
(take 3 . repeat) 8
-- Cons 8 (Cons 8 (Cons 8 Nil))
(sum . take 3 . repeat) 8
-- 24
|
Наша самодеятельность
|
В стандартной библиотеке
|
|
|
|
|
|
|
|
|
|
|
|
|
data IntList = Nil | Cons Int IntList
data [Int] = [] | Int : [Int]
repeat, sum и take тоже есть в стандартной библиотеке.
repeat 8
-- [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, ...
[8 ..]
-- [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
[8 .. 11]
-- [8, 9, 10, 11]
quicksort :: [Int] -> [Int]
quicksort (x:xs) =
quicksort smaller ++ [x] ++ quicksort larger
where
smaller = filter (< x) xs
larger = filter (>= x) xs
filter :: (Bool -> Int) -> [Int] -> [Int]
filter f (x:xs) =
if f x
then x:(filter f xs)
else filter f xs
filter _ [] = []
filter f (x:xs) =
if f x
then filter f xs
else x:(filter f xs)
quicksort :: [Int] -> [Int]
quicksort (x:xs) =
quicksort smaller ++ [x] ++ quicksort larger
where
smaller = filter (< x) xs
larger = filter (>= x) xs
filter _ [] = []
filter f (x:xs) =
if f x
then filter f xs
else x:(filter f xs)
quicksort :: [Int] -> [Int]
quicksort [] = []
quicksort (x:xs) =
quicksort smaller ++ [x] ++ quicksort larger
where
smaller = filter (< x) xs
larger = filter (>= x) xs
filter _ [] = []
filter f (x:xs) =
if f x
then filter f xs
else x:(filter f xs)
quicksort [2, 1, 3, 4]
-- [1, 2, 3, 4]
filter тоже есть в стандартной библиотеке.
Параметрический полиморфизм
(Дженерики)
data IntList = Cons Int IntList | Nil
data List a = Cons a (List a) | Nil
ints :: List Int
ints = Cons 1 (Cons 2 (Cons 3 Nil))
-- Типы как всегда можно не писать
strings :: List String
strings = Cons "one" (Cons "two" (Cons "three" Nil))
things = Cons "one" (Cons 2 Nil)
• Couldn't match type ‘Int’ with ‘String’
Expected type: List String
Actual type: List Int
|
10 | things = Cons "one" (Cons 2 Nil)
| ^^^^^^^^^^^^
data List a = Cons a (List a) | Nil
a должен всегда быть a.
take :: Int -> IntList -> IntList
take _ Nil = Nil
take 0 _ = Nil
take n (Cons x xs) = Cons x (take (n - 1) xs)
take :: Int -> List a -> List a
take _ Nil = Nil
take 0 _ = Nil
take n (Cons x xs) = Cons x (take (n - 1) xs)
Где и как смотреть “стандартную библиотеку”
Hackage
(Там вам нужен только пакет base. Ссылка ведет прямо на него.) Еще если там нажать s, то будет поиск.
Hoogle
Это поиск по типам. Например: Int -> [Int] -> [Int] (Тут вам опять же нужен только пакет base. Нужно чтобы справа было "package:base".)