Scala while(true) type mismatch? Infinite loop in scala?

后端 未结 6 2040
感情败类
感情败类 2021-02-14 21:01

Why following code

def doSomething() = \"Something\"

var availableRetries: Int = 10

def process(): String = {
  while (true) {
    availableRetries -= 1
    tr         


        
相关标签:
6条回答
  • 2021-02-14 21:33

    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"
    }
    
    0 讨论(0)
  • 2021-02-14 21:35

    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).

    0 讨论(0)
  • 2021-02-14 21:40

    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
    }
    
    0 讨论(0)
  • 2021-02-14 21:48

    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
    
    0 讨论(0)
  • 2021-02-14 21:55

    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 
      } 
    } 
    
    0 讨论(0)
  • 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 讨论(0)
提交回复
热议问题