2012-05-29 19 views
7

Dlaczego następujący kodScala podczas niezgodności typu (true)? Nieskończona pętla w 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 
     } 
     } 
    } 
    } 
} 

produkuje następujący błąd kompilatora

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

?

Działa to dobrze w języku C#. Pętla while pozostaje na zawsze, więc nie może zakończyć, dlatego nie może spowodować czegoś innego niż string. Lub jak zrobić nieskończoną pętlę w Scali?

+3

FWIW - istnieją [ wiele sposobów ogólnej implementacji logiki powtórzeń] (http://stackoverflow.com/q/7930814/115478). – leedm777

+0

+1 Dziękujemy za porady i więcej informacji na temat pułapek tailrec. –

Odpowiedz

1

podstawie senia, elbowich i dave „s rozwiązań użyłem następujących:

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

które następnie mogą być wykorzystane jako elbowich i rozwiązań Dave'a:

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

Należy zauważyć, że to nie działa, jeśli z jakiegoś powodu nie można wykonać funkcji rekurencji tail (np. Można nazwać siebie kilka razy). –

0

edytuj: Właśnie zauważyłem faktyczny zwrot. Instrukcja return wewnątrz pętli while zostanie zignorowana. Na przykład, w 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"} 
         ^ 

Mówiłeś kompilator, że metoda process() zwraca String, ale twoje ciało metoda jest tylko while pętla, która nie zwraca niczego (to Unit, lub Java void). Zmień typ zwracanego tekstu lub dodaj ciąg po pętli while.

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

lub

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

Kompilator nie jest wystarczająco inteligentny, aby wiedzieć, że nie można wyjść z pętli while, niestety. Łatwo jest jednak oszukać, nawet jeśli nie można rozsądnie wygenerować elementu zwracającego - wystarczy rzucić wyjątek.

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

Teraz kompilator będzie sobie sprawę, że nawet w przypadku jego ucieczki pętli while, że nie może wrócić tam wartość, więc nie martw się, że pętla ma natomiast typ Unit powrotu (czyli nie zwraca wartość).

16

W przeciwieństwie do języków C# (oraz Java i C i C++), które są językami opartymi na instrukcjach, Scala jest językiem opartym na ekspresji. To głównie duży plus pod względem kompozycyjności i czytelności, ale w tym przypadku różnica cię ugodziła.

metoda A Scala niejawnie zwraca wartość ostatniego wyrażenia w metodzie

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

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

w Scala prawie wszystko jest wyrażeniem. Rzeczy, które wyglądają jak instrukcje, są nadal wyrażeniami, które zwracają wartość typu o nazwie Jednostka. Wartość można zapisać jako().

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

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

Nie kompilator dla Turing-równoważnej języku można wykryć wszystkie non-zakończenia pętli (por Turing zatrzymanie problem) więc większość kompilatorów zrobić bardzo mało pracy, aby wykryć każdy. W tym przypadku typ "while (someCondition) {...}" jest jednostką bez względu na to, co jest pewne, nawet jeśli jest to stała prawda.

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

Scala stwierdzi, że zadeklarowany typ zwracany (String) nie jest zgodna z rzeczywistą typ zwracany (część), która jest rodzajem ostatniego wyrazu (gdy ...)

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

odpowiedzi: dodanie wyjątek na końcu

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

+1 Dziękuję za głębokie wyjaśnienie. Ale rzeczą, że kompilator Scala powinien rozwiązywać wyrażenia stałe (jak robi to kompilator C#). –

+1

W tym przypadku C# nie jest inteligentny w odniesieniu do stałego wyrażenia. Po prostu traktuje "while" jako niezwracające oświadczenie. Możesz to udowodnić dla siebie, pisząc podobny konstrukt C#, w którym warunek w "while" nie jest stały - np. Odczytaj zmienną dostarczoną przez użytkownika. C# po prostu widzi "podczas" zupełnie inaczej niż Scala. C# analizuje kod, mówiąc, że jedyny jawny "return" zwraca ciąg znaków. Scala widzi "podczas" jako ostatnią, a zatem powracającą ekspresję. Taka jest różnica między językiem opartym na wyrażeniach (jak Scala) a językiem opartym na wyrażeniach jak C#. –

+3

Teoretycznie, język może mieć specjalny przypadek rozpoznawania, podczas gdy (true) {...} ma typ Nothing, a nie type Unit. Wtedy przykład będzie działał dobrze, ponieważ Nic nie jest podtypem String (i wszystkim innym). Zasugerowałem to na mailingu kilka lat temu, ale ogólna odpowiedź była taka, że ​​nie zdarzało się to wystarczająco często, by warto było zmienić język. Jeśli często robisz tego typu rzeczy, łatwo jest stworzyć metodę "na zawsze", jak to opisujesz, ale ma ona raczej typ zwrotu niż Nic. –

7

sposób funkcjonalny do wyznaczenia pętli nieskończonej jest rekursji:

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

elbowich jest retry funkcji bez wewnętrzna loop funkcja:

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 Dziękujemy za najprostsze rozwiązanie. –

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 Dziękujemy za bardziej ogólne rozwiązanie. –

+1

Doskonała odpowiedź, ale nie potrzebujesz wewnętrznej funkcji 'loop'. – senia

+0

@senia Right. Miałem wątpliwości, czy "ciało" oceni na rekurencyjnym wywołaniu. – elbowich