97 votes

Confondu avec la transformation de flat-map / map pour comprendre

J'ai vraiment ne semblent pas être la compréhension de la Carte et FlatMap. Ce que je suis à défaut de comprendre, c'est comment un pour-la compréhension est une séquence d'appels imbriqués de la carte et flatMap. L'exemple suivant est tiré de la Programmation Fonctionnelle en Scala

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
            f <- mkMatcher(pat)
            g <- mkMatcher(pat2)
 } yield f(s) && g(s)

se traduit par

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = 
         mkMatcher(pat) flatMap (f => 
         mkMatcher(pat2) map (g => f(s) && g(s)))

Le mkMatcher méthode est définie comme suit:

  def mkMatcher(pat:String):Option[String => Boolean] = 
             pattern(pat) map (p => (s:String) => p.matcher(s).matches)

Et le modèle de la méthode est comme suit:

import java.util.regex._

def pattern(s:String):Option[Pattern] = 
  try {
        Some(Pattern.compile(s))
   }catch{
       case e: PatternSyntaxException => None
   }

Il sera grand si quelqu'un pourrait jeter quelque lumière sur la justification de l'utilisation de la carte et flatMap ici.

227voto

pagoda_5b Points 3909

TL;DR aller directement au dernier exemple

Je vais essayer de récapituler

Définitions

L' for compréhension est une syntaxe raccourcie pour combiner flatMap et map d'une manière qui est facile à lire et à comprendre.

Nous allons simplifier les choses un peu et supposons que chaque class qui fournit à la fois des méthodes ci-dessus peut être appelé un monad et, nous allons utiliser le symbole M[A] signifier monad avec un intérieur de type A.

Exemples

Certains communément vu monades

  • List[String]
    • M[_]: List[_]
    • A: String
  • Option[Int]
    • M[_]: Option[_]
    • A: Int
  • Future[String => Boolean]
    • M[_]: Future[_]
    • A: String => Boolean

carte et flatMap

Définie dans un générique monade M[A]

 /* applies a transformation of the monad "content" mantaining the 
  * monad "external shape"  
  * i.e. a List remains a List and an Option remains an Option 
  * but the inner type changes
  */
  def map(f: A => B): M[B] 

 /* applies a transformation of the monad "content" by composing
  * this monad with an operation resulting in another monad instance 
  * of the same type
  */
  def flatMap(f: A => M[B]): M[B]

par exemple

  val list = List("neo", "smith", "trinity")

  //converts each character of the string to its corresponding code
  val f: String => List[Int] = s => s.map(_.toInt).toList 

  list map f
  >> List(List(110, 101, 111), List(115, 109, 105, 116, 104), List(116, 114, 105, 110, 105, 116, 121))

  list flatMap f
  >> List(110, 101, 111, 115, 109, 105, 116, 104, 116, 114, 105, 110, 105, 116, 121)

pour l'expression

  1. chaque ligne dans l'expression à l'aide de l' <- symbole est traduite en flatMap appel où le "lié " symbole" sur le côté gauche est passée comme paramètre à la fonction d'argument (ce que nous avons appelé précédemment f: A => M[B])

    //writing
    for {
      bound <- list
      out <- f(bound)
    } yield out
    
    //is the same as 
    list flatMap f
    
  2. l' yield expression est convertie à conclure map appel à l'expression passée en argument

    //writing
    for {
      bound <- list
    } yield f(bound)
    
    //is the same as
    
    list map f
    

Maintenant, pour le point

Comme vous pouvez le voir, l' map opération préserve la "forme" de l'original, monad, de sorte que la même chose se passe pour l' yield expression: un List reste List avec le contenu transformé par l'opération de la yield

D'autre part, chacun de liaison de la ligne dans l' for est juste une composition de successives monads, qui doit être "aplati" afin de maintenir une seule "forme extérieure"

Supposons pour un instant que chacun de liaison interne a été traduite en map appel, mais la droite était le même A => M[B] fonction, vous vous retrouvez avec un M[M[B]] pour chaque ligne dans la compréhension.
L'intention de l'ensemble de l' for syntaxe est facilement "aplatir" la concaténation de successives monadique opérations (opérations que "l'ascenseur" d'une valeur dans une "monadique de la forme: A => M[B]), avec l'ajout d'un final map opération qui , éventuellement, effectue une conclusion de transformation

J'espère que c'est ce qui explique la logique derrière le choix de la traduction, qui est appliqué de manière mécanique, c'est - n flatMap appels imbriqués conclu par un seul map appel.

Artificiel, exemple
Destinés à montrer l'expressivité de l' for de la syntaxe

