62 votes

Différences entre les foncteurs et les monades Scala

Quelqu'un peut-il expliquer les différences entre Functor et Monad dans le contexte de Scala ?

48voto

huynhjl Points 26045

Scala lui-même ne met pas vraiment l'accent sur le Functor y Monad à ce point. Je suppose qu'utiliser map est le côté foncteur, en utilisant flatMap est le côté Monad.

Pour moi, regarder et jouer avec scalaz a été jusqu'à présent le meilleur moyen de se faire une idée de ces concepts fonctionnels dans le contexte de Scala (par opposition au contexte de Haskell). Il y a deux ans, lorsque j'ai commencé à utiliser Scala, le code de Scalaz était du charabia pour moi, puis il y a quelques mois, j'ai recommencé à chercher et j'ai réalisé qu'il s'agissait vraiment d'une implémentation propre de ce style particulier de programmation fonctionnelle.

Par exemple, le Monad montre qu'une monade est un objet pointu foncteur car il étend le Pointed (ainsi que le trait Applicative trait). Je vous invite à aller voir le code. Il comporte des liens dans la source elle-même et il est très facile de suivre les liens.

Les foncteurs sont donc plus généraux. Les monades apportent des fonctionnalités supplémentaires. Pour avoir une idée de ce que l'on peut faire avec un foncteur ou une monade, vous pouvez consulter les documents suivants MA

Vous verrez des méthodes utilitaires qui ont besoin d'un foncteur implicite (en particulier des foncteurs applicatifs) telles que sequence et parfois des méthodes qui nécessitent une monade complète, par exemple replicateM .

28voto

oxbow_lakes Points 70013

En prenant scalaz comme point de référence, un type F[_] (c'est-à-dire un type F qui est paramétré par un type unique) est un foncteur si une fonction peut être levée en lui. Qu'est-ce que cela signifie ?

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

C'est-à-dire que si j'ai une fonction A => B un foncteur F[_] alors j'ai maintenant une fonction F[A] => F[B] . Il s'agit en fait de l'inverse de l'approche de l'approche de Scala. map qui (en ignorant la méthode CanBuildFrom ) est en fait :

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

Si j'ai une liste de chaînes de caractères, une fonction de chaîne de caractères à Int, alors je peux évidemment produire une liste d'Ints. Il en va de même pour Option, Stream, etc. Ce sont tous des foncteurs

Ce que je trouve intéressant à ce sujet, c'est que vous pourriez immédiatement sauter à la conclusion (incorrecte) qu'un foncteur est un "conteneur" de A s. Il s'agit d'une restriction inutile. Par exemple, pensez à une fonction X => A . Si j'ai une fonction X => A et une fonction A => B alors clairement, par composition, j'ai une fonction X => B . Mais maintenant, regardez-le de cette façon :

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

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

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

Donc le type X => A pour un certain X fixe est aussi un foncteur. Dans scalaz Le foncteur est conçu comme un trait, comme suit :

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

d'où le Function1.lift la méthode ci-dessus est mise en œuvre

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

Quelques exemples de foncteurs :

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
}

Sur scalaz une monade est conçue comme suit :

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]
}

L'utilité de cette mesure n'est pas particulièrement évidente. Il s'avère que la réponse est "très". J'ai trouvé l'article de Daniel Spiewak Les monades ne sont pas des métaphores extrêmement clair en décrivant pourquoi cela pourrait être le cas, ainsi que le travail de Tony Morris sur configuration via la monade de lecture un bon exemple pratique de ce que l'on peut entendre par écrire votre programme dans une monade .

20voto

GClaramunt Points 2090

Il y a quelque temps, j'ai écrit à ce sujet : http://gabrielsw.blogspot.com/2011/08/functors-applicative-functors-and.html (Je ne suis pas un expert, cependant)

La première chose à comprendre est le type ' T[X] ' : C'est une sorte de "contexte" (il est utile d'encoder les choses dans des types et avec cela vous les "composez") Mais voyez les autres réponses :)

