Saturday, July 07, 2007

Inline-Methods and Closure-Blocks

Yesterday, I browsed once again through Neal Gafter's Blog... It turned out to be a great mistake, as I got an idea which kept floating in my mind almost the whole night and day: A way to abstract loops or other code blocks without closures, but by inlining special methods around them (seems like the inverse of a closure to me).
Still not being 100% convinced by function types, especially their syntax, I tried to write myself: A proposal for extending the Java Language Specification

Actually, I think the idea behind it is great, although the proposal itself might not - I didn't sleep much last night and have/had plenty other things to work, so please forgive the small mistakes in the pseudo code and text. However, if there is interest, I'll try to find the time to revise it. So please let me know what you think about it!
[ Collaborators are welcome - its on Google Docs you know ;-) ]


Abstract:
This proposal shows that it is not necessary to add full blown closures or function types to the Java programming language in order to allow control abstraction.
The main motivation behind it is the agreement, that control abstraction cannot be implement with the current language constructs, such as anonymous classes - only syntactic improvements won't help. On the other side however, there is the fear that the introduction of nameless functions will remove one of the languages strongest points: its readability - raw type sequences everywhere. The proposed solution is to reuse existing syntax and add a single new semantic, which is simple-to-use, (relatively-)simple-to-implement, but yet very powerful. In short, the main idea is to inline special methods surrounding a (closure-)block, which remains in the lexical scope where is called.

Please have a look at the full document.



Usage-Example: Map enhanced for-loop.
(see document for the declaration of 'forEach')


Usage-Example: with a.k.a. closeAfter.
(see document for the declaration of 'with')


Usage-Example: blocked asynchronous reading.
(see document for the declaration of 'readFully')

7 comments:

Neal Gafter said...

See http://gafter.blogspot.com/2007/03/closures-for-organizing-your-code.html for an explanation of why this falls short of the expressive power of closures.

Stefan Schulz said...

Your ideas remind me of some earlier versions and notices Stephen, Ricky, and I had written down on realizing Java Control Abstraction (Control Invocation). What you describe as "Inlining" is quite close to "Macros". It provides a solution for selected application scenarios, where single level patterns are found.
Inlining introduces a new level of dependency into your application: a dependency on foreign method's code. Even though the API of an inlined method did not change, a code change within such a method will have to cause each usage to be recompiled. It could be an interesting research, how to identify such changes efficiently (especially when using external libraries).
Another problem is the use of annotations as an instrument to change the semantic of a method. It's no longer a method actually, as it is not invocable, but a code pattern (macro) to be copied at compile time to the using spot. Hence, the annotation changes the way a method is handled, compiled, and applied, which is quite beyond what annotations are meant for. I'd rather recommend to introduce a (local) keyword here to completely distinct from Java methods.
The use of labels for levels of loops seems as an indication that code introspection is necessary to make use of so defined inline methods. And, of course, it is impossible to nest looping inline methods, as they would introduce the same labels.
You are right, it does not introduce closures and their complexity. But it also does not provide its power.

Unknown said...

@Neal:

Thanks for commenting! Of course I'm aware that closures are potentially even more expressive. In fact inline-methods are limiting in two ways compared to a method with a closure passed in as argument:

1. A (closure-)block has to be specified after each call - where closures can be reused. The the lexical scope is exactly the one of the caller, which is also where they are defined. Therefore the not only the return type, but also break and continue are well defined. I'm not 100% sure about real closures, but even if return is, how does it work? At least it looks more confusing to me. Please let me recap and comment on it:

private void findAllJeffersoniansInternal(Graph g, {Jeffersonian => void} foundJeffersonian) {
// complex recursive algorithm here
Jeffersonian found = ...;
foundJeffersonian.invoke(found); // no return AND function signature is void !
// more complex recursion here
}

public boolean hasJeffersonian(Graph g) {
findAllJeffersoniansInternal(Jeffersonian j : g) {
return true; // now, this returns from the marked line above, that mens the signature of the closure is ( => boolean)
}
return false;
}

