Scott S. McCoy
Scott S. McCoy

Reputation: 1147

Why is a return statement required to allow this while statement to be evaluated properly?

Why is a return statement required to allow this while statement to be evaluated properly? The following statement allows

import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.BufferedReader
import java.io.InputStreamReader

trait Closeable {
  def close ()
}

trait ManagedCloseable extends Closeable {
  def use (code: () => Unit) {
    try {
      code()
    }
    finally {
      this.close()
    }
  }
}

class CloseableInputStream (stream: InputStream)
extends InputStream with ManagedCloseable {
  def read = stream.read
}

object autoclose extends App {
  implicit def inputStreamToClosable (stream: InputStream):
    CloseableInputStream = new CloseableInputStream(stream)

  override
  def main (args: Array[String]) {
    val test = new FileInputStream(new File("test.txt"))

    test use {
      val reader = new BufferedReader(new InputStreamReader(test))

      var input: String = reader.readLine

      while (input != null) {
        println(input)
        input = reader.readLine
      }
    }
  }
}

This produces the following error from scalac:

autoclose.scala:40: error: type mismatch;
 found   : Unit
 required: () => Unit
      while (input != null) {
      ^
one error found

It appears that it's attempting to treat the block following the use as an inline statement rather than a lambda, but I'm not exactly sure why. Adding return after the while alleviates the error:

test use {
  val reader = new BufferedReader(new InputStreamReader(test))

  var input: String = reader.readLine

  while (input != null) {
    println(input)
    input = reader.readLine
  }

  return
}

And the application runs as expected. Can anyone describe to me what is going on there exactly? This seems as though it should be a bug. It's been persistent across many versions of Scala though (tested 2.8.0, 2.9.0, 2.9.1)

Upvotes: 4

Views: 241

Answers (4)

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297155

To go the heart of the matter, blocks are not lambdas. A block in Scala is a scope delimiter, nothing more.

If you had written

test use { () =>
  val reader = new BufferedReader(new InputStreamReader(test))

  var input: String = reader.readLine

  while (input != null) {
    println(input)
    input = reader.readLine
  }
}

Then you'd have a function (indicated by () =>) which is delimited by the block.

If use had been declared as

def use (code: => Unit) {

Then the syntax you used would work, but not because of any lambda thingy. That syntax indicates the parameter is passed by name, which, roughly speaking, means you'd take the whole expression passed as parameter (ie, the whole block), and substitute it for code inside the body of use. The type of code would be Unit, not a function, but the parameter would not be passed by value.

Upvotes: 3

retronym
retronym

Reputation: 55028

return or return expr has the type Nothing. You can substitute this for any type, as it never yields a value to the surrounding expression, instead it returns control to the caller.

In your program, it masquerades as the required type () => Unit.

Here's an occasionally convenient use for that (although you might be tarnished as unidiomatic if you use it too often, don't tell anyone you heard this from me!)

def foo(a: Option[Int]): Int = {
  val aa: Int = a.getOrElse(return 0)
  aa * 2
}

For the record, you should probably write:

def foo(a: Option[Int]): Int =
  a.map(_ * 2).getOrElse(0)      

You can get an insight into the mind of the compiler by checking the output of scala -Xprint:typer -e <one-liner>. Add -Ytyper-debug if you like sifting through the reams of output!

scala210 -Ytyper-debug -Xprint:typer -e 'def foo: Any = {val x: () => Any = { return }}'
... elided ...
   typed return (): Nothing
    adapted return (): Nothing to () => Any,

Upvotes: 1

Luigi Plinge
Luigi Plinge

Reputation: 51109

() => Unit is the type of a Function0 object, and you've required the use expression to be of that type, which it obviously isn't. => Unit is a by name parameter, which you should use instead.

You might find my answer to this question useful.

Upvotes: 3

pedrofurla
pedrofurla

Reputation: 12783

That's because it's use is declared as () => Unit, so the compiler expects the block you are giving use to return something that satisfies this signature.

It seems that what you want is to turn the entire block into a by-name parameter, to do so change def use (code : () => Unit) to def use (code : => Unit).

Upvotes: 8

Related Questions