3

I ran into this while using a PDF library, but there have been plenty of other occasions in which I would have something like this useful.

There are many situations in which you have a resource (that needs to be closed) and you use these resources for obtaining objects that are only valid as long as the resource is open and hasn't been released yet.

Let's say the b reference in the code below is only valid while a is open:

val a = open()
try {
  val b = a.someObject()
} finally {
  a.close()
}

Now, this code is fine, but this code isn't:

val b = {
  val a = open()
  try {
    a.someObject()
  } finally {
    a.close()
  }
}

With that code I would have a reference to something of resource a, while a is no longer open.

Ideally I'd like to have something like this:

// Nothing producing an instance of A yet, but just capturing the way A needs
// to be opened.
a = Safe(open()) // Safe[A]

// Just building a function that opens a and extracts b, returning a Safe[B]
val b = a.map(_.someObject()) // Safe[B]

// Shouldn't compile since B is not safe to extract without being in the scope 
// of an open A.
b.extract 

// The c variable will hold something that is able to exist outside the scope of 
// an open A.
val c = b.map(_.toString)

// So this should compile
c.extract
Wilfred Springer
  • 10,869
  • 4
  • 55
  • 69
  • When you know that exceptions are thrown when an error happens, then use `scala.util.Try`. – kiritsuku Oct 16 '13 at 10:06
  • Once I created something that mimics C#'s `using` construct (mostly used in my Android projects, where there is no try-with-resources), [it looks like this](https://gist.github.com/Patryczek/e9bba78081f79f63bdc4). Then you can do something like `using(new CloseableObject()){ c => // use the object now via 'c'}` When you get out of `using` function's scope, the resource is cleaned up automatically. – Patryk Ćwiek Oct 16 '13 at 10:11
  • Patryk the problem is not that I don't know how to automatically close resources. The problem is that you always run the risk of writing code that tries to do something to resources that have already been closed. Objects that rely on your resources being open might leak out of your resource opening and closing block. – Wilfred Springer Oct 16 '13 at 10:13
  • @WilfredSpringer True enough. If you're after that, personally I can't help you, although passing around objects that *might* depend on open resources (be it file system, DB access etc) is asking for trouble and, IMO, is best avoided if possible :) – Patryk Ćwiek Oct 16 '13 at 10:15
  • sschaef, that's just another way to write a `try {…} finally {…}` block, right? The problem is not that I don't have enough ways to write that in Scala. The problem is that I don't want objects from leaking out of my try finally block if they are only valid as long as my resource is open. – Wilfred Springer Oct 16 '13 at 10:15
  • Patryk, I know it is a problem, and that's exactly what I want to prevent: I want the compiler to prevent me from writing code like that. – Wilfred Springer Oct 16 '13 at 10:16
  • Yeah, that is what `Try` is for – kiritsuku Oct 16 '13 at 10:18

1 Answers1

2

In your example, it is typical that a exception is thrown when you access a stream that is already closed. There exists util.Try, which is made exactly for this use case:

scala> import scala.util._
import scala.util._

scala> val s = Try(io.Source.fromFile("exists"))
s: scala.util.Try[scala.io.BufferedSource] = Success(non-empty iterator)

// returns a safe value
scala> s.map(_.getLines().toList)
res21: scala.util.Try[List[String]] = Success(List(hello))

scala> s.map(_.close())
res22: scala.util.Try[Unit] = Success(())

scala> val data = s.map(_.getLines().toList)
data: scala.util.Try[List[String]] = Failure(java.io.IOException: Stream Closed)

// not safe anymore, thus you won't get access to the data with map
scala> data.map(_.length)
res24: scala.util.Try[Int] = Failure(java.io.IOException: Stream Closed)

Like other monads, Try gives you a compile time guarantee to not access the wrapped value directly: you have to compose higher order functions to operate on its value.

kiritsuku
  • 52,967
  • 18
  • 114
  • 136
  • Interesting. I didn't know about this class. However it doesn't give a _compile time_ guarantee that you don't access something that is not safe to access, does it? You will only find out you're doing something wrong if at runtime you get a `Failure` – Wilfred Springer Oct 16 '13 at 10:32
  • You can't access `Failure` or `Success` directly (unless you extract the value by yourself, which is discouraged), that's how monads work. Take the `Option` monad, it is the same concept: [What is the point of the class Option\[T\]?](http://stackoverflow.com/questions/2079170/what-is-the-point-of-the-class-optiont) – kiritsuku Oct 16 '13 at 10:42