Каковы последствия рекурсивного объединения Обещаний с точки зрения монад?
Я знаю, что обещания Javascript не являются технически ни функторами, ни монадами в смысле Хаскелла, потому что (среди прочего)
- они включают в себя
bind
операция, которая возвращается кmap
когда передается чистая функция (и, следовательно, имеет неоднозначный тип) - оба
Promise
конструктор иresolve
(акаreturn
) присоединиться к вложенным обещаниям рекурсивно
Первая проблема может быть легко обойдена, всегда предоставляя функцию с правильным типом a -> Promise b
,
Вторая проблема явно нарушает параметрическую черту параметрических полиморфных функций, то есть нельзя построить m (m a)
состав. Но что эта структура будет означать в контексте обещаний / асинхронных вычислений? Я не могу придумать смысловой семантики для Promise (Promise a)
, где Promise
это монада Так что мы теряем? Каковы последствия рекурсивного объединения?
При условии, что мы довольно прагматичны (и это то, чем мы должны быть, когда программируем Javascript), мы не можем утверждать, что Promise
такое монада в Javascript, если мы позаботимся о крайних случаях?
1 ответ
Первая проблема может быть легко обойдена, всегда предоставляя функцию с правильным типом
a -> Promise a
,
Или не используя then
как bind
работа монады, но некоторые из них правильные по типу. Creed - это функционально-ориентированная библиотека обещаний, которая обеспечивает map
а также chain
методы, которые реализуют спецификацию Fantasy-land для алгебраических типов.
Вторая проблема может быть обойдена также с тем же подходом, не используя resolve
но fulfill
вместо этого и статический of
метод как единичная функция.
Но что эта структура будет означать в контексте обещаний / асинхронных вычислений?
Это обещание для обещания ценности. Не каждый конструируемый тип должен быть "значимым" или "полезным":-)
Тем не менее, хороший пример аналогичного типа предоставлен Fetch API: он возвращает обещание, которое разрешается в Response
объект, который снова "содержит" обещание, которое разрешается в теле ответа.
Так что Promise (Promise a)
может иметь только одно значение результата успеха, к которому можно также получить доступ через Promise a
Однако два уровня обещаний
- может выполнить в разное время, добавив "средний шаг"
- может отклоняться по разным причинам - например, внешняя представляет проблему сети, а внутренняя представляет проблему синтаксического анализа
Обратите внимание, что Promise
тип должен иметь переменную второго типа по причине отклонения, аналогично Either
, Двухуровневый Promise err1 (Promise err2 a)
довольно сильно отличается от Promise err a
,
Я знаю, что обещания Javascript'а технически не являются ни функторами, ни монадами в смысле Хаскелла.
Вы еще не упомянули самую большую проблему: они изменчивы. Переход от ожидающего к установленному состоянию является побочным эффектом, который разрушает ссылочную прозрачность, если мы рассмотрим порядок выполнения, и, конечно, наши обычные сценарии использования для обещаний включают много операций ввода-вывода, которые вообще не моделируются типом обещания.
Promise.delay(50).then(() => Promise.delay(50))
// does something different than
const a = Promise.delay(50); a.then(() => a)
Применение законов монады забавно и иногда полезно, но нам действительно нужно много прагматизма.
Я знаю, что обещания Javascript'а технически не являются ни функторами, ни монадами в смысле Хаскелла
Не только в смысле Хаскелла, но и в любом другом.
- они включают в себя операцию связывания, которая возвращается к карте при передаче чистой функции (и, следовательно, имеет неоднозначный тип)
здесь нет bind
Оператор предоставлен родными обещаниями JS
- и конструктор Promise, и resol (или возвращение) рекурсивно объединяют вложенные обещания
Я предполагаю, что вы имеете в виду распаковку "theneables", то есть вызов функций, хранящихся в then
опора, когда есть такая функция.
Первая проблема может быть легко обойдена, всегда предоставляя функцию с правильным типом
a -> Promise b
,
Это не будет напоминать map
например, когда map(f)
используется для f = x => {then: a => a}
,
Вторая проблема явно нарушает параметрическую черту параметрических полиморфных функций, то есть нельзя построить
m (m a)
состав.
В самом деле.
Но что эта структура будет означать в контексте обещаний / асинхронных вычислений? Я не могу придумать смысловой семантики для
Promise (Promise a)
, гдеPromise
это монада Так что мы теряем? Каковы последствия рекурсивного объединения?
Вам нужно разрешить хранение произвольных значений. Обещаниям не разрешается хранить необходимые вещи (без распаковки), что является проблемой. Поэтому вам нужно изменить семантику как объектов, так и методов. Разрешить объектам хранить необходимые вещи без изменений и внедрять .bind
ака .chain
это разворачивает (или объединяет) theneables точно один раз - без рекурсии.
Это то, что creed
делает для обещающих объектов и cpsfy
для функций, основанных на обратном вызове (стиль продолжения передачи).
Если мы довольно прагматичны (и это то, чем мы должны быть, когда программируем Javascript), разве мы не можем утверждать, что Promise - это монада в Javascript, если мы позаботимся о крайних случаях?
Написание безопасного, сжатого и составного кода прагматично. Рискуя вносить тонкие ошибки через неплотные абстракции, которые могут привести к сбою критического программного обеспечения с далеко идущими последствиями, нет. Каждый крайний случай является потенциальным источником такого риска.
В этом отношении, утверждая, что Promise
Монада приносит больше вреда, чем помощи, кроме того, что она неверна. Это приносит вред, потому что вы не можете безопасно применять монадические преобразования к обещаниям. Например, небезопасно использовать любой код, соответствующий монадическому интерфейсу, с обещаниями, как если бы они были монадами. При правильном использовании монады помогают абстрагировать и повторно использовать ваш код, а не вводить строки проверки и поиска крайних случаев.