case class Customer(value: Int)
case class Consultant(portfolio: List[Customer])
case class Branch(consultants: List[Consultant])
case class Company(branches: List[Branch])

def getCompanyValue(company: Company): Int = {

  val valuesList = for {
    branch     <- company.branches
    consultant <- branch.consultants
    customer   <- consultant.portfolio
  } yield (customer.value)

  valueList reduce (_ + _)
}

Pouvez-vous deviner le type d' valuesList?

Comme déjà dit, la forme de l' monad est maintenue par le biais de la compréhension, nous commençons donc avec un List en company.branches, et doit se terminer par un List.
L'intérieur type, au lieu des modifications et est déterminé par l' yield expression: qui est - customer.value: Int

valueList devrait être un List[Int]

5voto

BGR Points 4330

La raison en est à la chaîne monadique opérations qui fournit un avantage, bon de "fail fast" erreur de manipulation.

C'est en fait assez simple. L' mkMatcher méthode retourne un Option (ce qui est une Monade). Le résultat de l' mkMatcher, le monadique de l'opération, soit un None ou Some(x).

L'application de l' map ou flatMap fonction None retourne toujours un None - la fonction passée en paramètre à l' map et flatMap n'est pas évalué.

Donc dans votre exemple, si mkMatcher(pat) renvoie un Néant, le flatMap appliquée à il sera de retour une None (la deuxième monadique opération mkMatcher(pat2) ne sera pas exécuté) et de la finale mapsera de nouveau de retour d'un None. En d'autres termes, si l'une de ces opérations dans la, pour la compréhension, retourne Aucun, vous avez une échouer rapidement le comportement et le reste des activités ne sont pas exécutées.

C'est le monadique style d'erreur de manipulation. L'impératif de style utilise des exceptions, qui sont en fait des sauts (à une clause catch)

Une dernière remarque: le patterns fonction est d'une manière typique de "traduire" un impératif style de gestion d'erreur (try...catch) pour un monadique style de gestion des erreurs à l'aide de Option

1voto

korefn Points 780

Cela peut être traduit comme:

 def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)  // for every element from this [list, array,tuple]
    g <- mkMatcher(pat2) // iterate through every iteration of pat
} yield f(s) && g(s)
 

Exécutez-le pour avoir une meilleure vue de la façon dont il est étendu

 def match items(pat:List[Int] ,pat2:List[Char]):Unit = for {
        f <- pat
        g <- pat2
} println(f +"->"+g)

bothMatch( (1 to 9).toList, ('a' to 'i').toList)
 

les résultats sont:

 1 -> a
1 -> b
1 -> c
...
2 -> a
2 -> b
...
 

Ceci est similaire à flatMap - une boucle à travers chaque élément en pat et l' élément de foreach map pour chaque élément en pat2

0voto

xiaowl Points 2342

Tout d'abord, mkMatcher retourne une fonction dont la signature est - String => Boolean, qui est un habitué de java procédure qui vient d'exécuter Pattern.compile(string), comme indiqué dans l' patternfonction. Alors, regardez cette ligne

pattern(pat) map (p => (s:String) => p.matcher(s).matches)

L' map fonction est appliquée à la suite de l' pattern, ce qui est Option[Pattern], de sorte que l' p en p => xxx est juste le modèle que vous avez compilé. Donc, étant donné un modèle p, une nouvelle fonction est construit, qui prend une Chaîne de caractères s, et de vérifier si s correspond au modèle.

(s: String) => p.matcher(s).matches

Remarque, l' p variable est limitée au masque compilé. Maintenant, il est clair que la façon dont une fonction de signature String => Boolean est construit en mkMatcher.

Ensuite, nous allons la caisse de l' bothMatch de la fonction, qui est basé sur l' mkMatcher. Pour montrer comment bothMathch œuvres, nous regardons d'abord à cette partie:

mkMatcher(pat2) map (g => f(s) && g(s))

Depuis que nous avons une fonction de signature String => Boolean de mkMatcher, ce qui est g dans ce contexte, g(s) est équivalent à Pattern.compile(pat2).macher(s).matches, ce qui renvoie si la Chaîne s correspond au modèle pat2. Alors que diriez - f(s), c'est même comme g(s), la seule différence est que, le premier appel d' mkMatcher utilise flatMap, au lieu de map, Pourquoi? Parce qu' mkMatcher(pat2) map (g => ....) retours Option[Boolean], vous obtiendrez un résultat imbriqué Option[Option[Boolean]] si vous utilisez map pour les deux appels, ce n'est pas ce que vous voulez .

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