Различия между Scala Functor и Monad - PullRequest
62 голосов
/ 11 декабря 2011

Может кто-нибудь объяснить разницу между Functor и Monad в контексте Scala?

Ответы [ 5 ]

48 голосов
/ 11 декабря 2011

Сама Scala действительно не особо выделяет термины Functor и Monad.Я полагаю, что использование map - это сторона функтора, использование flatMap - это сторона монады.

Для меня поиск и игра с scalaz до сих пор был лучшим способом получитьсмысл этих функциональных понятий в контексте scala (в сравнении с контекстом haskell).Два года назад, когда я запустил scala, код скалаза был для меня бессмысленным, а несколько месяцев назад я снова начал искать и понял, что это действительно чистая реализация этого особого стиля функционального программирования.

Например,реализация Monad показывает, что монада является остроконечным функтором , поскольку она расширяет черту Pointed (а также черту Applicative).Я приглашаю вас пойти посмотреть код.Он содержит ссылки в самом источнике, и по ним очень легко переходить.

Так что функторы более общие.Монады предоставляют дополнительные функции.Чтобы получить представление о том, что вы можете сделать, когда у вас есть функтор или монада, вы можете посмотреть на MA

Вы увидите вспомогательные методы, которые требуют неявногофунктор (в частности, аппликативные функторы), такой как sequence, и иногда методы, которым нужна полная монада, такая как replicateM.

28 голосов
/ 11 декабря 2011

Принимая scalaz в качестве контрольной точки, тип F[_] (то есть тип F, параметризованный каким-либо одним типом) является функтором, если в него можно поднять функцию. Что это значит:

class Function1W[A, B](self: A => B) { 
  def lift[F[_]: Functor]: F[A] => F[B]
}

То есть, если у меня есть функция A => B, функтор F[_], то теперь у меня есть функция F[A] => F[B]. Это действительно обратный взгляд на метод map в scala, который (без учета CanBuildFrom) в основном:

F[A] => (A => B) => F[B]

Если у меня есть список строк, функция от String до Int, тогда я, очевидно, могу создать список Ints. Это относится к Option, Stream и т. Д. Все они являются функторами

Что меня интересует в этом, так это то, что вы можете сразу же прийти к (неверному) выводу, что Функтор является «контейнером» A с. Это ненужное ограничение. Например, подумайте о функции X => A. Если у меня есть функция X => A и функция A => B, то ясно, что по составу у меня есть функция X => B. Но теперь посмотрите на это так:

type F[Y] = X => Y //F is fixed in X

(X => A) andThen (A => B) is   X => B

  F[A]            A => B       F[B]

Так что тип X => A для некоторого фиксированного X также является функтором. В scalaz функтор имеет следующую черту:

trait Functor[F[_]] { def fmap[A, B](fa: F[A], f: A => B): F[B] }

следовательно, Function1.lift описанный выше метод реализован

def lift[F[_]: Functor]: F[A] => F[B] 
  = (f: F[A]) => implicitly[Functor[F]].fmap(f, self)

Несколько экземпляров функторов:

implicit val OptionFunctor = new Functor[Option] {
  def fmap[A, B](fa: Option[A], f: A => B) = fa map f
}

implicit def Functor1Functor[X] = new Functor[({type l[a]=X => a})#l] {
  def fmap[A, B](fa: X => B, f: A => B) = f compose fa
}

В scalaz монада имеет такой вид:

trait Monad[M[_]] {
  def pure[A](a: A): M[A] //given a value, you can lift it into the monad
  def bind[A, B](ma: M[A], f: A => B): M[B]
}

Не совсем очевидно, какой может быть полезность этого. Оказывается, ответ «очень». Я обнаружил, что монады Даниэля Спивака - это не метафоры , они очень ясно объясняют, почему это так, а также материал Тони Морриса о конфигурации через монаду читателя , хороший практический пример того, что может означать написание вашей программы внутри монады .

20 голосов
/ 11 декабря 2011

Некоторое время назад я писал об этом: http://gabrielsw.blogspot.com/2011/08/functors-applicative-functors-and.html (хотя я не эксперт)

