Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Монады. Экспликация

Монады. Экспликация

Павел Петлинский (Rambler&Co) @ Moscow Python Meetup 44

"В докладе мы разберемся, что за зверь такой эта "Монада", и где прекрасный чистый мир математики ломается об особенности языков программирования".

Видео: http://www.moscowpython.ru/meetup/44/monady-eksplikacija/

Moscow Python Meetup
PRO

April 20, 2017
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. View Slide

  2. 40 50+ 700 1700+
    млн человек
    суммарная аудитория группы
    количество
    изданий,

    сервисов и
    проектов
    разработчиков человек в хорошей
    компании

    View Slide

  3. View Slide

  4. Языки и технологии

    View Slide

  5. Контакты
    В группе компаний Rambler&Co
    всегда есть открытые вакансии для
    тех, кто хочет профессионально
    расти и развиваться, занимаясь тем,
    что по-настоящему нравится
    [email protected]
    www.rambler-co.ru/jobs

    View Slide

  6. Монады.
    Экспликация.

    View Slide

  7. Дисклеймеры
    Этот доклад:
    … сложный.

    (Лучше пойти выпить кофе)
    … содержит всякие стрелки, морфизмы, формализмы, неологизмы. 

    (Требует знания простой математики.)
    … про прагматику, а не про математику.

    (Не будет чтений курсов, учебников, научных статей под кумбая.)
    … в роли докладчика мизантроп, сноб, и зануда.

    (Лучше пойти выпить кофе.)
    7

    View Slide

  8. Эксплика́ция (лат. explicatio — объяснение,
    развёртывание) — метод развёртывания (раскрытия)
    сущности того или иного предмета (явления) через
    некоторое многообразие иных предметов и явлений.
    8

    View Slide

  9. Да ладно, вали всё, мы тут разберёмся!
    9

    View Slide

  10. Да ладно, вали всё, мы тут разберёмся!
    10
    Математика
    Дискретная математика
    Теория множеств Теория групп Теория типов
    Логика и булевая алгебра
    Комбинаторная логика
    Лямбда исчисление
    Алгебра
    Типизированное лямбда исчисление

    View Slide

  11. Формализмы
    Формализмы и всякая заумь написаны таким шрифтом в
    такой рамочке (кроме этого).
    11

    View Slide

  12. Языки
    12
    Большая часть примеров в докладе приводится на трех
    языках:
    Haskell - как “естественная” среда обитания предмета
    рассказа,
    Python - как язык, знакомый многим слушателям,
    Scala - язык, в котором есть сущности из обоих миров
    (ООП и функционального программирования).

    View Slide

  13. Быстрый ответ #1
    Монада (в теории категорий) в категории C - это эндофунктор T: C → C с
    двумя естественными преобразованиями:
    η: 1 → T
    μ: T ° T → T.
    Монада может быть определена через общее понятие моноида в
    моноидальной категории. Монада над моноидальной категорией (C, ⊗, I) - это
    моноид в моноидальной категории эндофункторов End(С).
    13

    View Slide

  14. Быстрый ответ #2
    Эти естественные преобразования требуют исполнения
    следующих требований
    https://en.wikipedia.org/wiki/Monad_(category_theory)
    14

    View Slide

  15. Единственный слайд с котэ

    View Slide

  16. Менее заумное определение
    Монада (в функциональном программировании) - это
    абстракция линейной цепочки связанных вычислений. Её
    основное назначение — инкапсуляция функций с побочным
    эффектом от чистых функций, а точнее их выполнений от
    вычислений. Она описывается полиморфным контейнерным
    типом для выполнений с одним параметром, стратегией
    «поднятия» значения в монаду и стратегией связывания двух
    вычислений, второе из которых зависит от значения,
    вычисляемого первым.
    16

    View Slide

  17. Менее заумное определение
    Монада (в функциональном программировании) - это
    абстракция линейной цепочки связанных вычислений. Её
    основное назначение — инкапсуляция функций с побочным
    эффектом от чистых функций, а точнее их выполнений от
    вычислений. Она описывается полиморфным контейнерным
    типом для выполнений с одним параметром, стратегией
    «поднятия» значения в монаду и стратегией связывания двух
    вычислений, второе из которых зависит от значения,
    вычисляемого первым.
    17

    View Slide

  18. Монады. Традиционный бабушкин рецепт.
    Монада создается определением конструктора типа М и двух операций,
    bind и return (return также часто называют unit)
    унарная операция (функция одного аргумента) return описывает
    «возвращение» (втягивание) типа a в монаду m, то есть обрамление его
    контейнером, и возвращает монадическое значение M a
    бинарная операция (функция двух аргументов) bind принимает в качестве
    аргумента монадическое значение M a и функцию (a → M b) , которая
    может трансформировать значение. bind разворачивает значение a,
    обрамленное вложенным во входной параметр M a и передает его в
    функцию (a → M b). Функция создает новое монадическое значение М a,
    которое может быть передано в следующую функцию bind в конвейере
    18

    View Slide

  19. Тип переменной
    Тип значения переменной это множество значений, которое
    может принимать переменная
    19
    Haskell Scala Python
    let x = 42::Int val x: Int = 42 x = 42 # type: int

    View Slide

  20. Функция
    Функция с областью определения X и областью значений Y обозначается:
    https://en.wikipedia.org/wiki/Function_(mathematics)
    f: X -> Y , где X - это область определения функции, а Y - множество значений функции
    f(x) = y, где x принадлежит Х, y принадлежит Y
    square_sum(x, y) = x ^ 2 + y ^ 2
    Если стереть имена у функций, то можно представить функции как “анонимные” или
    лямбда-функции
    (x , y) -> x ^ 2 + y ^ 2
    https://en.wikipedia.org/wiki/Lambda_calculus
    20

    View Slide

  21. Тип функции
    Тип функции определяется типом возвращаемого значения и
    списком типов её формальных параметров.
    21
    Haskell Scala Python
    inc :: Integer -> Integer
    inc x = x + 1
    dec :: Integer -> Integer
    dec x = x - 1
    def inc(x: Int): Int = x + 1
    def dec(x: Int): Int = x - 1
    def inc(x: int) -> int:
    return x+1
    def dec(x: int) -> int:
    return x-1
    В мат. смысле - областью определения и областью значений

    В “естественном” смысле - тип функции это множество всех функций, преобразующих значения
    каких либо типов в значения других или тех же типов

    View Slide

  22. Функция map/apply
    Введем функцию map (или apply), которая принимает два аргумента, где первый аргумент функция типа
    Int -> Int, а второй аргумент - значение типа Int.
    Функция map применяет функция в из первого аргумента к значению второго аргумента и возвращает
    результат применения
    22
    Haskell Scala Python
    apply :: (Integer -> Integer)
    -> Integer -> Integer
    apply f v = f(v)
    def apply(f: Int => Int,
    v: Int): Int = f(v)
    scala> apply(inc, 5)
    6
    scala> apply(dec, 5)
    4
    def apply(f: Callable[[int], int],
    v: int) -> int:
    return f(v)
    >>> apply(inc, 5)
    6
    >>> apply(dec, 5)
    4
    * Функции, которые принимают в качестве значения аргумента другие функции
    называются функции высшего порядка

    View Slide

  23. Рекурсия типов
    Типы могут быть заданы рекурсивно, то есть одним из подтипов
    в определении типа может быть сам определяемый тип.
    23
    Haskell Scala Python
    data IntList = Empty | Cons Integer
    (IntList)
    sealed trait IntList
    case class Empty() extends IntList
    case class NotEmptyList(head: Int, tail:
    IntList) extends IntList
    class IntList:
    pass
    class EmptyList(IntList):
    pass
    class NotEmptyList(IntList):
    empty = EmptyList()
    def __init__(self,
    head: int,
    tail: IntList=empty):
    self.head = head
    self.tail = tail

    View Slide

  24. Обобщенное программирование
    Обобщённое программирование (англ. generic programming) — парадигма программирования,
    заключающаяся в таком описании данных и алгоритмов, которое можно применять к
    различным типам данных, не меняя само это описание.
    24
    Haskell Scala Python
    data List a = Empty |
    Cons a (List a)
    sealed trait UniversalList[T]
    case class Empty[T]()
    extends UniversalList[T]
    case class NotEmptyList[T]
    (head: Int,
    tail: UniversalList[T])
    extends UniversalList[T]
    T = TypeVar('T')
    class UniversalList(Generic[T]):
    pass
    class EmptyList(UniversalList[T]):
    pass
    class NotEmptyList(UniversalList[T]):
    empty = EmptyList()
    def __init__(self, head: T, tail:
    UniversalList=empty):
    self.head = head
    self.tail = tail

    View Slide

  25. 25 кадр
    Параметрический полиморфизм в языках программирования и
    теории типов представляет собой свойство семантики
    системы типов, позволяющее обрабатывать значения
    разных типов идентичным образом, то есть исполнять
    физически один и тот же код для данных разных типов.
    Параметрический полиморфизм является истинной формой
    полиморфизма.
    25

    View Slide

  26. Рекурсия, список списков
    Рекурсивно можно задать и список списков (List[List[T]]).
    26
    Haskell Scala Python
    data NList a = Empty |
    ConsElem a (NList a) |
    ConsList (NList a) (NList a)
    sealed trait UniversalList[T]
    case class Empty[T]() extends
    UniversalList[T]
    case class NotEmptyList[T](
    head: Int,
    tail: UniversalList[T])
    extends UniversalList[T]
    case class NotEmptyNestedList[T](
    head: UniversalList[T],
    tail: UniversalList[T])
    extends UniversalList[T]
    T = TypeVar('T')
    class UniversalList(Generic[T]):
    pass
    class EmptyList(UniversalList[T]):
    pass
    class NotEmptyList(UniversalList[T]):
    empty = EmptyList()
    def __init__(self, head: T,
    tail: UniversalList=empty):
    self.head = head
    self.tail = tail
    class NotEmptyNestedList(UniversalList[T]):
    empty = EmptyList()
    def __init__(self, head: UniversalList,
    tail: UniversalList=empty):
    self.head = head
    self.tail = tail

    View Slide

  27. Функция flatten (домашнее задание)
    Введем функцию, которая преобразует список списков в
    простой список чисел: 

    [[1], 2, [[3, 4], 5], [[[]]], [[[6]]], 7, 8, []] -> [1, 2, 3, 4, 5, 6, 7, 8]
    Её тип - List[List[T]] → List[T]
    27

    View Slide

  28. Функция flatMap (совмещаем map и flatten)
    28
    Для списка [1, 2, 3, 4], если применить лямбду (тип int -> List[str]):
    lambda x: [str(x-1), str(x), str(x+1)]
    На шаге map получим:
    [['0', '1', '2'], ['1', '2', '3'], ['2', '3', '4'], ['3', '4', ‘5’]]
    На шаге flatten:
    ['0', '1', '2', '1', '2', '3', '2', '3', '4', '3', '4', '5']

    View Slide

  29. Функция flatMap круче, чем map
    Выражаем map через flatMap и конструктор
    29
    Scala
    val x: Int = 42
    val v: List[Int] = List(1, 2, 3, 4, 5)
    val left: List[Int] = v.map(inc)
    val right: List[Int] = v.flatMap(x => List(inc(x)))
    // List[Int] on start,
    // List[List[Int]] after map,
    // List[Int] after flatten
    left == right

    View Slide

  30. Собираем всё вместе согласно приложенной схеме

    View Slide

  31. Монада - это контейнер
    31
    class Some:
    def __init__(self, value):
    self.value = value
    Она описывается полиморфным контейнерным типом для
    выполнений с одним параметром.

    View Slide

  32. Монада - это контейнер
    Она описывается полиморфным контейнерным типом для
    выполнений с одним параметром.
    32
    T = TypeVar('T')
    class Some(Generic[T]):
    def __init__(self, value: T):
    self.value = value

    View Slide

  33. Монада - это контейнер
    унарная операция (функция одного аргумента) return описывает «возвращение» (втягивание)
    типа a в монаду m, то есть обрамление его контейнером, и возвращает монадическое
    значение M a
    33
    T = TypeVar('T')
    class Optional(Generic[T]):
    def __init__(self):
    raise NotImplementedError()
    class Some(Optional[T]):
    def __init__(self, value: T):
    self.isEmpty = False
    self.value = value
    super(Optional, self).__init__()
    class Nothing(Optional[T]):
    def __init__(self):
    self.isEmpty = True
    super(Optional, self).__init__()
    @property
    def value(self):
    return None

    View Slide

  34. Функция связывания
    бинарная операция (функция двух аргументов) bind принимает в качестве аргумента
    монадическое значение M a и функцию (a → M b) , которая может трансформировать значение.
    bind разворачивает значение a, обрамленное вложенным во входной параметр M a и передает его
    в функцию (a → M b). Функция создает новое монадическое значение М a, которое может быть
    передано в следующую функцию bind в конвейере
    34
    Тип функции связывания
    (>>=) :: m a -> (a -> m b) -> m b

    View Slide

  35. Святые макароны, да вот же она!
    бинарная операция (функция двух аргументов) bind принимает в качестве аргумента
    монадическое значение M a и функцию (a → M b) , которая может трансформировать
    значение. bind разворачивает значение a, обрамленное вложенным во входной параметр M a и
    передает его в функцию (a → M b). Функция создает новое монадическое значение М a,
    которое может быть передано в следующую функцию bind в конвейере
    35
    Тип функции связывания
    (>>=) :: m a -> (a -> m b) -> m b
    flatMap - для List[A] тип flatMap (A -> List[B]) -> List[B]

    View Slide

  36. Святые макароны, да вот же она!
    36
    . . .
    class Some(Option[T]):
    def __init__(self, value: T):
    self.isEmpty = False
    self.value = value
    super(Option, self).__init__()
    def flatMap(self, f: Callable[[T], Option[T]]) ->
    Option[T]:
    return f(self.value)
    class Nothing(Option[T]):
    def __init__(self):
    self.isEmpty = True
    super(Option, self).__init__()
    def flatMap(self, _: Callable[[T], Option[T]]) ->
    Option[T]:
    return Nothing()
    @property
    def value(self):
    return None

    View Slide

  37. Проверяем
    37
    someVal = Some(5) # type: Some[int]
    nothing = Nothing()
    result = someVal.flatMap(lambda x: Some(x+1))
    print("someVal :", someVal.value)
    print("result :", result.value)
    resultAnother = someVal.flatMap(lambda x: Some(x+1)).\
    flatMap(lambda x: Nothing()).\
    flatMap(lambda x: Some(x+10))
    print("resultWithError :", resultAnother.value)
    someVal : 5
    result : 6
    resultWithError : None

    View Slide

  38. Выводы №1
    38
    Монада обязана обладать двумя функциями (методами):
    конструктор, который создает экземпляр
    функция или метод связывания, которая принимает другую
    функцию для совершения операций над значением

    View Slide

  39. Законы

    View Slide

  40. Законы
    Нейтральный элемент бинарной операции — элемент, который
    оставляет любой другой элемент неизменным при применении
    этой бинарной операции к этим двум элементам. Например, для
    множества вещественных чисел и бинарной операции + (сложение):
    Left-identity law (X + 0 = 0 + X)
    Right-identity law (0 + X = X + 0)
    Плюс закон ассоциативности (также, для бинарной операции
    сложения вещественных чисел):
    Associative law ((A + B) +C = A + (B + C))
    40

    View Slide

  41. Законы
    Left-identity law:
    (X + 0 == 0 + X) <=> unit(x).flatMap(f) == f(x)
    Right-identity law:
    (0 + X == X + 0) <=> m.flatMap(unit) == unit(x)
    Associative law:
    ((A + B) +C == A + (B + C)) <=>
    m.flatMap(f).flatMap(g) == m.flatMap( \ x => f(x).flatMap(g))
    41

    View Slide

  42. Поиграем членами Right-identity law
    Left-identity law: unit(x).flatMap(f) == f(x)
    Right-identity law: m.flatMap(unit) == unit(x)
    42
    # If x is not less than 10, return 2x
    def f(x):
    if x < 10:
    return Nothing()
    else:
    return Some(x * 2)
    a = 20
    # Left identity law check
    test1_result = Some(a).flatMap(f) == f(a)
    # Right identity law check
    test2_result = Some(a).flatMap(Some) == Some(a)
    print(test1_result, test2_result)

    View Slide

  43. Поиграем членами Right-identity law
    Left-identity law: unit(x).flatMap(f) == f(x)
    Right-identity law: m.flatMap(unit) == unit(x)
    43
    class Some(Option[T]):
    . . .
    def __eq__(self, other):
    if isinstance(other, Nothing):
    return False
    elif self.value == other.value:
    return True
    else:
    return False
    class Nothing(Option[T]):
    . . .
    def __eq__(self, other):
    if isinstance(other, Nothing):
    return True
    else:
    return False

    View Slide

  44. Поиграем членами Associative law
    Associative law:
    m.flatMap(f).flatMap(g) == m.flatMap( \ x => f(x).flatMap(g))
    44
    # If x is not less than 10, return 2x
    def f(x):
    if x < 10:
    return Nothing()
    else:
    return Some(x * 2)
    # If x is greater than 50, return x + 1
    def g(x):
    if x > 50:
    return Some(x + 1)
    else:
    return Nothing()
    m = Some(30)
    test3_result = m.flatMap(f).flatMap(g) == m.flatMap(lambda x: f(x).flatMap(g))
    print(test3_result)

    View Slide

  45. Выводы номер 2
    45
    Поведение функций и объектов обязано следовать трем законам:
    Left-identity law: unit(x).flatMap(f) == f(x)
    Right-identity law: m.flatMap(unit) == unit(x)
    Associative law: m.flatMap(f).flatMap(g) == m.flatMap( \ x => f(x).flatMap(g))

    View Slide

  46. View Slide

  47. Триллер про залетевшего дятла, который ломает цивилизацию часть 1 (динамические языки)
    47
    def magic_division(x: int) -> int:
    return (x - 10) / (x - 20)
    def proxy_function(x: int) -> Some[int]:
    return Some(magic_division(x))
    m = Some(30)
    m.flatMap(lambda x: Some(x - 10)).\
    flatMap(proxy_function).\
    flatMap(lambda x: Some(x * 10))

    View Slide

  48. Триллер про залетевшего дятла, который ломает цивилизацию часть 1 (динамические языки)
    48
    def convert_to_string(x: int) -> str:
    return str(x)
    def proxy_function(x: int):
    return Some(convert_to_string(x))
    m = Some(30)
    m.flatMap(lambda x: Some(x - 10)).\
    flatMap(proxy_function).\
    flatMap(lambda x: Some(x * 10))

    View Slide

  49. Триллер про залетевшего дятла, который ломает цивилизацию часть 1 (динамические языки)
    49
    PyMonad
    class Maybe(Monad, Monoid):
    . . .
    def bind(self, function):
    """ Applies 'function' to a 'Just' value.
    'function' must accept a single argument and return a 'Maybe' type,
    either 'Just(something)' or 'Nothing'.
    """

    View Slide

  50. -//- часть 2 (статические языки в Non Unified Type system)
    50
    private Function> f = x -> {
    if (x == null) {
    x = -1;
    } else if (x == 2) {
    x = null;
    } else {
    x = x + 1;
    }
    return Optional.ofNullable(x);
    };
    public void test() {
    // true, Optional[2] === Optional[2]
    Optional.of(1).flatMap(f).equals(f.apply(1));
    // true, Optional.empty === Optional.empty
    Optional.of(1).flatMap(f).equals(f.apply(1));
    // false
    Optional.ofNullable((Integer) null).flatMap(f).equals(f.apply(null));
    // or
    // equal to Optional[-1]
    Optional.ofNullable((Integer) null).flatMap(f);
    //but
    // equal to Optional[-1]
    f.apply(null);
    // Optional not respect Left Identity law!
    };

    View Slide

  51. -//- часть 2 (статические языки в Non Unified Type system)
    51
    private Function f = x -> (x % 2 == 0) ? null : x;
    private Function g = y -> y == null ? "no value" : y.toString();
    public void test2() {
    // A value that f maps to null - this breaks .map
    Optional opt = Optional.of(2);
    // Optional.empty
    opt.map(f).map(g);
    // "no value"
    opt.map(f.andThen(g));
    // Optional not respect Associative Law!
    }

    View Slide

  52. -//- часть 3 (Scala привирает, Side-effects рушат мир)
    52
    def twoEffects: (Future[Unit], Future[Unit]) =
    ( Future { println("hello") },
    Future { println("hello") } )
    scala> twoEffects
    hello
    hello
    scala> twoEffects
    hello
    lazy val anEffect = Future { println("hello") }
    def twoEffects2: (Future[Unit], Future[Unit]) = (anEffect, anEffect)
    scala> twoEffects2
    hello
    scala> twoEffects2

    View Slide

  53. -//- часть 3 (Scala привирает, Side-effects рушат мир)
    53
    def unit[A](block: => A): Future[A] =
    Future(block)
    def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] =
    fa flatMap f
    lazy val effect = Future { println("hello") }
    Check Right identity law (m.flatMap(unit) == unit(x)) <=> :
    scala> effect
    hello
    scala> flatMap(effect) { unit(_) }
    scala>

    View Slide

  54. -//- часть 3 (Scala привирает, долбанный runtime)
    54
    def f(s: String): Try[Int] = Try { s.toInt }
    def g(i: Int): Try[Int] = Try { i * 2 }
    def unit[T](v: T): Try[T] = Success(v)
    val v = "bad"
    val m = Success(v)
    Check Left identity law:
    scala> unit(v).flatMap(f) == f(v)
    Boolean = false
    Check Associative law
    scala> m.flatMap(f).flatMap(g) == m.flatMap(n => f(n).flatMap(g))
    Boolean = false

    View Slide

  55. -//- часть 4 (а был ли мальчик?)
    55
    Actual Hask does not have sums, products,

    or an initial object, and () is not a terminal object. 

    The Monad identities fail for almost

    all instances of the Monad class.
    https://wiki.haskell.org/Hask

    View Slide

  56. Выводы №3
    56
    В неестественных средах обитания у монад есть враги
    Спецификация языка
    Runtime
    STD Lib
    Ваш собственный код

    View Slide

  57. Список часто употребимых монад
    57
    List
    Maybe (Option)
    Either
    Except (Error)
    IO
    Reader/Writer
    State
    Parser
    Future (Task, Promise)
    Effect

    View Slide

  58. Общие выводы
    Монады в прагматичном понимании - это не про
    математическую заумь, а про поведение (протокол).
    Чем слабее языковая среда (смесь спецификации языка,
    рантайма и стандартной библиотеки) предназначена для
    контроля и поддержания подобного поведения - тем легче
    ошпарить ноги.
    Внимательно понаблюдав за признаками и поведением,
    можно сказать, может ли обладать в общем и обладает ли в
    конкретном исполнении этот код свойствами и поведением
    монад.
    58

    View Slide

  59. Вопросы.

    View Slide