Когда и почему следует использовать аппликативные функторы в Scala
Я знаю, что Monad может быть выражено в Scala следующим образом:
trait Monad[F[_]] {
def flatMap[A, B](f: A => F[B]): F[A] => F[B]
}
Я вижу, почему это полезно. Например, даны две функции:
getUserById(userId: Int): Option[User] = ...
getPhone(user: User): Option[Phone] = ...
Я могу легко написать функцию getPhoneByUserId(userId: Int) С Option - это монада:
def getPhoneByUserId(userId: Int): Option[Phone] =
getUserById(userId).flatMap(user => getPhone(user))
...
теперь я вижу Applicative Functor в Scala:
trait Applicative[F[_]] {
def apply[A, B](f: F[A => B]): F[A] => F[B]
}
мне интересно, когда я должен использовать вместо монады . Я думаю, что оба варианта и список Applicatives. Не могли бы вы дать простой примеры использования apply с опцией и списком и объяснить почему я должен использовать вместоflatMap ?
2 ответов:
до процитирую себя::
так зачем вообще беспокоиться о прикладных функторах, когда у нас есть монады? Во-первых, это просто не возможно, чтобы предоставить экземпляры монады для некоторые абстракции, с которыми мы хотим работать -
Validation- это совершенный пример.во-вторых (и относительно), это просто твердая практика развития, чтобы использовать наименее мощная абстракция, которая выполнит эту работу. В принципе это может позволить оптимизации, которые иначе не были бы возможно, но, что более важно, это делает код более писать многократно используемый.
чтобы немного расширить первый абзац: иногда у вас нет выбора между монадическим и прикладным кодом. Смотрите остальное что ответ для обсуждения того, почему вы могли бы хотеть использовать Scalaz-это
Validation(который не имеет и не может иметь экземпляр монады) в модель утверждение.о точке оптимизации: это будет вероятно, будет некоторое время до того, как это вообще актуально в Scala или Scalaz, но см. например документации :
аппликативный стиль иногда может привести к более быстрому коду, как
binaryбудет пытаться оптимизировать код, группируя чтения вместе.написание прикладного кода позволяет избежать ненужных утверждений о зависимостях между вычислениями-утверждает, что подобный монадический код будет поручаю тебе. Достаточно умная библиотека или компилятор может в принципе воспользуйтесь этим фактом.
чтобы сделать эту идею немного более конкретной, рассмотрим следующий монадический код:
case class Foo(s: Symbol, n: Int) val maybeFoo = for { s <- maybeComputeS(whatever) n <- maybeComputeN(whatever) } yield Foo(s, n)The
for-понимание десугаров к чему-то более или менее похожему на следующее: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 в настоящее время делают работу с монадами намного более удобной (синтаксически и т. д.) чем работа с аппликативными функторами, но это в основном вопрос исторической случайности, и такие события, как идиома скобки - это шаг в правильном направлении.
функтор предназначен для подъема вычислений в категорию.
trait Functor[C[_]] { def map[A, B](f : A => B): C[A] => C[B] }и он прекрасно работает для функции одной переменной.
val f = (x : Int) => x + 1но для функции 2 и более, после подъема в категорию, мы имеем следующую подпись:
val g = (x: Int) => (y: Int) => x + y Option(5) map g // Option[Int => Int]и это подпись аппликативного функтора. И применить следующее значение к функции
g- необходим апликативный функтор.trait Applicative[F[_]] { def apply[A, B](f: F[A => B]): F[A] => F[B] }и наконец:
(Applicative[Option] apply (Functor[Option] map g)(Option(5)))(Option(10))аппликативный функтор-это функтор для применения специального значения (значения в категории) к поднятой функции.
Comments