Функциональное программирование
Структуры
data CircleType = Circle Double Double Double
data RectangleType = Rectangle Double Double Double Double
data Shape =
CircleShape CircleType | RectangleShape RectangleType
shape = CircleShape (Circle 0 0 2)
otherShape = RectangleShape (Rectangle 1 2 3 4)
Структура “человек”
Имя, Фамилия, Отчество, Адрес, Почта, Возраст
data Person =
Person String String String String String Natural
![]()
Функции!
data Person =
Person String String String String String Natural
firstName :: Person -> String
firstName (Person name _ _ _ _ _) = name
setFirstName :: String -> Person -> Person
setFirstName
newName
(Person _ lName mName address email age)
= Person newName lName mName address email age
lastName :: Person -> String
lastName (Person _ lName _ _ _ _) = lName
setLastName :: String -> Person -> Person
setLastName
newLastName
(Person fName _ mName address email age)
= Person fName newLastName mName address email age
middleName :: Person -> String
middleName (Person _ _ mName _ _ _) = mName
setMiddleName :: String -> Person -> Person
setMiddleName
newMiddleName
(Person fName lName _ address email age)
= Person fName lName newMiddleName address email age
![]()
А что если структуры еще больше?
Record syntax
data Person
= Person
{ firstName :: String,
lastName :: String,
middleName :: String,
address :: String,
email :: String,
age :: Natural
}
-- Автоматически генерируются компилятором!
firstName :: Person -> String
lastName :: Person -> String
-- ...
setFirstName :: String -> Person -> String
setFirstName newName person = person {firstName = newName}
-- Делает копию, заменяя одно (или нескеолько) поле.
-- (То есть функции set... не нужны)
Полиморфизм – одну реализацию можно применять к разным типам.
map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]
Problem: сама функция с a
и b
делать ничего не может – она про них ничего не знает.
quicksort :: [Int] -> [Int]
Откуда sort
узнает что такое a
и как их сравнивать?
Самое близкое – интрефейс.
Описывает свойства, которыми должен обладать объект
class Eq a where
(==) :: a -> a -> Bool
a
относится к классу Eq
если существует такая функция (==) :: a -> a -> Bool
class Eq a where
(==) :: a -> a -> Bool
data PersonType = Person String Int
instance Eq PersonType where
(Person name1 age1) == (Person name2 age2) =
name1 == name2 && age1 == age2
instance – “удовлетворяет”, а не “экземпляр”.
Использование
notEqual :: a -> a -> Bool
notEqual :: Eq a => a -> a -> Bool
notEqual a b = not (a == b)
class Eq a => Ord a where
(<=) :: a -> a -> Bool
a
относится к классу Ord
если он относится к классу Ord
и существует такая функция (<=) :: a -> a -> Bool
class Eq a => Ord a where
(<=) :: a -> a -> Bool
data PersonType = Person String Int
instance Eq PersonType where
(Person name1 age1) == (Person name2 age2) =
name1 == name2 && age1 == age2
instance Ord PersonType where
(Person name1 age1) <= (Person name2 age2) =
name1 <= name2 && age1 <= age2
Использование
(>) :: Ord a => a -> a -> Bool
a > b = not (a <= b)
(>=) :: Ord a => a -> a -> Bool
a >= b = a > b || a == b
Тут мы можем использовать >
на типах a
потому что внутри функции уже есть определение Ord a
.
notEqual
в стандартной библиотеке определен как /=
.
max :: Ord a => a -> a -> a
max a b = if a > b then a else b
Из-за сильной системы типов гарантируется что функция вернет один из своих аргументов.
Case
data Maybe a = Just a | Nothing
isJust :: Maybe a -> Bool
isJust Nothing = False
isJust (Just _) = True
isJust x = case x of
Nothing -> False
Just _ -> True
Просто возможность делать pattern matching где угодно, а не только в аргументах функций.
maximum :: Ord a => [a] -> Maybe a
max :: Ord a => a -> a -> a
maximum :: Ord a => [a] -> Maybe a
maximum [] = Nothing
maximum (x : xs) = case maximum xs of
Nothing -> Just x
Just y -> Just (max x y)
Полиморфные инстансы
instance Eq a => Eq (Maybe a) where
(==) Nothing Nothing = True
(==) (Just a) (Just b) = a == b
_ == _ = False
instance Ord a => Ord (Maybe a) where
-- Потому что мы хотим чтобы Nothing был меньше всех
Nothing <= _ = True
(Just a) <= (Just b) = a <= b
(Just _) <= Nothing = False
Что нам это дает?
maximum :: Ord a => [a] -> Maybe a
maximum [] = Nothing
maximum (x : xs) = case maximum xs of
Nothing -> Just x
Just y -> Just (max x y)
maximum :: Ord a => [a] -> Maybe a
maximum [] = Nothing
maximum (x : xs) = max (Just x) (maximum xs)
Моноид
Моноид – полугруппа с нейтральным элементом
Полугруппа
class Semigroup s where
(<>) :: s -> s -> s
instance Semigroup [x] where
a <> b = a ++ b -- конкатенация списков
Моноид
class Semigroup s => Monoid s where
mempty :: s
instance Monoid [x] where
mempty = []
“Схлопывает” список моноидов.
fold :: Monoid m => [m] -> m
fold [] = mempty
fold (x : xs) = x <> fold xs
fold [[1, 3, 5], [42, 69]]
-- [1, 3, 5, 42, 69]
fold :: Monoid m => [m] -> m
fold [] = mempty
fold (x : xs) = x <> fold xs
fold [1, 2, 3]
-- Что должно произойти?
У целых чисел нет одного определения моноида.
Но мы можем определить свои типы!
data Sum = Sum { unSum :: Int }
instance Semigroup Sum where
(Sum a) <> (Sum b) = Sum (a + b)
instance Monoid Sum where
mempty = Sum 0
map :: (a -> b) -> [a] -> [b]
map (+ 1) [1, 2, 3]
-- [2, 3, 4]
(unSum . fold . map Sum) [1, 2, 3]
-- 6
Deriving
class Show a where
show :: a -> String
data Foo = Bar Int Int String
deriving Show
show (Bar 1 2 "hello")
-- Bar 1 2 "hello"
data Foo = Bar Int Int String
deriving (Show, Eq, Ord)
Bar 3 10 "hello" == Bar 3 10 "hello"
-- True
Bar 3 10 "hello" == Bar 3 10 "oh no"
-- False
Bar 3 10 "hello" < Bar 4 -1000 "oh no"
-- True
Синонимы типов
Тип можно обозвать как-то по-другому чтобы код было удобнее читать.
type Age = Int
type Name = String
data Person = Person Name Age
Буквально эквивалентно тому чтобы сделать find and replace по коду.
scream :: String -> String
scream = map toUpper
name :: Name
name = "Vasya"
-- This is fine.
scream name
-- "VASYA"
В функцию capitalize
можно передать константу типа Name
потому что String
и Name
это одно и то же.
data
– новый тип
type
– просто обозвали по-другому
fold :: Monoid m => [m] -> m
fold [] = mempty
fold (x : xs) = x <> fold xs
fold ["Hello", "World"]
-- "HelloWorld"
--
-- Потому что
-- type String = [Char]
TreeMap<String, Integer> m = new TreeMap();
m.put("one", 1);
m.put("two", 2);
m.put("three", 3);
class TreeMap<K,V> {
V get(K key) {
...
Неявный контекст ошибки ВЕЗДЕ.
class TreeMap<K,V> {
Pair<V, Boolean> get(K key) {
...
Pair<Integer, Boolean> result = m.get("one");
if (result.second()) {
print(m.first());
} else {
print("oh no");
}
// 1
Pair<Integer, Boolean> result = m.get("five");
if (result.second()) {
print(m.first());
} else {
print("oh no");
}
// oh no
class TreeMap<K,V> {
V get(K key) throws NoElementException {
...
try {
print(m.get("five"));
} catch (NoElementException e) {
print("oh no");
}
// oh no
class TreeMap<K,V> {
Optional<V> get(K key) {
...
m :: Map String Int
m = fromList [("one", 1), ("two", 2), ("three", 3)]
data Maybe a = Just a | Nothing
lookup :: k -> Map k v -> Maybe v
lookup "five" m
-- Nothing
case lookup "one" m of
Just n -> ...
Nothing -> ...
data Either a b = Left a | Right b
lookup :: k -> Map k v -> Either MapError v
makePayment :: Card -> Amount -> Either PaymentError Card
makePayment card amount =
if balance card >= amount
then Right (withdraw amount card)
else Left InsufficientFunds
-- Обновляет аккаунт пользователя новой информацией о карте
updateAccount :: Account -> Card -> Account
makePayment :: Card -> Amount -> Either PaymentError Card
card = creditCard account
newAcccount :: Either PaymentError Account
newAcccount =
updateAccount account (makePayment card (RUB 150))
newAcccount =
updateAccount account (makePayment card (RUB 150))
-- (Card -> Account) (Either PaymentError Card)
newAcccount =
case makePayment card (RUB 150) of
Left e -> Left e
Right c -> updateAccount account c
newAcccount =
updateAccount account (makePayment card (RUB 150))
-- (Card -> Account) (Either PaymentError Card)
newAcccount =
magic (updateAccount account) (makePayment card (RUB 150))
-- | (Card -> Account) (Either PaymentError Card)
-- |
-- (Card -> Account)
-- -> (Either PaymentError Card
-- -> Either PaymentError Account)
Lists!
map :: (a -> b) -> [a] -> [b]
map :: (a -> b) -> ([a] -> [b])
magic ::
(Card -> Account) ->
( (Either PaymentError) Card ->
(Either PaymentError) Account
)
type F = Either PaymentError
magic :: (Card -> Account) -> (F Card -> F Account)
magic :: (Card -> Account) -> (F Card -> F Account)
А мы можем обобщить F
?
fmap ::
Functor f =>
(Card -> Account) ->
(f Card -> f Account)
Functor!
![]()
![]()
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Functor [] where
fmap :: (a -> b) -> [a] -> [b]
fmap _ [] = []
fmap f (x : xs) = f x : fmap f xs
class Functor Maybe where
fmap :: (a -> b) -> Maybe a -> Maybe b
fmap _ Nothing = Nothing
fmap f (Just x) = Just (f x)
class Functor (Either e) where
fmap :: (a -> b) -> Either e a -> Either e b
fmap _ (Left e) = Left e
fmap f (Right x) = Right (f x)
updateAccount :: Account -> Card -> Account
makePayment :: Card -> Amount -> Either PaymentError Card
newAcccount :: Either PaymentError Account
newAcccount =
fmap (updateAccount account) (makePayment card (RUB 150))
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Functor (x ->) where
fmap :: (a -> b) -> (x -> a) -> (x -> b)
fmap (f :: a -> b) (g :: x -> a) = \(u :: x) -> f (g u)
fmap (f :: a -> b) (g :: x -> a) = f . g
Functor + Monoid
= Alternative!
class Monoid s where
(<>) :: s -> s -> s
mempty :: s
class Functor f => Alternative f where
(<|>) :: f a -> f a -> f a
empty :: f a
class Functor f => Alternative f where
(<|>) :: f a -> f a -> f a
empty :: f a
instance Alternative Maybe where
(Just x) <|> _ = Just x
Nothing <|> y = y
empty = Nothing
Just 42 <|> Just 69
-- Just 42
Nothing <|> Just 69
-- Just 69
instance Monoid e => Alternative (Either e) where
(Right x) <|> _ = Right x
_ <|> y = y
empty = Left mempty
Left "oh no" <|> Right 8
-- Right 8
Right 42 <|> Right 8
-- Right 42
makePayment :: Card -> Amount -> Either PaymentError Card
makePayment card1 (Rub 150) <|> makePayment card2 (Rub 150)
instance Alternative [] where
xs <|> ys = xs ++ ys
empty = []
class Applicative f => Alternative f where ...
class Functor f => Applicative f where
pure :: a -> f a
...
mult :: Alternative f => Int -> f Int
mult n = mult' n 2
where
mult' n m =
if n <= m
then empty
else
if n `mod` m == 0
then pure m <|> mult (n `div` m)
else mult' n (m + 1)
mult 10 :: [Int]
-- [2, 5]
mult 10 :: Maybe Int
-- Just 2
mult 23 :: Maybe Int
-- Nothing
import Control.Applicative