2012-05-29 3 views
7

Почему следующий кодScala while (true) несоответствие типа? Бесконечная петля в 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 
     } 
     } 
    } 
    } 
} 

производит следующие ошибки компилятора

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

?

Это работает нормально на C#. Цикл while навсегда, поэтому он не может быть завершен, поэтому он не может привести к чему-то другому, кроме строки. Или как сделать бесконечный цикл в Scala?

+3

FWIW - есть [ множество способов реализации логики повторов в целом] (http://stackoverflow.com/q/7930814/115478). – leedm777

+0

+1 Благодарим вас за советы и дополнительную информацию о подводных ловушках. –

ответ

1

на основе senia, elbowich и dave «s решения я использовал следующее:

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

который может быть затем использован в качестве elbowich и решений Дэйва:

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

Обратите внимание, что это не работает, если по какой-либо причине вы не можете сделать функцию хвостовой рекурсивной (например, вы можете называть себя несколько раз). –

0

Редактировать: Я только что заметил фактический оператор возврата. Оператор return внутри цикла while будет проигнорирован. Например, в 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"} 
         ^ 

Вы сказали компилятору, что метод process() возвращает String, но ваш метод тело просто while петля, которая ничего не возвращает (это Unit, или Java void). Либо измените тип возврата, либо добавьте строку после цикла while.

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

или

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

Компилятор не достаточно умен, чтобы знать, что вы не можете выйти из цикла WHILE, к сожалению. Однако легко обмануть, даже если вы не можете разумно генерировать член возвращаемого типа - просто выбросьте исключение.

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

Теперь компилятор будет понимать, что даже если он избегает время цикла, он не может возвращать значение там, так что не беспокойтесь о том, что цикл в то время как имеет тип Unit возврата (т.е. не возвращать стоимость).

16

В отличие от C# (и Java и C и C++), которые являются языками на основе инструкций, Scala является языком, основанным на выражениях. Это в основном большой плюс с точки зрения композитоспособности и удобочитаемости, но в этом случае разница укусила вас.

Способ Scala неявно возвращает значение последнего выражения в методе

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

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

В Scala в значительной степени все это выражение. Вещи, которые выглядят как заявления, по-прежнему являются выражениями, возвращающими значение типа Unit. Значение может быть записано как().

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

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

Нет компилятор для Тьюринг-эквивалентного языка может обнаружить все не оконечные петли (c.f. Тьюринга проблемы остановки), так что большинство компиляторов делает очень мало работу для обнаружения любого. В этом случае тип «while (someCondition) {...}» - это единица, независимо от того, что такое someCondition, даже если это константа true.

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

Scala определяет, что заявленный тип возвращаемого (String) не совместим с фактическим типом возвращаемого (Unit), который является типом последнего выражения (в то время как ...)

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

Ответ: добавить исключение в конце

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

+1 Спасибо за глубокое объяснение. Но я считаю, что компилятор Scala должен разрешать постоянные выражения (как это делает компилятор C#). –

+1

В этом случае C# не разбирается в постоянном выражении. Он просто рассматривает «while» как не возвращающее утверждение. Вы можете доказать это сами, написав аналогичную конструкцию C#, где условие в «while» не является константой - например, прочитайте переменную, предоставленную пользователем. C# просто видит «пока» совсем по-другому от Scala. C# анализирует код, говоря, что только явный «возврат» возвращает строку. Scala видит «while» как последнее и, следовательно, возвращающее выражение. В этом разница между выражением на основе lang (например, Scala) и выражением, основанным на выражении как C#. –

+3

Теоретически, язык может иметь особый случай распознавания while (true) {...} как имеющий тип Nothing, а не тип Unit. Тогда пример будет работать нормально, поскольку Nothing - это подтип String (и всего остального). Я предложил это по почте через пару лет назад, но общий ответ заключался в том, что это не происходило достаточно часто, чтобы стоить изменить язык. Если вы часто это делаете, достаточно легко создать метод «навсегда», как вы описали, но иметь тип возврата «Нет», а не «Единица». –

7

Функциональный способ определения бесконечного цикла является рекурсия:

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

elbowich «s retry функция без внутреннего loop функция:

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 Спасибо за простейшее решение. –

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 Благодарим вас за более общее решение. –

+1

Отличный ответ, но вам не нужна внутренняя функция 'loop'. – senia

+0

@senia Right. Я сомневался, оценит ли «тело» рекурсивный вызов. – elbowich