Domanda

Chiedo una domanda molto di base che mi ha confuso di recente. Voglio scrivere a Scala Per l'espressione di fare qualcosa di simile al seguente:

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

Il problema è che, nei molteplici generatori per l'espressione, non so dove posso mettere ogni corpo per l'espressione.

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

Come posso riscrivere il codice a Scala di stile?

È stato utile?

Soluzione

Il primo codice che hai scritto è perfettamente valida, quindi non c'è bisogno di riscriverla. Altrove hai detto che volevi sapere come farlo Scala-stile. Non c'è davvero un "in stile Scala", ma darò per scontato uno stile più funzionale e virata che.

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

La prima preoccupazione è che questo non restituisce alcun valore. Tutto ciò che fa è gli effetti collaterali, che sono da evitare pure. Così il primo cambiamento sarebbe come questo:

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

Ora, c'è una grande differenza tra

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

e

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

Tornano cose diverse, e ci sono volte che si desidera più tardi, non il primo. Darò per scontato che si desidera che il primo, però. Ora, prima di procedere, Si può correggere il codice. E 'brutto, difficile da seguire e uninformative. refactoring Ammettiamolo estraendo metodi.

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)

E 'già molto più pulito, ma non restituisce tutto quello che ci aspettiamo. Guardiamo la differenza:

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)

Il tipo di result c'è Array[AnyRef], mentre l'utilizzo di più generatori produrrebbe Array[Element]. La parte più facile della correzione è questo:

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

Ma questo da solo non funziona, perché classifyElements si restituisce AnyRef, e vogliamo che la restituzione di un insieme. Ora, validElements restituire un insieme, in modo che non è un problema. Abbiamo solo bisogno di fissare la parte else. Dal momento che validElements restituisce un IndexedSeq, torniamo che da parte else pure. Il risultato finale è:

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

che fa esattamente la stessa combinazione di loop e condizioni come presentato, ma è molto più leggibile e facile da cambiare.

Chi Resa

Penso che sia importante notare una cosa in merito al problema presentato. Diciamo che semplificano:

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

Ora, che viene implementato con foreach (vedi qui , o altre domande simili e rispondi). Ciò significa che il codice di cui sopra fa esattamente la stessa cosa di questo codice:

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

Esattamente la stessa cosa. Questo non è affatto vero, quando si sta usando yield. Le seguenti espressioni non danno lo stesso risultato:

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

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

Il primo frammento sarà attuata tramite due chiamate map, mentre il secondo frammento utilizzerà una flatMap e uno map.

Quindi, è solo nel contesto di yield che rende ancora alcun senso preoccuparsi di nidificazione loop for o utilizzando più generatori. E, infatti, generatori sta per il fatto che qualcosa è stato generato , che è vero solo di vero per-comprensioni (quelli yielding qualcosa).

Altri suggerimenti

La parte

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

può essere riscritta come

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

(ovviamente è possibile utilizzare un rendimento, invece, se il vostro fare .. metodi restituiscono qualcosa)

In questo caso non è così terribile utile, ma se si dispone di una struttura più profonda nidificato, si potrebbe ...

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

Non è possibile. Il for (expr, se). Costrutto appena filtrare l'elemento che deve essere gestito nel ciclo

Se l'ordine non è importante per le chiamate a doSomething () e doSomethingElse (), allora è possibile riorganizzare il codice come questo.

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

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

Per rispondere alla tua domanda iniziale, credo che per comprensioni possono essere abbastanza bello per specifici casi d'uso, e il tuo esempio non si adatta bene.

Le condizioni specificate nella Scala per il funzionamento atto a filtrare gli elementi dei generatori. Elementi che non soddisfano le condizioni vengono scartati e non sono presentati al blocco resa / code.

Ciò significa che se si desidera eseguire operazioni alternative basate su un'espressione condizionale, le esigenze di prova da differiti al blocco di rendimento / code.

Anche essere consapevoli del fatto che il funzionamento è relativamente costoso per calcolare (attualmente) in modo forse un approccio iterativo semplice potrebbe essere più appropriato, forse qualcosa di simile a:

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

Aggiornamento:

Se è necessario utilizzare una di comprensione e si può vivere con alcune restrizioni, questo lavoro potrebbe:

for (i <- expr1; j <- i) {
  if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top