Let me emphasize that as used, a closure with a boolean return type (signature { => boolean } is needed, since it returns from 'hasJeffersonian'.
The contradiction however lies in the signature of 'findAllJeffersoniansInternal' where the closure argument has a return type of void.
(And please don't argue with covariant returns since, I can pick another example with contravariant ones.)

Fortunately, In my proposal this problem does never occur since a closure-block does not have a return type, because only a parameter type-list is specified by an inline-method.

2. Another difference is that there must be exactly one insert-point inside the inline-method, which behaves much like the call to a closure (in fact it insert the therefore so-called closure-block). Therefore it always can be determined whether the block is inside a loop or not, which is needed to allow continue and break in loops.


Again, thanks for commenting. I hope here more feedback from you on the proposal, but please remember I don't doubt in the power of closures. In contrast, I believe they are very much - but this may come at the cost of runtime errors and readability.

Unknown said...

@Stefan

Thanks a lot for your detailed points! I may reply to these briefly:

--------- Backward Dependency --------
Your right, the in theory the proposed would introduce a new level of dependency, but I don't think this is problematic. Java is interpreted/JIT-compiled and the byte code of the class defining an inline-method should contain all information to wrap it around the closure-block. Another 1 round compile-tim approach could simulate everything with anonymous methods and code/macro insertion around, however encoding all the various control-flow statements in the return type or properties will certainly make debugging almost impossible.

--------- The Annotation does not change the semantic --------
A short note about @Inline: It does not intend to change the semantic itself, in contrast I say that it is optional. Inline-methods will be recognized by their extended message header syntax, which allows the compiler to treat it semantically different.

1. An optional marker like @Override for inline-methods, at compilation validated if present otherwise added.
[The compiler can distinguish them because of the method declarator: Identifier( TypeParameterList_opt : FormalParameterList_opt } ]

2. A Container for the following information: whether one ore more loops surround the insertion-point inside the method's body, and if so, the labels used on them. Javac itself, automatic code generators and and tools like JavaDoc may query them.

Of course, you are right that might not be possible to validate (and surely to add) the annotation using an AnnotationProcessor and javax.lang.model exclusively, but I believe javac must have some kind of node representation for loops containing the labels.

------- Labels and Loops -------
I wrote the examples with label only to show that even loops with labels can be inlined and it is absolutely not impossible to nest looping inline methods. if an inline-method contains a loop with label 'x' and its call would be surrounded with another loop also labeled with "x" a compile time error will occur as if you use the same identifier on nested loops without utilizing inline-methods.


---
Please find my notes in the previous comment about the power of closures.

Stefan Schulz said...

"The Annotation does not change the semantic"
Well, as far as I understood, one cannot use break and continue if the annotation does not tell the inline to hide a loop. So the use of an inline depends on the macro.

"if an inline-method contains a loop with label 'x' and its call would be surrounded with another loop also labeled with "x" a compile time error will occur as if you use the same identifier on nested loops without utilizing inline-methods."
That's exactly what I meant. It is, of course, limiting and irritating, as one needs introspection to become aware of the (compilation) problem. Using a library with no source code available, this will be at least surprising to the developer.

Unknown said...

Stefan, can you please tell where exactly lis the problem with this:

1. I define an inline-Method with a loop labbeled x:

void loopX(:int times)
{
x:
for(int i=0; i<times; i++) #();
}

2.the compiler adds the annotation

@Inline(loop="true", lables={"x"})
void loopX(:int times)

3. I give you the class file.

4. You try use it within a loop, also labeled "x"

x:
for(int i=0; i<10; i++)
{
loopX(:i) { if(i==5) break; }
}

5. The compiler looks up the @Inline annotation of loopX to check for its labels and accordingly will print and error.

Thats not much more to check by the compiler in addition to method signature.


Also I don't see the re-compile problem, when I program starts it will be compiled from byte to machine code, as part of this process the method gets inlined where used - no source code, only the byte code is needed.

Stefan Schulz said...

You are right. I was a bit confused because of the term "compile", where you are talking about the JVM Hotspot compiler.
Regarding loops etc., one would not need explicit labels then, I think, as there are only two cases that make sense to cover (inner loop continue and outer loop break). Using byte code copying, this can be solved by the JVM.
Operating on byte colde level, I cannot see a use for the annotation, actually. All necessary information could be put into the class files method representation directly.
As I said, there is few difference to Stephen and mine first idea on closures or rather macros for Java. And there, too, is a (slightly outdated) project called JSE (Java syntactic expander) going in this direction.