Ответ 1
Так зачем вообще заниматься аппликативными функторами, когда у нас есть монады? Прежде всего, просто невозможно предоставить экземпляры monad для некоторых абстракций, с которыми мы хотим работать.
Validation
- прекрасный пример.Второй (и связанный с этим), это просто твердая практика развития, чтобы использовать наименее мощную абстракцию, которая выполнит свою работу. В принципе это может позволить оптимизацию, которая в противном случае была бы невозможна, но что более важно, это делает код, который мы пишем, многократно используется повторно.
Чтобы немного расширить первый абзац: иногда у вас нет выбора между монадическим и аппликативным кодом. См. Остальную часть этого ответа для обсуждения того, почему вы можете использовать Scalaz Validation
(который не имеет и не может иметь экземпляр монады) для проверки модели.
О точке оптимизации: это, вероятно, будет некоторое время, прежде чем это, как правило, будет актуально в Scala или Scalaz, но см., Например, документацию для Haskell Data.Binary
:
Аппликативный стиль иногда может приводить к более быстрому коду, поскольку
binary
будет пытаться оптимизировать код, объединяя чтения вместе.
Написание аппликативного кода позволяет избежать ненужных заявлений о зависимостях между утверждениями-вычислениями, которые вам поделили бы аналогичный монадический код. Достаточно умная библиотека или компилятор в принципе могли воспользоваться этим фактом.
Чтобы сделать эту идею немного более конкретной, рассмотрим следующий монадический код:
case class Foo(s: Symbol, n: Int)
val maybeFoo = for {
s <- maybeComputeS(whatever)
n <- maybeComputeN(whatever)
} yield Foo(s, n)
В for
-comprehension desugars к чему - то более или менее, как следующее:
val maybeFoo = maybeComputeS(whatever).flatMap(
s => maybeComputeN(whatever).map(n => Foo(s, n))
)
Мы знаем, что, возможно, maybeComputeN(whatever)
не зависит от s
(предполагая, что это хорошие методы, которые не меняют какое-то изменяемое состояние за кулисами), но компилятор не имеет - с его точки зрения он должен знать s
до он может начать вычислять n
.
Применимая версия (с использованием Scalaz) выглядит следующим образом:
val maybeFoo = (maybeComputeS(whatever) |@| maybeComputeN(whatever))(Foo(_, _))
Здесь мы явно заявляем, что нет никакой зависимости между двумя вычислениями.
(И да, синтаксис |@|
довольно ужасен - см. Это сообщение в блоге для обсуждения и альтернатив.)
Но последний момент действительно самый важный. Выбор наименее мощного инструмента, который поможет решить вашу проблему, - это чрезвычайно мощный принцип. Иногда вам действительно нужна монадическая композиция, например, в вашем методе getPhoneByUserId
, но часто вы этого не делаете.
Жаль, что и Haskell, и Scala в настоящее время работают с монадами гораздо удобнее (синтаксически и т.д.), Чем работа с аппликативными функторами, но это в основном вопрос исторической катастрофы, а такие события, как скобки идиомы, являются шагом в правильном направление.