2012-05-29 13 views
7

Perché seguente codiceScala durante la mancata corrispondenza di tipo (vero)? Ciclo infinito in scala?

def doSomething() = "Something" 

var availableRetries: Int = 10 

def process(): String = { 
    while (true) { 
    availableRetries -= 1 
    try { 
     return doSomething() 
    } catch { 
     case e: Exception => { 
     if (availableRetries < 0) { 
      throw e 
     } 
     } 
    } 
    } 
} 

produce seguente errore del compilatore

error: type mismatch; 
found : Unit 
required: String 
      while (true) { 
      ^

?

Funziona bene in C#. Il ciclo while si interrompe per sempre, quindi non può terminare, quindi non può risultare altro che una stringa. O come fare loop infinito in Scala?

+3

FWIW - ci sono [ molti modi per implementare nuovamente la logica retry] (http://stackoverflow.com/q/7930814/115478). – leedm777

+0

+1 Grazie per i suggerimenti e ulteriori informazioni sulle insidie ​​di Tailrec. –

risposta

1

Sulla base di senia, elbowich e s' dave soluzioni che ho usato seguente:

@annotation.tailrec 
def retry[T](availableRetries: Int)(action: => T): T = { 
    try { 
    return action 
    } catch { 
    case e: Exception if (availableRetries > 0) => { } 
    } 
    retry(availableRetries - 1)(action) 
} 

Che può essere quindi utilizzato come elbowich e le soluzioni di Dave:

retry(3) { 
    // some code 
} 
+0

Nota che questo non funziona se per qualsiasi motivo non è possibile rendere la funzione ricorsiva in coda (ad esempio potresti chiamarti più volte). –

0

modifica: Ho appena notato la dichiarazione di reso effettiva. L'istruzione return all'interno del ciclo while verrà ignorata. Ad esempio, nel REPL:

scala> def go = while(true){return "hi"} 
<console>:7: error: method go has return statement; needs result type 
    def go = while(true){return "hi"} 
         ^ 

ti ha detto al compilatore che il metodo process() restituisce un String, ma il tuo corpo metodo è solo un ciclo while, che non restituisce nulla (è un Unit, o Java void). Modificare il tipo restituito o aggiungere una stringa dopo il ciclo while.

def process(): Unit = { 
    while(true){...} 
} 

o

def process(): String = { 
    while(true){...} 
    "done" 
} 
3

Il compilatore non è abbastanza intelligente per sapere che non si può uscire dal ciclo while, purtroppo. È facile ingannare, anche se, anche se non è possibile generare un membro del tipo restituito in modo ragionevole, è sufficiente lanciare un'eccezione.

def process(): String = { 
    while (true) { 
    ... 
    } 
    throw new Exception("How did I end up here?") 
} 

Ora il compilatore si renderà conto che, anche in caso di fuga del ciclo while, non può restituire un valore lì, quindi non si preoccupa che il ciclo while ha Tipo Unit tornare (cioè non restituisce un valore).

16

A differenza di C# (e Java e C e C++) che sono linguaggi basati su istruzioni, Scala è un linguaggio basato su espressioni. Questo è principalmente un grande vantaggio in termini di compostezza e leggibilità, ma in questo caso la differenza ti ha morsicato.

metodo A Scala restituisce implicitamente il valore dell'ultima espressione nel metodo

scala> def id(x : String) = x 
id: (x: String)String 

scala> id("hello")   
res0: String = hello 

In Scala praticamente tutto ciò è espressione. Le cose che assomigliano a dichiarazioni sono ancora espressioni che restituiscono un valore di un tipo chiamato Unità. Il valore può essere scritto come().

scala> def foo() = while(false){} 
foo:()Unit 

scala> if (foo() ==()) "yes!" else "no" 
res2: java.lang.String = yes! 

No compilatore per un linguaggio Turing-equivalente in grado di rilevare tutti i loop non fatale (c.f. Turing problema della terminazione) in modo maggior parte dei compilatori fare ben poco lavoro per rilevare eventuali. In questo caso il tipo di "while (someCondition) {...}" è Unit, indipendentemente dalla Condizione, anche se è costante.

scala> def forever() = while(true){} 
forever:()Unit 

Scala determina che il tipo restituito dichiarato (stringa) non è compatibile con il tipo di rendimento effettivo (Unit), che è il tipo dell'ultima espressione (mentre ...)

scala> def wtf() : String = while(true){} 
<console>:5: error: type mismatch; 
found : Unit 
required: String 
     def wtf() : String = while(true){} 

Risposta: aggiungere un'eccezione alla fine

scala> def wtfOk() : String = { 
    | while(true){} 
    | error("seriously, wtf? how did I get here?") 
    | } 
wtfOk:()String 
+0

+1 Grazie per la tua profonda spiegazione. Ma io cosa che il compilatore Scala dovrebbe risolvere le espressioni costanti (come fa il compilatore C#). –

+1

In questo caso, C# non è intelligente riguardo all'espressione costante. Tratta semplicemente "while" come una dichiarazione di non ritorno. Puoi dimostrarlo da solo scrivendo un costrutto C# simile in cui la condizione nel "while" non è costante, come per esempio la lettura di una variabile fornita dall'utente. C# vede "mentre" in modo molto diverso da Scala. C# analizza il codice dicendo che l'unico "ritorno" esplicito restituisce una stringa. Scala vede "while" come l'ultima espressione, e quindi di ritorno. Questa è la differenza tra un lang basato su espressioni (come Scala) e un lang basato su istruzioni come C#. –

+3

In teoria, il linguaggio potrebbe avere un caso speciale da riconoscere mentre (true) {...} come tipo Niente, piuttosto che tipo Unità. Quindi l'esempio funzionerebbe correttamente, poiché Nothing non è un sottotipo di String (e di tutto il resto). Ho suggerito questo a mailing un paio di anni fa, ma la risposta generale è stata che non si è verificato abbastanza spesso da valere la pena di cambiare la lingua. Se fai questo genere di cose spesso, è abbastanza facile creare un metodo "per sempre" come descrivi, ma avere un tipo di ritorno Niente piuttosto che Unità. –

7

modo funzionale per definire un ciclo infinito è ricorsione:

@annotation.tailrec def process(availableRetries: Int): String = { 
    try { 
    return doSomething() 
    } catch { 
    case e: Exception => { 
     if (availableRetries < 0) { 
     throw e 
     } 
    } 
    } 
    return process(availableRetries - 1) 
} 

elbowich retry funzione s' colpo interno loop funzione:

import scala.annotation.tailrec 
import scala.util.control.Exception._ 

@tailrec def retry[A](times: Int)(body: => A): Either[Throwable, A] = { 
    allCatch.either(body) match { 
    case Left(_) if times > 1 => retry(times - 1)(body) 
    case x => x 
    } 
} 
+1

+1 Grazie per la soluzione più semplice. –

4
import scala.annotation.tailrec 
import scala.util.control.Exception._ 

def retry[A](times: Int)(body: => A) = { 
    @tailrec def loop(i: Int): Either[Throwable, A] = 
    allCatch.either(body) match { 
     case Left(_) if i > 1 => loop(i - 1) 
     case x => x 
    } 
    loop(times) 
} 

retry(10) { 
    shamelessExceptionThrower() 
} 
+0

+1 Grazie per la soluzione più generica. –

+1

Risposta eccellente, ma non è necessaria la funzione interna 'loop'. – senia

+0

@senia Giusto. Avevo dei dubbi sul fatto che 'body' valesse o meno sulla chiamata ricorsiva. – elbowich

Problemi correlati