Closures Views - UnquietCode/Closures-for-Java GitHub Wiki
Generally, you'll want to wrap your Closure object into a ClosureView. This hides some of the features like currying and accessing arguments. You'll need those when creating the Closure, but not when running it, and especially not when passing it around.
The ClosureView interfaces have three main goals: prevent modification to the Closure (through currying or accessing arguments), and more importantly to signify that this object might change at any time. A view is a direct view to the parent Closure, and changes made will be reflected in the views.
The third reason is that we want a unified form of the Closure classes to pass around and work with. Closure and ClosureView are those classes, and many other classes explicitly require them as parameters. All Closure classes can return a ClosureView representation of themselves.
public void basicViews() {
// Views are closures shielded by an interface. They are linked to their parent closure,
// however they cannot modify it nor retrieve it. Being views, if the original closure
// is modified, the view will be modified. This goes for currying as well as mutable
// arguments.
Closure1<Integer, Integer> magnify = new AbstractClosure1<Integer, Integer>() {
public Integer run(Integer val) {
return 10 * val;
}
};
// get a view
Closure1View<Integer, Integer> view1 = magnify.getView();
out(view1.run(12)); // 12 * 10 = 120
// of course, typing is not required
Closure1View view2 = magnify.getView();
out(view2.run(15)); // 12 * 10 = 120
// All closure classes can also return a ClosureView for unification purposes.
ClosureView view3 = magnify.toClosure();
out("\nExpected arguments: " + view3.getExpectedArgs());
out(view3.run(4)); // 4 * 10 = 40
out(view3.run(4, 8, 12, 20)); // additional arguments are ignored
}
public void instaView() {
// If the closure isn't needed, just leverage Java's syntax to ditch it.
// Remember though, the reference to the original closure is lost. No modifications
// can be made (except inside). This is also a good way to ensure that the view
// will never be adversely affected by the parent closure being mutated.
// This is NOT a defense against mutable arguments!
Closure1View<Integer, Integer> view1 = (new AbstractClosure1<Integer, Integer>() {
public Integer run(Integer val) {
return 10 * val;
}
}).getView();
out(view1.run(12)); // 12 * 10 = 120
}
public void fibonacciGenerator() {
// Just to clarify, while currying can be done from within, it is slow
// and definitely the wrong way to do it.
Closure0View<Long> fibonacci = (new AbstractClosure0<Long>() {
long a = 0;
long b = 1;
long c;
public Long run() {
long temp = a;
c = a + b;
a = b; // The correct way.
this.curry(2, c); // Why oh why would you do this?!
return temp;
}
}).getView();
for (int i=0; i < 20; ++i) {
outN(fibonacci.run());
outN(", ");
}
}
public void curryViewExample() {
// Views are tied to specific closures. If the original parent closure is modified, the
// view will respond accordingly (or perhaps the better term is 'unknowingly').
// Therefore it is best not to consider a view as immutable.
// Views will also reflect changes to mutable arguments in the original closure.
Map<String, Integer> names = new HashMap<String, Integer>();
Closure0<String> helloBot = new AbstractClosure0<String>(names, 5) {
String greeting = "Hello";
public String run() {
StringBuilder sb = new StringBuilder();
Map<String, Integer> names = a1();
for (Map.Entry<String, Integer> entry : names.entrySet()) {
for (int i=0; i < entry.getValue(); ++i) {
sb.append(greeting).append(" ").append(entry.getKey()).append(".\n");
}
sb.append("\n");
}
return sb.toString();
}
};
// using a view
Closure0View<String> view = helloBot.getView();
names.put("Alice", 2);
names.put("Bob", 3);
out(view.run());
// curried
names.clear();
names.put("Martin", 3);
names.put("Akheel", 2);
helloBot.curry(1, "Goodbye");
out(view.run()); // Same view, different output.
}