(defun foo (x) (let ((resource nil)) ; or #f or whatever (unwind-protect (progn (setq resource (obtain-resource pool)) (compute x resource)) (if (not (null resource)) (return-resource pool resource)))))There are two problems with this. The first is a minor race condition where the resource can be lost if an asynchronous interrupt is taken right after
obtain-resource
returns but just before the value is assigned to the variable. We'll ignore that problem for now. The bigger problem is that we cannot easily spot the separation between the parts of foo
concerned with resource management and the parts needed for computing the return value.What we want to do is abstract out the resource management:
(defun foo (x) (call-with-resource resource-pool (lambda (resource) (compute x resource)))) (defun call-with-resource (pool receiver) (let ((resource nil)) (unwind-protect (progn (setq resource (obtain-resource pool)) (funcall receiver resource)) (if (not (null resource)) (return-resource pool resource)))))(One could use macros here, too.)
Now we can see that
foo
just supplies an additional argument to a call to compute
. As an additional bonus, we have a simple idiom that correctly encapsulates the acquisition and release of our resource. Other code that has the ‘boilerplate’ needed to manage the resource can be simplified to use our wrapper. It is less likely that resources are dropped by accidentally copying the boilerplate incorrectly. This sort of wrapper code is frequently found in Lisp or Scheme.The puzzle is “How do you do this in Java?” To be more specific, suppose I have this code:
class Foo { ... public Answer method1 (int x) throws BadRequest { Resource resource = null; try { resource = pool.obtainResource(); return computeAnswer (x, resource); } finally { if (resource != null) pool.returnResource(resource); } } private Answer computeAnswer (int x, Resource r) throws BadRequest { .... } } class Bar { ... public void method2 (Boolean flag, String name) throws NoSuchEntry { ExpensiveObject resource = null; try { resource = expensiveObjectPool.obtainResource(); if (flag) resource.addName(name); doSomething(resource); } finally { if (resource != null) expensiveObjectPool.returnResource(resource); } } private void doSomething (ExpensiveObject expo) throws NoSuchEntry { .... } }and I want to write something more like this:
class Foo { ... public Answer method1 (int x) throws BadRequest { // WRAPPER CODE HERE return computeAnswer(x, resource); // POSSIBLE WRAPPER EPILOGUE } private Answer computeAnswer (int x, Resource r) throws BadRequest { .... } } class Bar { ... public void method2 (Boolean flag, String name) throws NoSuchEntry { // WRAPPER CODE HERE; if (flag) resource.addName(name); doSomething(resource); // POSSIBLE WRAPPER EPILOGUE HERE } private void doSomething (ExpensiveObject expo) throws NoSuchEntry { .... } }Where the wrapper and possible epilogue (if needed) are some relatively simple, easy to use constructs like a method call or inner class declaration or whatever.
Doesn't the callWithResource way still work? It's just clunkier:
ReplyDeletereturn pool.callWithResource(new Function<Resource, Answer>() {
Answer call(Resource r) { return computeAnswer(x, r); }
});
...with the obvious definitions of callWithResource and Function, and — oh, wait, that's a checked exception, isn't it. So we have to wrap it. Ouch:
public Answer method1 (int x) throws BadRequest {
try {
return pool.callWithResource(new Function<Resource, Answer>() {
Answer call(Resource r) {
try { return computeAnswer(x, r); }
catch (BadRequest e) { throw new RuntimeException(e); }
}
});
} catch (RuntimeException e) {
if (e.getCause() instanceof BadRequest)
throw e.getCause();
else
throw e;
}
}
...except that this could possibly mistakenly unwrap an exception someone else wrapped, so theoretically it should use a private wrapper class...
So let's assume something analogous for method2. That's the easy part. Now abstract it (e.g. with generics).
ReplyDeleteI'm surprised this works:
ReplyDeleteinterface FunctionThrowing<Arg, Res, Exn extends Throwable> {
Res call(Arg a) throws Exn;
}
So, if you duplicate callWithResource (and other high-order functions) with exception-throwing variations, you can make this work, at least until someone wants to throw two exceptions:
return pool.callWithResource(new FunctionThrowing<Resource, Answer, BadRequest>() {
public Answer call(Resource r) throws BadRequest { return computeAnswer(x, r); }
});
This maybe isn't an improvement. (And I'm starting to sympathize with the people who advocate using only RuntimeExceptions.)
Fortunately defining callWithResource is no more painful than calling it:
ReplyDelete<Result, Exn extends Throwable> Result callWithResource(FunctionThrowing<Resource, Result, Exn> f) throws Exn {
Resource r = null;
try {
r = obtainResource();
return f.call(r);
} finally {
if (r != null)
returnResource(r);
}
}