9

Why following code

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
        }
      }
    }
  }
}

produces following compiler error

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

?

This works ok in C#. The while loops forever, so it cannot terminate, therefore it cannot result something else than string. Or how to make infinite loop in Scala?

TN.
  • 18,874
  • 30
  • 99
  • 157

6 Answers6

17

Unlike C# (and Java and C and C++) which are statement based languages, Scala is an expression based language. That's mostly a big plus in terms of composibility and readability but in this case the difference has bitten you.

A Scala method implicitly returns the value of the last expression in the method

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

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

In Scala pretty much everything is an expression. Things that look like statements are still expressions that return a value of a type called Unit. The value can be written as ().

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

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

No compiler for a Turing-equivalent language can detect all non-terminating loops (c.f. Turing halting problem) so most compilers do very little work to detect any. In this case the type of "while(someCondition){...}" is Unit no matter what someCondition is, even if it's the constant true.

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

Scala determines that the declared return type (String) isn't compatible with the actual return type (Unit), which is the type of the last expression (while...)

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

Answer: add an exception at the end

scala> def wtfOk() : String = {
     | while(true){}
     | error("seriously, wtf? how did I get here?")
     | }
wtfOk: ()String
James Iry
  • 19,367
  • 3
  • 64
  • 56
  • +1 Thank your for deep explanation. But I thing that Scala compiler should resolve constant expressions (as C# compiler does). – TN. May 30 '12 at 08:36
  • 1
    In this case, C# isn't being smart about the constant expression. It just treats "while" as a non-returning statement. You can prove that for yourself by writing a similar C# construct where the condition in the "while" is not constant - like say reading a variable that's provided by the user. C# just sees "while" very differently from Scala. C# analyzes the code by saying the only explicit "return" returns a string. Scala sees "while" as the last, and therefore returning, expression. That's the difference between an expression based lang (like Scala) and a statement based lang like C#. – James Iry May 30 '12 at 14:04
  • 4
    In theory, the language could have a special case to recognize while(true){...} as having type Nothing, rather than type Unit. Then the example would work fine, as Nothing is a subtype of String (and everything else). I suggested this on mailing a couple years back, but the general response was that it didn't occur often enough to be worth changing the language. If you do this sort of thing often, it's easy enough to create a "forever" method as you describe, but have it have return type Nothing rather than Unit. – Dave Griffith May 30 '12 at 18:17
  • 1
    @James I understand what you mean, but I do not think that your first sentence is true. Try following in C#: `int T() { while (true) return 5; }` This compiles and returns 5. However: `int T(bool f) { while (f) return 5; }` This produces an error: `not all code paths return a value`. – TN. May 31 '12 at 07:59
9

Functional way to define an infinite loop is recursion:

@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 function without inner loop function:

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 
  } 
} 
senia
  • 37,745
  • 4
  • 88
  • 129
5

The compiler isn't smart enough to know that you can't exit the while loop, unfortunately. It's easy to trick, though, even if you can't sensibly generate a member of the return type--just throw an exception.

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

Now the compiler will realize that even if it escapes the while loop, it can't return a value there, so it doesn't worry that the while loop has return type Unit (i.e. does not return a value).

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
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()
}
elbowich
  • 1,941
  • 1
  • 13
  • 12
2

Based on senia, elbowich and dave's solutions I used following:

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

Which can be then used as elbowich and dave's solutions:

retry(3) {
  // some code
}
Community
  • 1
  • 1
TN.
  • 18,874
  • 30
  • 99
  • 157
  • Note that this does not work if for any reason you cannot make the function tail-recursive (e.g. you might call yourself multiple times). – Mikaël Mayer Nov 28 '16 at 16:57
1

edit: I just noticed the actual return statement. The return statement inside the while loop will be ignored. For example, in the 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"}
                        ^  

You told the compiler that the process() method returns a String, but your method body is just a while loop, which doesn't return anything (it's a Unit, or a Java void). Either change the return type or add a String after the while loop.

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

or

def process(): String = {
  while(true){...}
  "done"
}
Dylan
  • 13,645
  • 3
  • 40
  • 67