Когда и почему следует использовать аппликативные функторы в 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 ?

628   2  

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

    Ничего не найдено.