views:

266

answers:

6
+7  Q: 

try block scope

I'm unhappy with the rule about variable scope in a try block not being shared with associated catch and finally blocks. Specifically it leads to code like the following:

var v: VType = null

try {
  v = new VType()
}
catch {
  case e => // handle VType constructor failure (can reference v)
}
finally {
  // can reference v.
}

As opposed to:

try {
  val v = new VType()
}
catch {
  case e => // handle VType constructor failure (can reference v)
}
finally {
  // can reference v.
}

Can anyone please explain or justify why this rule from Java persists?

and / or is there hope that this could change?

Thanks!

UPDATE

Many thanks for all the responses to date.

The consensus seems to imply "just get on with it" and I'm starting to conclude that perhaps technically what I want is either unsound, not worth the effort or hard to achieve.

I like Rex Kerr's answer but how would the original code above be wrapped in a method call without introducing a local var in the method body?

My own efforts weren't too good, using a by-name parameter to delay construction until safely in the try block works but still doesn't give me access to the constructed (or not) object in the catch or finally blocks.

+6  A: 

How would this code work?

try
{
    int i = 0;

    // Do stuff...

    Foo x = new Foo();

    // Do more stuff...

    Bar y = new Bar();
}
catch
{
    // Print the values of i, x, and y.
}

What are the values of i, x, and y? Did y even get declared before we landed in the catch block?

Jon B
Interesting point, thanks, however I think scope is a compile time issue, if so the vars or vals would be known even if their values are undetermined.
Don Mackenzie
@Don - I think you are fundamentally mis-understanding scope in this regard; scope allows the compiler to reason about what *must* be initialized to some value at a point in your code. In Jon B's example, he demonstrates that the compiler cannot be sure that any of `i`, `x` or `y` have been initialized
oxbow_lakes
@oxbowlakes, I take your point, undetermined values are unacceptable to the compiler. I was just thinking along the lines of the defaults for declared members in Java (I know it doesn't apply to locals) might have been possible to apply in try blocks. I feel it isn't impossible to achieve but I'm getting the impression it's not well regarded.
Don Mackenzie
+4  A: 

The exception concept is not a subroutine of the try block, it is an alternate code flow. That makes at try-catch control block more like an "if anything untoward happens" then insert these (catch) lines in the current position of the try block, as needed.

With that in mind, it isn't clear if Val v = Type(); is going to be defined or not because the exception could (theoretically) be thrown before Val v = Type(); is evaluated. Yes, Val v is the first line in the block, but there are JVM errors which could be thrown before it.

Finally is another code construct which adds and alternate, but required, code flow the end of leaving the try-catch construct. Again, we have no idea how much (if any) of the try block was evaluated before the finally block was called, so we cannot depend on the declared variables within that block.

The only alternative left (now that we cannot use try block variables, due to their uncertainty of existence) is to use variable outside of the entire try-catch-finally construct for communications between the individual code blocks.

Does it suck? Maybe a little. Do we have anything that's better? Probably not. Putting the variable declarations outside of the block makes it obvious that the variables will have been defined prior to whatever control structure you process through in a try-catch-finally scenario.

Edwin Buck
@Edwin Buck, thanks for the answer, I'm still not convinced that the try block vals and vars are not available, their values may or may not be determined, but I think that it is a useful assumption to make in exception handling unless variable state is provably known. As far as I know, the catch block is reached by a non-local jump from the location in the try block where the exception is raised. The catch block has access to the complete scope upto and including that of the block containing the try block, so why not the try block too?
Don Mackenzie
How can a compiler check for "may or may not" be available? The only option is to consider the variables unavailable, as any consideration of them being available is not guaranteed.
Edwin Buck
+3  A: 

If your main concern is that v should be immutable, you might get close to what you want with:

case class VType(name: String) { 
   // ... maybe throw an exception ...
}

val v = LazyVal(() => new VType())
try {
   // do stuff with v
   println(v.name) // implicitly converts LazyVal[VType] to VType

   // do other unsafe stuff
} catch {
   case e => // handle VType constructor failure
   // can reference v after verifying v.isInitialized
} finally {
   // can reference v after verifying v.isInitialized
   if (v.isInitialized) v.safelyReleaseResources
}

where LazyVal is defined as

/**
 * Based on DelayedLazyVal in the standard library
 */
class LazyVal[T](f: () => T) {
   @volatile private[this] var _inited = false
   private[this] lazy val complete = {
      val v = f()
      _inited = true
      v
   }

   /** Whether the computation is complete.
    *
    *  @return true if the computation is complete.
    */
   def isInitialized = _inited

   /** The result of f().
    *
    *  @return the result
    */
   def apply(): T = complete
}

object LazyVal {
   def apply[T](f: () => T) = new LazyVal(f)
   implicit def lazyval2val[T](l: LazyVal[T]): T = l()
}

It would be nice if we could use lazy val v = new VType(), but AFAIK there is no mechanism to safely determine whether a lazy val has been initialized.

Aaron Novstrup
Thanks for reply, this elegant technique addresses what made me unhappy about this issue in the first place but I'm curious about the scope restriction.
Don Mackenzie
Changing the scoping rules for try-catch-finally blocks would make it inconsistent with other blocks (if-else, nested functions, etc), and, as Rex pointed out, would lead to potential surprises when accessing a variable that may not have been initialized.
Aaron Novstrup
There's no need to replicate a lazy initialization mechanism, just use `lazy val v = f` and, if you still want it, `def apply(): T = v`.
Randall Schulz
@Randall Is there a built-in mechanism to safely test whether a `lazy val` has been instantiated? I'm thinking of a case where `f` throws an exception, and therefore the `isDone` method is required in order to safely access the lazy value in the `catch` or `finally` block. Or maybe I'm misunderstanding your suggestion?
Aaron Novstrup
No, `lazy val` is opaque. I overlooked what you were using your `LazyVal` for.
Randall Schulz
+11  A: 

You might be thinking about the problem the wrong way. Why do you want so much stuff in your try/catch/finally block? In your code,

try { val v = new VType() }

the exception could be thrown before you get v back, so you can't safely reference v. But if you can't reference v, then what can you do on the finally side that won't break or throw its own exception or have some other ill-defined behavior? What if you create v but fail to create w, but disposal requires having w as well? (Or doesn't?) It ends up being a mess.

