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

April 20, 2017
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

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

    изданий,
 сервисов и проектов разработчиков человек в хорошей компании
  2. Контакты В группе компаний Rambler&Co всегда есть открытые вакансии для

    тех, кто хочет профессионально расти и развиваться, занимаясь тем, что по-настоящему нравится [email protected] www.rambler-co.ru/jobs
  3. Дисклеймеры Этот доклад: … сложный.
 (Лучше пойти выпить кофе) …

    содержит всякие стрелки, морфизмы, формализмы, неологизмы. 
 (Требует знания простой математики.) … про прагматику, а не про математику.
 (Не будет чтений курсов, учебников, научных статей под кумбая.) … в роли докладчика мизантроп, сноб, и зануда.
 (Лучше пойти выпить кофе.) 7
  4. Эксплика́ция (лат. explicatio — объяснение, развёртывание) — метод развёртывания (раскрытия)

    сущности того или иного предмета (явления) через некоторое многообразие иных предметов и явлений. 8
  5. Да ладно, вали всё, мы тут разберёмся! 10 Математика Дискретная

    математика Теория множеств Теория групп Теория типов Логика и булевая алгебра Комбинаторная логика Лямбда исчисление Алгебра Типизированное лямбда исчисление
  6. Языки 12 Большая часть примеров в докладе приводится на трех

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

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

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

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

    и двух операций, 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
  11. Тип переменной Тип значения переменной это множество значений, которое может

    принимать переменная 19 Haskell Scala Python let x = 42::Int val x: Int = 42 x = 42 # type: int
  12. Функция Функция с областью определения 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
  13. Тип функции Тип функции определяется типом возвращаемого значения и списком

    типов её формальных параметров. 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 В мат. смысле - областью определения и областью значений
 В “естественном” смысле - тип функции это множество всех функций, преобразующих значения каких либо типов в значения других или тех же типов
  14. Функция 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 * Функции, которые принимают в качестве значения аргумента другие функции называются функции высшего порядка
  15. Рекурсия типов Типы могут быть заданы рекурсивно, то есть одним

    из подтипов в определении типа может быть сам определяемый тип. 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
  16. Обобщенное программирование Обобщённое программирование (англ. 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
  17. 25 кадр Параметрический полиморфизм в языках программирования и теории типов

    представляет собой свойство семантики системы типов, позволяющее обрабатывать значения разных типов идентичным образом, то есть исполнять физически один и тот же код для данных разных типов. Параметрический полиморфизм является истинной формой полиморфизма. 25
  18. Рекурсия, список списков Рекурсивно можно задать и список списков (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
  19. Функция flatten (домашнее задание) Введем функцию, которая преобразует список списков

    в простой список чисел: 
 [[1], 2, [[3, 4], 5], [[[]]], [[[6]]], 7, 8, []] -> [1, 2, 3, 4, 5, 6, 7, 8] Её тип - List[List[T]] → List[T] 27
  20. Функция 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']
  21. Функция 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
  22. Монада - это контейнер 31 class Some: def __init__(self, value):

    self.value = value Она описывается полиморфным контейнерным типом для выполнений с одним параметром.
  23. Монада - это контейнер Она описывается полиморфным контейнерным типом для

    выполнений с одним параметром. 32 T = TypeVar('T') class Some(Generic[T]): def __init__(self, value: T): self.value = value
  24. Монада - это контейнер унарная операция (функция одного аргумента) 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
  25. Функция связывания бинарная операция (функция двух аргументов) bind принимает в

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

    аргументов) 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]
  27. Святые макароны, да вот же она! 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
  28. Проверяем 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
  29. Выводы №1 38 Монада обязана обладать двумя функциями (методами): конструктор,

    который создает экземпляр функция или метод связывания, которая принимает другую функцию для совершения операций над значением
  30. Законы Нейтральный элемент бинарной операции — элемент, который оставляет любой

    другой элемент неизменным при применении этой бинарной операции к этим двум элементам. Например, для множества вещественных чисел и бинарной операции + (сложение): Left-identity law (X + 0 = 0 + X) Right-identity law (0 + X = X + 0) Плюс закон ассоциативности (также, для бинарной операции сложения вещественных чисел): Associative law ((A + B) +C = A + (B + C)) 40
  31. Законы 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
  32. Поиграем членами 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)
  33. Поиграем членами 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
  34. Поиграем членами 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)
  35. Выводы номер 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))
  36. Триллер про залетевшего дятла, который ломает цивилизацию часть 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))
  37. Триллер про залетевшего дятла, который ломает цивилизацию часть 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))
  38. Триллер про залетевшего дятла, который ломает цивилизацию часть 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'. """
  39. -//- часть 2 (статические языки в Non Unified Type system)

    50 private Function<Integer, Optional<Integer>> 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! };
  40. -//- часть 2 (статические языки в Non Unified Type system)

    51 private Function<Integer, Integer> f = x -> (x % 2 == 0) ? null : x; private Function<Integer, String > g = y -> y == null ? "no value" : y.toString(); public void test2() { // A value that f maps to null - this breaks .map Optional<Integer> opt = Optional.of(2); // Optional.empty opt.map(f).map(g); // "no value" opt.map(f.andThen(g)); // Optional not respect Associative Law! }
  41. -//- часть 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
  42. -//- часть 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>
  43. -//- часть 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
  44. -//- часть 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
  45. Выводы №3 56 В неестественных средах обитания у монад есть

    враги Спецификация языка Runtime STD Lib Ваш собственный код
  46. Список часто употребимых монад 57 List Maybe (Option) Either Except

    (Error) IO Reader/Writer State Parser Future (Task, Promise) Effect
  47. Общие выводы Монады в прагматичном понимании - это не про

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