Функциональное программирование
Функциональное программирование это не про использование map filter reduce. Функциональное программирование это вообще ортогонально к ооп. ИМХО проблема с ООП – наследование (которое тоже ортогонально к ООП) – нет разграничения между абстракцией и реализацией. и вообще обзывание это функциональным программированием – не совсем правильно. то, что мы сейчас будем пытаться сделать – сделать код более переиспользуемым. сделать наши данные максимально тупыми => переиспользуемыми. пытаться комбинировать слишком умные куски – не получится – слишком сложно. мы хотим сделать простые куски, которые просто комбинировать и еще чтобы компилятор проверял эти наши комбинации на валидность.
Программировать можно без боли
Функциональное программирование:
функции – это тоже данные.
Int -> Bool
(функция) – ровно такие же данные как и Int
. Их также можно складывать в структуры, передавать как аргументы и т. п.
static <T> T f (T x)
static <T> T g (T x)
моноид
моноид – у тебя есть две штуки одного типа и ты можешь получить еще одну штуку такого же типа. Это очень простой способ рождения сложности из простоты причем тип h выводится из определения. Нам его указывать не надо. если у нас изначальные маленькие кусочки хорошо описаны, то порожденные большие кусочки будут автоматически хорошо описанными.
Моноид
Бинарная асоциативная операция
(.) :: (a -> a) -> (a -> a) -> (a -> a)
Нейтральный элемент
Чисто функциональный
Все что функция может сделать – посмотреть на свои аргументы это вернуть значение.
Нельзя просто произвольно распечатать строку в консоль, нельзя просто произвольно отправить запрос на бд, нельзя просто произвольно изменить глобальную переменную.
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++);
Нет переменных, циклов и условных переходов
Зато есть константы и нормальная рекурсия (А переходов вообще нет)
Очень ленивый
xs = [4 , 1 , 3 , 2 ]
xs' = sort xs
print xs
xs'
не нужен чтобы распечатать xs
, поэтому сортироваться ничто не будет. (Зачем делать то, что можно не делать)
Если хотите быстро и просто потыкаться, тут есть интерктивная штука:
haskell.org
add2 :: Int -> Int
add2 x = x + 2
Все функции и константы всегда обозначаются словами с маленькой буквы без пробелов.
(Константы это просто функции с нулем аргументов.)
Pattern mathcing
fixBuz :: Int -> String
divide8 3 = "Fiz"
divide8 5 = "Buz"
divide8 15 = "FizBuz"
divide8 _ = "Some other number"
Так матчить можно произвольные структуры произвольного уровня вложенности.
_
– специальное название константы, которое говорит что вам все равно что в ней лежит.
функции объявляются в несколькро строк. Первая – обьявленик типа функции, а последубщие – реализация. В общем случае тип можно не указывать, но указывать тип у высказыванй на самом верхнем уровне (не вложенные) считается хорошим тоном и улучшает выведение типов и ошибки компиляции. У функции может быть несколько реализаций: какая из них вызовется зависит от значений передаваемых аргументов. матчинг произхводится сверху вниз. если в аргемантах написано слово с маленькой буквы, то значение аргумента биндится на эту константу.
data Foo = Bar
foo :: Bar
foo = Bar
Foo
– тип структуры. Bar
– конструктор структуры.
Тут у Foo
всего одно значение Bar
.
Произведение типов
(обычные поля структур)
После констрктора можно укзать существующие типы, которые в нем храняться.
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"
Еще немного функций
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 = Cons Int IntList | Nil
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
Наша самодеятельность
В стандартной библиотеке
repeat
, sum
и take
тоже есть в стандартной библиотеке.
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 filter f xs
else x: (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 ))
strings :: List String
strings = Cons "one" (Cons "two" (Cons "three" Nil ))
things = Cons ("one" :: String ) (Cons (2 :: Int ) Nil )
• Couldn't match type ‘Int’ with ‘String’
Expected type: List String
Actual type: List Int
|
10 | things = Cons ("one" :: String) (Cons (2 :: Int) 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".)