sc-lectures/2.md
2020-04-01 23:35:09 +03:00

17 KiB
Raw Blame History

theme author
superblack Ilya Kostyuchenko

Функциональное программирование

Структуры

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

{height=200px .fragment}


Функции!

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

{height=200px .fragment}

[А что если структуры еще больше?]{.fragment}


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... не нужны)

Абстракция


Полиморфизм -- одну реализацию можно применять к разным типам.


length :: [a] -> Int
map :: (a -> b) -> [a] -> [b]
filter :: (a -> Bool) -> [a] -> [a]

[Problem: сама функция с a и b делать ничего не может -- она про них ничего не знает.]{.fragment}


quicksort :: [Int] -> [Int]
quicksort :: [a] -> [a]

[Откуда sort узнает что такое a и как их сравнивать?]{.fragment}


Typeclass


Самое близкое -- интрефейс.

Описывает свойства, которыми должен обладать объект


class Eq a where
  (==) :: a -> a -> Bool

[a относится к классу Eq если существует такая функция (==) :: a -> a -> Bool]{.fragment}


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 -- "удовлетворяет", а не "экземпляр".]{ .fragment }


Использование

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]{.fragment}


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.]{.fragment}


notEqual в стандартной библиотеке определен как /=.


max :: Ord a => a -> a -> a
max a b = if a > b then a else b

[Из-за сильной системы типов гарантируется что функция вернет один из своих аргументов.]{.fragment}


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 где угодно, а не только в аргументах функций.]{ .fragment }


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 Ord (Maybe a)
instance Eq (Maybe a)
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]
-- Что должно произойти?

У целых чисел нет одного определения моноида.

[Но мы можем определить свои типы!]{.fragment}

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

Синонимы типов

[Тип можно обозвать как-то по-другому чтобы код было удобнее читать.]{.fragment}

type String = [Char]
type Age = Int
type Name = String
data Person = Person Name Age

[Буквально эквивалентно тому чтобы сделать find and replace по коду.]{.fragment}


scream :: String -> String
scream = map toUpper
type Name = String
name :: Name
name = "Vasya"

-- This is fine.
scream name
-- "VASYA"

[В функцию capitalize можно передать константу типа Name потому что String и Name это одно и то же.]{.fragment}


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);
m.get("one");
// 1
m.get("five");
// null
class TreeMap<K,V> {
  V	get(K key) {
    ...

[Неявный контекст ошибки ВЕЗДЕ.]{.fragment}


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 Optional<T> {
  ...
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 "one" m
-- Just 1
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?]{.fragment}

fmap ::
  Functor f =>
  (Card -> Account) ->
  (f Card -> f Account)

Functor!

{width=450 .fragment}

{width=450 .fragment}


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
  fmap = (.)

Functor + Monoid

[= Alternative!]

class Monoid s where
  (<>) :: s -> s -> s
  mempty :: s
foo :: f a -> f a -> f a
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 :: [Int]
-- []
mult 23 :: Maybe Int
-- Nothing

import Control.Applicative