Ok, maintenant vous avez vos types à l'intérieur d'un contexte, disons M[A] (A "à l'intérieur" de M), et vous avez une fonction simple f:A=>B ... vous ne pouvez pas simplement aller de l'avant et l'appliquer, parce que la fonction attend A et vous avez M[A]. Vous devez trouver un moyen de "déballer" le contenu de M, d'appliquer la fonction et de le "remballer" à nouveau. Si vous avez une connaissance "intime" des internes de M, vous pouvez le faire, si vous le généralisez dans un trait, vous vous retrouvez avec

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

Et c'est exactement ce qu'est un foncteur. Il transforme un T[A] en un T[B] en appliquant la fonction f.

Une monade est une créature mythique à la compréhension insaisissable et aux multiples métaphores, mais je l'ai trouvée assez facile à comprendre une fois que vous avez compris le foncteur applicatif :

Les foncteurs nous permettent d'appliquer des fonctions aux choses dans un contexte. Mais que faire si les fonctions que nous voulons appliquer sont déjà dans un contexte ? (Et il est assez facile de se retrouver dans cette situation si vous avez des fonctions qui prennent plus d'un paramètre).

Maintenant, nous avons besoin de quelque chose comme un foncteur mais qui prend aussi les fonctions déjà dans le contexte et les applique aux éléments du contexte. Et c'est ce qu'est le foncteur applicatif. Voici la signature :

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]
}

Jusqu'ici tout va bien. Maintenant vient la monade : que se passe-t-il si vous avez maintenant une fonction qui place les choses dans le contexte ? Sa signature sera g:X=>M[X] ... vous ne pouvez pas utiliser un foncteur car il s'attend à ce que X=>Y, nous finirons donc par M[M[X]], vous ne pouvez pas utiliser le foncteur applicatif car il s'attend à ce que la fonction soit déjà dans le contexte M[X=>Y] .

Nous utilisons donc une monade, qui prend une fonction X=>M[X] et quelque chose qui se trouve déjà dans le contexte M[A] et applique la fonction à ce qui se trouve dans le contexte, en plaçant le résultat dans un seul contexte. La signature est la suivante :

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

Cela peut être assez abstrait, mais si vous réfléchissez à la façon de travailler avec "Option", cela vous montre comment composer des fonctions X=>Option[X]

EDIT : J'ai oublié la chose importante pour le lier : le symbole >>= est appelé lier et est flatMap en Scala. (En outre, pour l'anecdote, il existe certaines lois que les foncteurs, les applicatifs et les monades doivent suivre pour fonctionner correctement).

17voto

VonC Points 414372

Le meilleur article exposant en détail ces deux notions est " L'essence du modèle Iterator " de Blog d'Eric Torreborre .

Fecteur

trait Functor[F[_]] {
  def fmap[A, B](f: A => B): F[A] => F[B]
}
  • Une façon d'interpréter un Functor est de le décrire comme un calcul des valeurs de type A .
    Par exemple :
    • List[A] est un calcul retournant plusieurs valeurs de type A (calcul non déterministe),
    • Option[A] est pour les calculs que vous pouvez avoir ou non,
    • Future[A] est un calcul d'une valeur de type A que vous obtiendrez plus tard, et ainsi de suite.
  • Une autre façon de l'imaginer est la suivante une sorte de "conteneur" pour les valeurs de type A .

C'est la couche de base à partir de laquelle vous définissez :

  • PointedFunctor (pour créer une valeur de type F[A] ) et
  • Applic (pour fournir une méthode applic étant une valeur calculée à l'intérieur du conteneur F (F[A => B]) à appliquer à une valeur F[A] ), Applicative Functor (agrégation d'un Applic et un PointedFunctor ).

Ces trois éléments sont utilisés pour définir un Monad .

0voto

Jackson Tale Points 6557

Je pense que ce grand article de blog vous aidera d'abord pour monad . http://blog.enfranchisedmind.com/2007/08/a-monad-tutorial-for-ocaml/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X