От Callback Hell к моноидам на самофункторах: расшифровка знакомых и незнакомых монад

JavaScript функциональное программирование Promise

За многими общими темами в области переднего плана на самом деле стоят классические основы компьютерных наук. Сегодня, пока вы используете JS для инициирования сетевых запросов, вы в основном используете монады в функциональном программировании. так что случилось? Начнем с ада обратного вызова...

Callback Hell и обещания

Студенты, знакомые с JS, не будут незнакомы с функциями обратного вызова — это наиболее распространенный способ обработки асинхронных событий в этом языке. Однако, как мы знаем,Последовательная обработка нескольких асинхронных задачРабочий процесс может легко вызвать вложение обратных вызовов, заставляя код трудно поддерживать:

$.get(a, (b) => {
  $.get(b, (c) => {
    $.get(c, (d) => {
      console.log(d)
    })
  })
})

Эта проблема уже давно преследует большинство JS-пользователей, и сообщество предлагает множество решений. Одна из таких схем, ставшая стандартной, называетсяPromise, вы можете разместить асинхронный обратный вызовОбернутый обещаниями,Зависит отPromise.thenЦепочка методов работает асинхронно:

const getB = a =>
  new Promise((resolve, reject) => $.get(a, resolve))

const getC = b =>
  new Promise((resolve, reject) => $.get(b, resolve))

const getD = c =>
  new Promise((resolve, reject) => $.get(c, resolve))

getB(a)
  .then(getC)
  .then(getD)
  .then(console.log)

Хотя ES7 уже имеет более лаконичный синтаксис async/await, промисы имеют очень широкий спектр приложений. Например, новая стандартная выборка для сетевых запросов будет инкапсулировать возвращаемый контент в виде обещания, и самая популярная библиотека Ajax axios делает то же самое. Что касается почтенной базовой библиотеки jQuery, которая когда-то занимала 70% веб-страниц, то Promise поддерживался уже в версии 1.5. Это означает, что пока вы делаете сетевой запрос на внешнем интерфейсе, вы в основном имеете дело с промисами.И сам Promise является своего рода Монадой.

Тем не менее, введение PROMISE в основном связано с миграцией различных состояний и использованием API, что звучит так, как будто Монада, кажется, совершенно не в состоянии это сделать. Какова связь между этими двумя концепциями? Чтобы прояснить этот вопрос, мы должны понять хотя бычто такое монада.

Зима Три Спрайт и Монада

Многих студентов, которые изначально были заинтересованы в изучении функциональных языков, таких как Haskell, может шокировать известная поговорка — [Разве монада — это не просто моноид на самофункторах, что трудно понять]. Фактически, это предложение похоже на то, что сказал Бай Ученый.Донгма Сяосан, Сюэкай Бичи] Никакой разницы, но это правильный бред, слушать людей знающих или понимающих, людей не понимающих или не понимающих. Так что если дальше и познакомить вас с Монадой так, то будьте уверены, что убили его - кормить и прочее, кто сказал, что зимние три Спрайта в самый раз!

специальный объект.

Поскольку это объект, его черная магия также существует в двух местах: свойствах и методах. Ниже мы должны ответить на важный вопрос:У монад есть какие-то специальные свойства и методы, которые помогут нам избежать ада обратного вызова?

мы можем использоватьочень простойпсевдокод для прояснения вопроса. Если у нас есть четыре вещи A B C D, то на основе вложенности обратных вызовов можно написать простейшее функциональное выражение вида:

A(B(C(D)))

Видите кошмар вложенных обратных вызовов? Однако мы можем упростить эту сцену по нитке. Во-первых, давайте сведем проблему к наиболее распространенному вложению обратного вызова:

A(B)

на основедобавить средний слойа такжеИнверсия контроляКонцепция , нам нужно всего дюжина строк кода для реализации простого промежуточного объекта P, передачи A и B этому объекту по отдельности, чтобы разделить обратный вызов:

P(A).then(B)

Теперь А заворачивается нами, П этоконтейнерЭто прототип Promise! В моем блогеКонцепция обещания и реализация из исходного кодаВ статье объяснялся основной механизм такого разложения обратных вызовов, и соответствующая реализация кода здесь повторяться не будет.

Однако это решение работает только при вложении между двумя функциями A b. Пока вы пытаетесь реализовать эту версию P, вы обнаружите, что у нас нет этой способности:

P(A).then(B).then(C).then(D)

Также не эта способность:

P(P(P(A))).then(B)

Здесь в игру вступают Монады! Сначала даем ответ:Этот объект является простой версией Monad.PthenКонечно, этот API не является именем в ортодоксальной монаде, но для справки мы можем сначала взглянуть на одну из спецификаций Promise/A+.ключевые детали:

Каждый раз, когда мы разрешаем промис, нам нужно оценивать две ситуации:

  1. Если решаемый контент по-прежнему является обещанием (т.н.thenable),ТакРекурсивное решениеЭто Обещание.
  2. Если содержимое, которое необходимо разрешить, не является промисом, то в соответствии с конкретной ситуацией содержимого (например, объекты, функции, примитивные типы и т. д.) перейдите кfulfillилиrejectТекущее обещание.

Интуитивно эта деталь гарантирует, что следующие два вызова полностью эквивалентны:

// 1
Promise.resolve(1).then(console.log)

// 1
Promise.resolve(
  Promise.resolve(
    Promise.resolve(
      Promise.resolve(1)
    )
  )
).then(console.log)

