Question

Je pose une question fondamentale qui m'a confondu récemment. Je veux écrire un Scala Pour l'expression de faire quelque chose comme ce qui suit:

for (i <- expr1) {
  if (i.method) {
    for (j <- i) {
      if (j.method) {
        doSomething()
      } else {
        doSomethingElseA()
      }
    }
  } else {
    doSomethingElseB()
  }
}

Le problème est que, dans les multiples générateurs d'expression, je ne sais pas où je peux mettre chacun pour le corps d'expression.

for {i <- expr1
  if(i.method) // where can I write the else logic ?
  j <- i 
  if (j.method)
} doSomething()

Comment puis-je réécrire le code dans Scala style?

Était-ce utile?

La solution

Le premier code que vous avez écrit est tout à fait valide, il n'y a donc pas besoin de le réécrire. Par ailleurs, vous avez dit que vous vouliez savoir comment le faire style Scala. Il n'y a pas vraiment une « Scala de style », mais je suppose un style plus fonctionnel et harnachement.

for (i <- expr1) {
  if (i.method) {
    for (j <- i) {
      if (j.method) {
        doSomething()
      } else {
        doSomethingElseA()
      }
    }
  } else {
    doSomethingElseB()
  }
}

La première préoccupation est que ce ne renvoie aucune valeur. Tout ce qu'il fait est des effets secondaires, qui doivent être évités aussi bien. Donc, le premier changement serait comme ceci:

val result = for (i <- expr1) yield {
  if (i.method) {
    for (j <- i) yield {
      if (j.method) {
        returnSomething()
        // etc

Maintenant, il y a une grande différence entre

for (i <- expr1; j <- i) yield ...

et

for (i <- expr1) yield for (j <- i) yield ...

Ils retour des choses différentes, et il y a des moments que vous voulez le plus tard, pas le premier. Je suppose que vous voulez que l'ancienne, cependant. Maintenant, avant d'aller plus loin, nous allons fixer le code. Il est laid, difficile à suivre et uninformative. Le refactoring Let par extraction des méthodes.

def resultOrB(j) = if (j.method) returnSomething else returnSomethingElseB
def nonCResults(i) = for (j <- i) yield resultOrB(j)
def resultOrC(i) = if (i.method) nonCResults(i) else returnSomethingC
val result = for (i <- expr1) yield resultOrC(i)

Il est déjà beaucoup plus propre, mais il ne revient pas tout à fait ce que nous attendons. Regardons la différence:

trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"

def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else Unrecognized
val result = for (i <- expr1) yield classifyElements(i)

Le type de result il y a Array[AnyRef], tout en utilisant de multiples générateurs donnerait Array[Element]. La partie la plus facile du correctif est la suivante:

val result = for {
  i <- expr1
  element <- classifyElements(i)
} yield element

Mais cela seul ne fonctionnera pas, parce que classifyElements se retourne AnyRef, et nous voulons le retourner une collection. Maintenant, validElements revenir une collection, donc ce n'est pas un problème. Nous avons seulement besoin de fixer la partie else. Depuis validElements retourne un IndexedSeq, Retournons que de la part de else ainsi. Le résultat final est:

trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"

def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else IndexedSeq(Unrecognized)
val result = for {
  i <- expr1
  element <- classifyElements(i)
} yield element

Ce fait exactement la même combinaison de boucles et les conditions que vous avez livré, mais il est beaucoup plus facile à lire et facile à changer.

A propos de rendement

Je pense qu'il est important de noter une chose au sujet du problème présenté. Simplifions il:

for (i <- expr1) {
  for (j <- i) {
    doSomething
  }
}

, qui est mis en œuvre avec foreach (voir , ou d'autres questions similaires et répond). Cela signifie que le code ci-dessus fait exactement la même chose que ce code:

for {
  i <- expr1
  j <- i
} doSomething

Exactement la même chose. Ce n'est pas vrai du tout quand on utilise yield. Les expressions suivantes ne donnent pas le même résultat:

for (i <- expr1) yield for (j <- i) yield j

for (i <- expr1; j <- i) yield j

Le premier extrait sera mis en œuvre à travers deux appels map, tandis que le second extrait utilisera un flatMap et un map.

Ainsi, il est seulement dans le contexte de yield qu'il fait même aucun sens à se soucier de l'imbrication des boucles de for ou en utilisant plusieurs générateurs. Et, en fait, générateurs représente le fait que quelque chose est généré , qui est seulement vrai de vrai pour-compréhensions (ceux yielding quelque chose).

Autres conseils

La partie

for (j <- i) {
   if (j.method) {
     doSomething(j)
   } else {
     doSomethingElse(j)
   }
 }

peut être réécrite comme

for(j <- i; e = Either.cond(j.method, j, j)) {
  e.fold(doSomething _, doSomethingElse _)  
}  

(bien sûr, vous pouvez utiliser un rendement plutôt que si votre do .. méthodes renvoient quelque chose)

Ici, il est pas si terrible utile, mais si vous avez une structure plus profonde imbriquée, il pourrait ...

import scalaz._; import Scalaz._

val lhs = (_ : List[X]) collect { case j if j.methodJ => doSomething(j) } 
val rhs = (_ : List[X]) map doSomethingElse
lhs <-: (expr1 partition methodI) :-> rhs

Vous ne pouvez pas. Le for (expr, si). Construction du filtre juste l'élément qui doit être manipulé dans la boucle

Si l'ordre n'a pas d'importance pour les appels à doSomething () et doSomethingElse (), vous pouvez réorganiser le code comme celui-ci.

val (tmp, no1) = expr1.partition(_.method)
val (yes, no2) = tmp.partition(_.method)

yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())

Pour répondre à votre question initiale, je pense que pour compréhensions peut être très agréable pour les cas d'utilisation spécifiques, et votre exemple ne correspond pas bien.

Les conditions spécifiées dans un acte de Scala de fonctionnement pour filtrer les éléments des générateurs. Les éléments ne satisfaisant pas les conditions sont rejetées et ne sont pas présentés au rendement / bloc de code.

Ce que cela signifie est que si vous souhaitez effectuer d'autres opérations sur la base d'une expression conditionnelle, les besoins de test pour être reportés au bloc rendement / code.

Il faut aussi savoir que le fonctionnement est relativement coûteux à calculer (actuellement) alors peut-être un peut-être quelque chose comme approche itérative plus simple pourrait être plus approprié,:

expr1 foreach {i =>
  if (i.method) {
    i foreach {j =>
      if (j.method)
        doSomething()
      else
        doSomethingElseA()
    }
  }
  else
    doSomethingElseB()
}

Mise à jour:

Si vous devez utiliser une pour la compréhension et vous pouvez vivre avec certaines restrictions, ce travail pourrait:

for (i <- expr1; j <- i) {
  if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top