But if you're coming from Java, there are a few things that can help you write try/catch/finally blocks in a sensible way.

One thing you can do is catch certain classes of exceptions and turn them into options instead:

def s2a(s: String) = try { Some(s.toInt) } catch { case nfe: NumberFormatException => None}

Another thing you can do is to create your own resource manager

def enclosed[C <: { def close() }](c: C)(f: C => Unit) {
  try { f(c) } finally { c.close() }
}
enclosed(new FileInputStream(myFile))(fis => {
  fis.read...
}

Or you can create your own shut-down-and-escape-safely method within another method:

val r = valuableOpenResource()
def attempt[F](f: => F) = {
  try { f } catch { case re: ReasonableException => r.close() throw re }
}
doSomethingSafe()
attempt( doSomethingDangerous() )
doSomethingElseSafe()
r.close()

Between these different ways of handling things, I've not had much need to create vars to hold variables that I want to clean up later or otherwise deal with in catch or finally blocks.

Rex Kerr
@Rex Kerr, many thanks for your advice and examples, as far a style is concerned I can't argue. It just seems like this is the one that got away as far as a Scala version of a Java idiom (if you know what I mean) and try blocks are something to be hidden away like the while keyword.
Don Mackenzie
One suggestion: instead of using {def close() } in enclosed() method, we can use java.io.Closeable http://download.oracle.com/javase/6/docs/api/java/io/Closeable.html
Marimuthu Madasamy
@Marimuthu Using { def close() } may be preferable because it allows you to manage resources that satisfy the closeable contract but do not implement the `java.io.Closeable` interface.
Aaron Novstrup
No need to make your own resource manager, see: http://github.com/jsuereth/scala-arm It will also correctly use java.io.Closeable OR reflection, depending on the need and the type.
jsuereth
+1  A: 

Your example does not concretize why you need the finally clause. If VType is e.g. a resource that needs do be closed, you could do it one of the following ways.

1) You want to reference v after using it throws an exception:

try {
  val v = new VType // may throw
  try {
    v.someThing  // may throw
  }
  catch {
    case ex => println("Error on doing something with v :" + v + ex) // or whatever
  }
  finally {
    v.close()
  }
}
catch {
  case ex => println("Error on getting or closing v: " + ex)  // v might not be constructed
}

2) You don't care about v in the catch clause:

try {
  val v = new VType // may throw
  try {
    v.someThing  // may throw
  }
  finally {
    v.close()
  }
}
catch {
  case ex => println("Error on either operation: " + ex)
}

In either case, you get rid of the var.

Knut Arne Vedaa
+2  A: 

Here's another alternative:

object Guard {
    type Closing = {def close:Unit}

    var guarded: Stack[Set[Closing]] = Stack()
    def unapply(c: Closing) = {
      guarded.push(guarded.pop + c)
      Some(c)
    }

    private def close {println("Closing"); guarded.head.foreach{c => c.close}}
    private def down {println("Adding Set"); guarded.push(Set())}
    private def up {println("Removing Set"); guarded.pop}

    def carefully(f: => Unit) {
      down
      try {f}
      finally {close; up}
    }
}

You can use it like this:

import Guard.carefully

class File {def close {println("Closed File")}}
class BadFile {def close {println("Closed Bad File")}; throw new Exception("BadFile failed")}

carefully {
  val Guard(f) = new File
  val Guard(g) = new File
  val Guard(h) = new BadFile
}

which results in

Adding Set

Closing

Closed File

Closed File

java.lang.Exception: BadFile failed

So the first two files are created, then when the third constructor fails, the first two are automatically closed. All files are values.

Magnus
@Magnus, this is impressive and very creative. I particularly like the extractor technique to gain the vals and how any exceptions just propagate out of carefully.
Don Mackenzie
Thanks, one caveat; it's not threadsafe as written, so handle with care in an application that uses actors, for example.
Magnus