2013-07-19 8 views
25

Dlaczego wyraźna instrukcja return (taka, która używa słowa kluczowego return) w anonimowej funkcji zwraca z otaczającej funkcji nazwanej, a nie tylko z samej anonimowej funkcji?Instrukcje zwrotne Scala w anonimowych funkcjach

E.g. następujące wyniki programu w błąd typu:

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

wiem, zaleca się unikać słowa kluczowego return, ale jestem zainteresowany dlaczego jawne i ukryte oświadczenia powrotne mają inną semantykę w funkcji anonimowych.

W poniższym przykładzie instrukcja return "przetrwa" po zakończeniu wykonywania m, a program spowoduje wyjątek w czasie wykonywania. Jeśli funkcje anonimowe nie powróciły z funkcji otaczającej, nie byłoby możliwe skompilowanie tego kodu.

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

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

Odpowiedz

16

Formalnie definiuje się jako powrót zawsze powrocie z dokładnością do załączania nazwie metody

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.

Więc nie mieć różne semantykę w lambda. Zmarszczka polega na tym, że w przeciwieństwie do normalnej metody zamknięcie utworzone z lambda może uniknąć wywołania metody otaczającej i można uzyskać wyjątek, jeśli w takim zamknięciu występuje powrót.

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.

Teraz, jak w przypadku "dlaczego". Jednym mniejszym powodem jest estetyka: lambdy są wyrażeniami i to jest miłe, gdy wyrażenie i wszystkie jego podwyrażenia mają to samo znaczenie, bez względu na strukturę gniazdowania. Neal Gafter mówi o tym pod adresem: http://gafter.blogspot.com/2006/08/tennents-correspondence-principle-and.html

Głównym powodem, dla którego istnieje, jest umożliwienie łatwej symulacji form przepływu kontrolnego powszechnie używanego w programowaniu imperatywnym, ale nadal pozwala na przekształcenie rzeczy w funkcje wyższego rzędu. Jako zabawny przykład, konstrukcja foreach języka Java (for (x : xs) { yada; }) umożliwia powrót wewnątrz pętli. Scala nie ma foreach na poziomie językowym. Zamiast tego umieszcza foreach w bibliotece (nie licząc "wyrażenia" bez plonu, ponieważ po prostu rezygnują z foreach). Posiadanie nielokalnego zwrotu oznacza, że ​​możesz wziąć foreach Java i przetłumaczyć bezpośrednio na foreach Scala.

BTW, Ruby, Smalltalk i Common Lisp (poza moją głową) również mają podobne "nielokalne" zwroty.

+0

Czy są jakieś poważniejsze przykłady, dlaczego potrzebna jest różnica w semantykach? Ponieważ to, co masz na liście, można łatwo emulować za pomocą zmodyfikowanego 'foreach', który ma parametr predykatu. – corazza

3

return kluczowe jest zarezerwowana dla metod (klasa), nie mogą być wykorzystywane w funkcjach. Można łatwo sprawdzić, że:

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

Daje

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

Głównie można traktować metody i funkcje takie same, ponieważ funkcji za apply sposób zachowuje się składniowo jak wywołanie metody, a tak zwane eta-expansion pozwalająca na przekazanie metody jako argumentu funkcji.

W tym przypadku robi różnicę. Przy definiowaniu jako metody, to jest legalne:

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

Podsumowując, należy użyć tylko return metod, które pozwalają warunkowy (wcześnie) powraca. Zobacz dyskusję na temat metod i funkcji w artykule this post.

+0

Ta odpowiedź w rzeczywistości nie odpowiada na * dlaczego *, tj. Za uzasadnieniem projektu językowego. – corazza

+0

@jcz to byłoby bardzo rozmyte i nieintuicyjne. Pomyśl o lambdach, np. 'xs.foreach {x => if (x == y) zwraca x}' i tym podobne. –