Friday, October 2, 2020

Using: Did they forget something?

I was upgrading one of my open-source projects (TableParser) to Scala 2.13 and I realized that I could now use the new "Using" resource management utility.

But, it seems they missed a necessary signature. Let's review:

The basic signature for normal (single-resource use) is this apply method (I've used a context bound to save a bit of space, and I also renamed the first parameter to r):

def apply[R : ReleasableA](r: => R)(f: (R) => A)Try[A

 = Try { Using.resource(r)(f) }

As you can see, it invokes the other single-resource signature resource:

def resource[R : ReleasableA](r: R)(f: (R) => A)A

The difference between these signatures is somewhat subtle: apply returns a Try[A] whereas resource returns A. But, there's another, more subtle yet more significant difference: the r parameter in apply is call-by-name, whereas in resource, it's call-by-value. This implies that, if an exception is thrown while evaluating r for resource, it will indeed be thrown and not wrapped in a Failure. So, the resource signature is not suitable if there's a possibility of an exception being thrown while evaluating the r parameter.

If that's the case, then you must use the apply signature. However, suppose that your type A is of the form Try[X],  then the result type will be Try[Try[X]]. That's a bit ugly.

What is needed is a method such as the following (this signature is to apply, as flatMap is to map):

def safeResource[R: Releasable, A](resource: => R)(f: R => Try[A]): Try[A] = Using(resource)(f).flatten

An alternative expression for the body of this method would be the following:

try { Using.resource(resource)(f) } catch { case NonFatal(e) => Failure(e) }  

You could, of course, add the flatten method call yourself, but that's not the most elegant. So, I think it would be nice to have this signature added to the Using object in the Scala library.

No comments:

Post a Comment