20

Scala in Depth demonstrates the Loaner Pattern:

def readFile[T](f: File)(handler: FileInputStream => T): T = {
  val resource = new java.io.FileInputStream(f)
  try {
    handler(resource)
  } finally {
      resource.close()
  }
}

Example usage:

readFile(new java.io.File("test.txt")) { input =>
   println(input.readByte)
}

This code appears simple and clear. What is an "anti-pattern" of the Loaner pattern in Scala so that I know how to avoid it?

jub0bs
  • 60,866
  • 25
  • 183
  • 186
Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384
  • Note that it fails on `Future`: `readFile(...) { in => Future { println(in.readByte) } }`. `in.readByte` could be executed after `close`. – senia Dec 24 '13 at 14:04
  • interesting. so, to avoid this problem, simply don't use `Future`'s with this pattern? – Kevin Meredith Dec 24 '13 at 14:07
  • Also don't return `in` or anything that could contain `in`: `readFile(...){in => b: Byte => input.readByte + b}`. (Note that there is no method `readByte` in FileInputStream). – senia Dec 24 '13 at 14:15
  • 1
    There is an implementation of this pattern in [truecommons.io](http://illegalexception.schlichtherle.de/2012/07/19/try-with-resources-for-scala/). – 2rs2ts Dec 29 '14 at 19:25
  • 1
    From Scala 2.13 onwards there is no need to write your own loan pattern implementation because there is a [Using](https://scala-lang.org/files/archive/api/2.13.0/scala/util/Using%24.html) utility. def readFile[T](f: File)(handler: FileInputStream => T): T = Using.resource(new FileInputStream(f))(handler) – Philip Schwarz Jul 13 '19 at 07:15

2 Answers2

34

Make sure that whatever you compute is evaluated eagerly and no longer depends on the resource. Scala makes lazy computation fairly easy. For instance, if you wrap scala.io.Source.fromFile in this way, you might try

readFile("test.txt")(_.getLines)

Unfortunately, this doesn't work because getLines is lazy (returns an iterator). And Scala doesn't have any great way to indicate which methods are lazy and which are not. So you just have to know (docs will tend to tell you), and you have to actually do the work before returning:

readFile("test.txt")(_.getLines.toVector)

Overall, it's a very useful pattern. Just make sure that all accesses to the resource are completed before exiting the block (so no uncompleted futures, no lazy vals that depend on the resource, no iterators, no returning the resource itself, no streams that haven't been fully read, etc.; of course any of these things are okay if they do not depend on the open resource but only on some fully-computed quantity based upon the resource).

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
13

With the Loan pattern it is important to know when the "bit" of code that is going to actually call your loaned resource is going to use it.

If you want to return a future from a loan pattern I advise to not create it inside the function that is passed to the loan pattern function.

Don't write

readFile("text.file")(future { doSomething })

but do:

future { readFile("text.file")( doSomething ) }

what I usually do is that I define two types of loan pattern functions: Synchronous and Async

So in your case I would have:

def asyncReadFile[T](f: File)(handler: FileInputStream => T): Future[T] = {
  future{
    readFile(f)(handler)
  }
}

This way you avoid calling closed resources. And you reuse your already tested and hopefully correct code of the Synchronous function.

le-doude
  • 3,345
  • 2
  • 25
  • 55