Гнездо здесь выглядит знакомым? На самом деле это основная компетенция Monads in Promises: для P одеваться таккакой-то контентконтейнер, мы можемРекурсивно разберите контейнер слой за слоем и напрямую извлеките значение, содержащееся в самом внутреннем.只要实现了这个能力,通过一些技巧,我们就能够实现下面这个优雅的цепной вызовAPI:

Promise(A).then(B).then(C).then(D)

Это дает дополнительные преимущества: независимо от того, возвращает ли здесь функция BCD синхронно выполняемое значение или асинхронно разрешенный промис, мы можемточно так жеиметь дело с. Например, это синхронное дополнение:

const add = x => x + 1
Promise
  .resolve(0)
  .then(add)
  .then(add)
  .then(console.log)
// 2

и это слегка перевёрнутое асинхронное дополнение:

const add = x =>
  new Promise((resolve, reject) => setTimeout(() => resolve(x + 1), 1000))

Promise
  .resolve(0)
  .then(add)
  .then(add)
  .then(console.log)
// 2

Независимо от того, синхронные и асинхронные, то, как они называются, точно такое же, как и конечный результат!

  • простейшийP(A).then(B)реализация, егоP(A)Эквивалент монадыunitИнтерфейс, который можетЛюбое значение для контейнера упаковки Monad.
  • Для поддержки вложенных реализаций Promise егоthenЗа ним на самом деле в FPjoinконцепция,Когда в контейнере все еще есть контейнер, внутренний контейнер рекурсивно отсоединяется, и возвращается значение, содержащееся в нижнем слое.
  • Обещайте перезвонить цепь, на самом деле, это монадbindконцепция. Вы можете объединить кучу квартир.then(), передать различные функции, Promise может помочь вам сгладить разницу между синхронным и асинхронным, поместите эти функцииПрименить к значениям в контейнере по одному.

Возвращаясь к исходному вопросу в этом разделе,что такое монада? Пока объект имеет следующие два метода, мы можем считать его монадой:

  1. Возможность обернуть значение как контейнер- В ФП это называетсяunit.
  2. Возможность применить функцию к значению, содержащемуся в контейнере- Сложность здесь в том, что контейнеры могут быть вложены в контейнеры, поэтому применение функции к значению требует рекурсии. В ФП это называетсяbind(Это то же самое, что и в JSbindСовершенно два понятия, прошу не путать).

Как мы видели,Promise.resolve()bind. Следовательно, мы можем считать: Promise — это монада. На самом деле это не новый вывод, на Github уже давно есть доказательства с точки зрения кода, заинтересованные студенты могут пойтиПочувствуй это :-)

  1. расколотьA(B)дляP(A).then(B)форма.这其实就是 Monad 用来构建容器的unit.
  2. Может писать независимо от синхронного или асинхронногоP(A).then(B).then(C)..., который на самом деле находится в монадеbind.

Здесь мы сможем понять роль Monad из функциональности Promise и использовать концепцию Monad для объяснения Promise его дизайна 😉

Что такое моноид на самом функторе

К этому моменту, если вы понимаете Promise, вы уже должны понимать Monad. Однако как быть с моноидом на самом функторе в легенде о монадах? На самом деле, пока вы читаете это, вы уже виделиавтофунктора такжемоноид(Понимание здесь может быть неточным, право следует использовать для привлечения большего количества идей, надеюсь, Далао это поправит).

автофунктор

ФункторТак называемый Functor — это функция, которая может помещать в него значения и преобразовывать содержимое контейнера, передавая функции.контейнерPromise.resolve就相当于这样的映射,能把任意值装进 Promise 容器里。 а такжеавтофункторPromise(A).then()

моноид

Моноид, так называемый монадический, удовлетворяет двум условиям: тождественности и ассоциативности.

Единица: юаньэти два условия:

Сначала подействуем на единичный элементunit(a)Вверхf, результат иf(a)Последовательный:

const value = 6
const f = x => Promise.resolve(x + 6)

// 下面两个值相等
const left = Promise.resolve(value).then(f)
const right = f(value)

Во-вторых, воздействуя на неединичные элементыmВверхunit, результат все равноmсам:

const value = 6

// 下面两个值相等
const left = Promise.resolve(value)
const right = Promise.resolve(value).then(x=> Promise.resolve(x))

Что касаетсяассоциативностьЭто условие:(a • b) • cравныйa • (b • c):

const f = a => Promise.resolve(a * a)
const g = a => Promise.resolve(a - 6)

const m = Promise.resolve(7)

// 下面两个值相等
const left = m.then(f).then(g)
const right = m.then(x => f(x).then(g))

Приведенные выше короткие строки кода на самом деле являются доказательством [Promise is a Monad]. На данный момент мы можем обнаружить, что при написании обещаний с интерфейсом ежедневной стыковки все, что мы пишем, может быть обновлено до уровня монад функционального программирования, а затем объяснено с помощью абстрактной алгебры и теории категорий.Моментально ли сила увеличивается?XD

Суммировать

Все вышеперечисленные аргументы не касаются>>==Это содержимое Haskell, мы можем в полной мере использовать такой низкий порог языка JS Monad для представления того, что такое использование. В некоторой степени я согласен с мнением Ван Инь: порог функционального программирования искусственно превышает или миф, и, очевидно, фактическое развитие очень практично и легко понять, нужно использовать более сложную концепцию, чтобы понять формализацию определить и объяснить, я боюсь Это не способствует популярности отличных инструментов и идей.

BumpoverПри обещании нового понимания. Заинтересованные студенты приветствуют внимание OH XD