Pouvez-vous rompre avec une fermeture Groovy «each»?

143

Est-il possible de breakpartir d'un Groovy .each{Closure}ou devrais-je utiliser une boucle classique à la place?

métallique
la source

Réponses:

194

Non, vous ne pouvez pas abandonner un «chacun» sans lancer une exception. Vous souhaiterez probablement une boucle classique si vous souhaitez que l'interruption s'interrompe dans une condition particulière.

Alternativement, vous pouvez utiliser une fermeture «find» au lieu d'un each et retourner true lorsque vous auriez fait une pause.

Cet exemple s'arrêtera avant de traiter toute la liste:

def a = [1, 2, 3, 4, 5, 6, 7]

a.find { 
    if (it > 5) return true // break
    println it  // do the stuff that you wanted to before break
    return false // keep looping
}

Tirages

1
2
3
4
5

mais n'imprime pas 6 ou 7.

Il est également très facile d'écrire vos propres méthodes d'itération avec un comportement de rupture personnalisé qui accepte les fermetures:

List.metaClass.eachUntilGreaterThanFive = { closure ->
    for ( value in delegate ) {
        if ( value  > 5 ) break
        closure(value)
    }
}

def a = [1, 2, 3, 4, 5, 6, 7]

a.eachUntilGreaterThanFive {
    println it
}

Imprime également:

1
2
3
4
5    
Ted Naleid
la source
3
J'ai également soumis un correctif à groovy qui ajoute une méthode findResult qui effectue une recherche court-circuitée qui renvoie le premier résultat non nul de la fermeture. Cette méthode pourrait être utilisée pour couvrir presque toutes les situations dont quelqu'un pourrait vouloir sortir tôt. Vérifiez le patch pour voir s'il est accepté et transformé en groovy (j'espère d'ici la 1.8): jira.codehaus.org/browse/GROOVY-4253
Ted Naleid
2
J'ai essayé ce cas: def a = [1, 2, 3, 4, 5, 6, 7, -1, -2] Il a renvoyé 1 2 3 4 5 -1 -2. Donc, "breaK" ne fonctionne pas.
Phat H.VU
La recherche correcte est une option correcte, chacun ne sera jamais interrompu par retour
Pushkar
est findmieux que any- voir l'autre réponse ci-dessous de @Michal que l'on travaille pour moi
Rhubarb
Le patch de Ted Naleid pour groovy est très utile. Utilisez plutôt findResult si vous avez réellement besoin du résultat de l'opération de recherche et non de l'élément lui-même. Ex: ​def test = [2] test.findResult{ it * 2 }​retournera 4 au lieu de 2
Doug
57

Remplacez chaque boucle par n'importe quelle fermeture.

def list = [1, 2, 3, 4, 5]
list.any { element ->
    if (element == 2)
        return // continue

    println element

    if (element == 3)
        return true // break
}

Production

1
3
Michal Z muda
la source
Juste un truc. Que se passe-t-il s'il y a une déclaration après "pause". Cette déclaration sera toujours exécutée après la «pause» de la rencontre.
Phat H.VU
2
@Phat H. VU J'ai ajouté le retour. La déclaration après "pause" ne sera pas exécutée
Michal Z muda
1
Merci pour votre réponse. Vous avez raison. Je vote également votre réponse. De plus: la méthode any est définie dans DefaultGroovyMethods et il s'agit d'une fonction de prédicat qui renvoie true si un élément d'une collection satisfait la fermeture de prédicat fournie.
Phat H.VU
Utiliser any()de cette manière est un peu trompeur, mais cela fonctionne certainement et vous donne la possibilité de rompre ou de continuer .
vegemite4me
1
comme @ vegemite4me, je pense que c'est un "truc" mais vous ne comprenez pas bien le sens de tout. Pour des raisons de maintenabilité, vous ne devez pas utiliser cette solution.
MatRt
11

Non, vous ne pouvez pas rompre avec une fermeture dans Groovy sans lancer une exception. De plus, vous ne devez pas utiliser d'exceptions pour le flux de contrôle.

Si vous avez envie de sortir d'une clôture, vous devriez probablement d'abord vous demander pourquoi vous voulez faire cela et non comment le faire. La première chose à considérer pourrait être la substitution de la fermeture en question par l'une des fonctions d'ordre supérieur (conceptuel) de Groovy. L'exemple suivant:

for ( i in 1..10) { if (i < 5) println i; else return}

devient

(1..10).each{if (it < 5) println it}

devient

(1..10).findAll{it < 5}.each{println it} 

ce qui contribue également à la clarté. Il énonce bien mieux l'intention de votre code.

L'inconvénient potentiel des exemples illustrés est que l'itération ne s'arrête que tôt dans le premier exemple. Si vous avez des considérations de performances, vous voudrez peut-être l'arrêter immédiatement.

Cependant, pour la plupart des cas d'utilisation qui impliquent des itérations, vous pouvez généralement recourir à l'une des méthodes find, grep, collect, inject, etc. de Groovy. Ils prennent généralement une certaine "configuration" et ensuite "savent" comment faire l'itération pour vous, de sorte que vous puissiez réellement éviter les boucles impératives dans la mesure du possible.

Kai Sternad
la source
1
"Non, vous ne pouvez pas rompre avec une fermeture dans Groovy sans lancer une exception.". C'est ce que fait Scala, voir dev.bizo.com/2010/01/scala-supports-non-local-returns.html
OlliP
2

Juste en utilisant une fermeture spéciale

// declare and implement:
def eachWithBreak = { list, Closure c ->
  boolean bBreak = false
  list.each() { it ->
     if (bBreak) return
     bBreak = c(it)
  }
}

def list = [1,2,3,4,5,6]
eachWithBreak list, { it ->
  if (it > 3) return true // break 'eachWithBreak'
  println it
  return false // next it
}
mer-kg
la source
3
et si vous avez 1 milliard de lignes et que la fermeture interne retourne true au premier appel, vous itérerez alors sur 1 milliard moins une valeur. :(
sbglasius
-4

(1..10) .chaque {

si (il <5)

imprimer

autre

retourner faux

Sagar Mal Shankhala
la source
2
Cela ne sort pas de l ' each, il n'imprime tout simplement pas les valeurs supérieures à 4. C'est elsesuperflu, votre code ferait de même sans lui. En outre, vous pouvez prouver eachque vous ne rompez pas avec return falsesi vous mettez println "not breaking"juste après elseet juste avant return false.
Stefan van den Akker
Veuillez faire au moins un effort minimum pour formater votre code pour plus de lisibilité. Il y a un aperçu visuel lorsque vous répondez et beaucoup d'instructions sur la façon de le faire
Mateusz était
-11

Vous pourriez passer RETURN. Par exemple

  def a = [1, 2, 3, 4, 5, 6, 7]
  def ret = 0
  a.each {def n ->
    if (n > 5) {
      ret = n
      return ret
    }
  }

Ça marche pour moi!

Tseveen D
la source
6
C'est exactement ce que le PO a demandé, même si ce n'est évidemment pas ce qu'il voulait dire.
Szocske
Puis-je avoir une description des raisons pour lesquelles cela a des votes négatifs. Cela me semble le même concept que ce que dit la première réponse (avec moins d'explications) avec +133 votes.
Skepi
1
@Skepi Vous ne pouvez pas "casser" au-dessus de la fermeture. Vous pouvez casser la anyméthode du tableau en retournantfalse . Vous ne pouvez pas interrompre la eachméthode de la même manière.
Nux