Первое, что нужно понять, это тип 'T [X]': это своего рода«контекст» (полезен для кодирования вещей в типах, и с их помощью вы «составляете» их). Но посмотрите другие ответы:)

Хорошо, теперь у вас есть свои типы внутри контекста, скажем, M [A] («Внутри» M), и у вас есть простая функция f: A => B ... вы не можете просто пойти дальше и применить ее, потому что функция ожидает A, и у вас есть M [A].Вам нужен какой-то способ «распаковать» содержимое M, применить функцию и «упаковать» ее снова.Если вы обладаете «глубокими» знаниями о внутренностях М, вы можете сделать это, если вы обобщите это в качестве черты, и в итоге получите

trait Functor[T[_]]{
  def fmap[A,B](f:A=>B)(ta:T[A]):T[B]
}

И это именно то, чем является функтор.Он превращает T [A] в T [B], применяя функцию f.

Монада - это мифическое существо с неуловимым пониманием и множеством метафор, но я понял, что довольно легко понять, как только вы получитеаппликативный функтор:

Функтор позволяет нам применять функции к вещам в контексте.Но что, если функции, которые мы хотим применить, уже находятся в контексте?(И довольно легко закончить в этой ситуации, если у вас есть функции, которые принимают более одного параметра).

Теперь нам нужно что-то вроде Functor, но он также принимает функции уже в контексте и применяет их к элементам вконтекст.И это то, что аппликативный функтор.Вот подпись:

trait Applicative[T[_]] extends Functor[T]{
  def pure[A](a:A):T[A]
  def <*>[A,B](tf:T[A=>B])(ta:T[A]):T[B]
}

Пока все хорошо.Теперь монады: что если теперь у вас есть функция, которая помещает вещи в контекст?Его подпись будет g: X => M [X] ... вы не можете использовать функтор, потому что он ожидает X => Y, поэтому мы закончим с M [M [X]], вы не можете использоватьаппликативный функтор, потому что ожидает функцию уже в контексте M [X => Y].

Поэтому мы используем монаду, которая принимает функцию X => M [X] и что-то уже в контексте M [A] и применяет функцию к тому, что находится внутри контекста, упаковывая результат только в один контекст.Подпись:

trait Monad[M[_]] extends Applicative[M]{
  def >>=[A,B](ma:M[A])(f:A=>M[B]):M[B]
}

Это может быть довольно абстрактно, но если вы подумаете о том, как работать с «Option», он покажет вам, как составлять функции X => Option [X]

РЕДАКТИРОВАТЬ: Забыли важную вещь, чтобы связать его: символ >> = называется bind и flatMap в Scala.(Также, как примечание, есть некоторые законы, которые функторы, аппликативы и монады должны соблюдать для правильной работы).

17 голосов
/ 11 декабря 2011

Лучшая статья, в которой подробно изложены эти два понятия: " Суть шаблона итератора " из Блог Эрика Торреборре .

Functor

trait Functor[F[_]] {
  def fmap[A, B](f: A => B): F[A] => F[B]
}
  • Один из способов интерпретации Functor состоит в том, чтобы описать его как вычисление значений типа A.
    Например:
    • List[A] - это вычисление, возвращающее несколько значений типа A (недетерминированное вычисление),
    • Option[A] для вычислений, которые вы можете иметь или не иметь,
    • Future[A] - это вычисление значения типа A, которое вы получите позже, и так далее.
  • Другой способ изобразить это как своего рода «контейнер» для значений типа A .

Это базовый слой, из которого вы определяете:

  • PointedFunctor (для создания значения типа F[A]) и
  • Applic (чтобы обеспечить метод applic, представляющий собой вычисленное значение внутри контейнера F (F[A => B]), для применения к значению F[A]), Applicative Functor (агрегация Applic и PointedFunctor).

Все три элемента используются для определения Monad.

0 голосов
/ 02 июля 2013

Я думаю, что этот отличный пост в блоге поможет вам сначала для monad. http://blog.enfranchisedmind.com/2007/08/a-monad-tutorial-for-ocaml/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...