2013-07-19 8 views
25

Perché un'istruzione di reso esplicita (una che utilizza la parola chiave return) in una funzione anonima restituisce dalla funzione denominata che la contiene e non solo dalla funzione anonima stessa?Istruzioni di ritorno Scala in funzioni anonime

E.g. i seguenti risultati del programma in un errore di tipo:

def foo: String = { 
    ((x: Integer) => return x) 
    "foo" 
} 

So che è consigliato di evitare la parola chiave return, ma mi interessa il motivo per cui le dichiarazioni esplicite ed implicite di ritorno hanno una semantica diversa in funzioni anonime.

Nell'esempio seguente, l'istruzione di reso "sopravvive" dopo che m ha terminato l'esecuzione e il programma restituisce un'eccezione di run-time. Se le funzioni anonime non tornassero dalla funzione di inclusione, non sarebbe possibile compilare quel codice.

def main(args: Array[String]) { 
    m(3) 
} 

def m: (Integer => Unit) = 
    (x: Integer) => return (y: Integer) => 2 

risposta

16

ritorno Formalmente è definito come il ritorno sempre dal nome metodo

A return expression return e must occur inside the body of some enclosing named method or function. The innermost enclosing named method or function in a source program, f , must have an explicitly declared result type, and the type of e must conform to it. The return expression evaluates the expression e and returns its value as the result of f . The evaluation of any statements or expressions following the return expression is omitted.

più vicino racchiude Quindi non ha una semantica diversa in un lambda. La ruga è che, a differenza di un metodo normale, una chiusura creata da una lambda può sfuggire a una chiamata al metodo di inclusione e si può ottenere un'eccezione se c'è un ritorno in tale chiusura.

If the return expression is itself part of an anonymous function, it is possible that the enclosing instance of f has already returned before the return expression is executed. In that case, the thrown scala.runtime.NonLocalReturnException will not be caught, and will propagate up the call stack.

Ora, come per "perché". Una ragione minore è estetica: i lambda sono espressioni ed è bello quando un'espressione e tutta la sua sottoespressione hanno lo stesso significato, indipendentemente dalla struttura di nidificazione. Neal Gafter ne parla al numero http://gafter.blogspot.com/2006/08/tennents-correspondence-principle-and.html

Il motivo principale per cui esiste, però, è che consente di simulare facilmente forme di flusso di controllo comunemente utilizzate nella programmazione imperativa, ma consente comunque di astrarre le cose in funzioni di ordine superiore. Come esempio di esempio, il costrutto foreach di Java (for (x : xs) { yada; }) consente un ritorno all'interno del ciclo. Scala non ha un livello di lingua predefinito. Invece, mette in foreach nella libreria (senza contare "per espressione" senza rendimento dal momento che hanno appena desugar a foreach). Avere un ritorno non locale significa che puoi prendere un Java foreach e tradurre direttamente in un foreach di Scala.

Anche BTW, Ruby, Smalltalk e Common Lisp (fuori dalla parte superiore della mia testa) hanno ritorni simili "non locali".

+0

Avete alcuni esempi più seri di perché è necessaria la differenza nella semantica? Perché ciò che hai elencato può essere facilmente emulato con un 'foreach' modificato che ha un parametro predicato. – corazza

3

Il return parola chiave è riservato per i metodi (di classe), non può essere utilizzato nelle funzioni. Si può facilmente verificare che:

object Foo { 
    val bar = (i: Int) => return i + i 
} 

Questo dà

<console>:42: error: return outside method definition 
     object Foo { val bar = (i: Int) => return i + i } 
             ^

lo più si può trattare metodi e le funzioni come lo stesso, a causa del metodo di apply della funzione comportarsi sintatticamente come chiamare un metodo, e il cosiddetto eta-expansion che consente di passare un metodo come argomento di funzione.

In questo caso, fa la differenza. Quando si definisce come metodo, è legale:

object Foo { 
    def bar(i: Int): Int = return i + i 
} 

In sintesi, si dovrebbe utilizzare solo return nei metodi che consentano (precoce) restituisce condizionali. Vedere this post per una discussione sui metodi rispetto alle funzioni.

+0

Questa risposta in realtà non risponde * perché *, cioè, il ragionamento del linguaggio design dietro quella decisione. – corazza

+0

@jcz questo sarebbe estremamente sfocato e non intuitivo. Pensa a lambda, ad es. 'xs.foreach {x => if (x == y) restituisce x}' e simili. –