Reputation: 3206
Following these examples and especially this code:
object Control {
def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B =
try {
f(resource)
} finally {
resource.close()
}
}
...
using(io.Source.fromFile("example.txt")) { source => { .....
I wanted to extend the using
method so instead of a type which implements close
it receives a string (filename), a function to open a source, and the processing function. In this way, I would avoid the exception which would be thrown in the above example in case the given file does not exist.
So I ended up with this code:
object Control
{
def using[A <: { def close(): Unit }, B](opener: String => A)(name:String)(func: A => B): Unit =
{
var resource:A
// ^ Error: 'Block cannot contain declarations'
try
{
resource = opener(name)
func(resource)
}
catch
{
case e: (_) => println(s"Failed to open resource '${name}' (${e})")
}
finally
{
println("Closing file ...")
resource.close()
}
}
}
So I am defining a method, which takes as first parameter an opener
-function, which receives a string, and returns an object which implements close
, a string (for the opener function), and a processing function.
However it won't let me declare the resource
variable outside of the try-catch
block (so I can reach it in the finally
block). It will work if I just put it into the try
block like var resource:A = opener(name)
, however then I cannot reach resource
in the finally
block.
How can I solve it? I have to say that I am still a beginner in Scala, so I am a bit lost here.
Upvotes: 0
Views: 389
Reputation: 12804
Here is a revised example that you can also run on Scastie:
import scala.util.control.NonFatal
import scala.language.reflectiveCalls
type Resource = { def close(): Unit }
def using[A <: Resource, B](opener: String => A)(name: String)(func: A => B): Unit = {
var resource = null.asInstanceOf[A]
try {
resource = opener(name)
func(resource)
} catch {
case NonFatal(e) => println(s"Failed to open resource '${name}' (${e.getMessage})")
} finally {
println("Closing resource...")
resource.close()
}
}
final class SomeKindOfResource(n: String) {
def use(): Int = n.toInt
def close(): Unit = {}
}
using(new SomeKindOfResource(_))("42")(n => println(n.use()))
using(new SomeKindOfResource(_))("NaN")(n => println(n.use()))
The piece that you were lacking is that initialization:
var resource = null.asInstanceOf[A]
Please note that despite what you may think, this does not throw a NullPointerException
. You can read more about it here.
I've added a few more things you may be interested in:
explicitly importing scala.language.reflectiveCalls
: structural typing is achieved at runtime through reflective calls (on the JVM, at least) and the compiler will tell you at compile time with a warning
naming the { def close(): Unit }
to something that makes it a little bit more readable in the method signature using type
using NonFatal
to handle exception (you can read more about it here)
Upvotes: 1