Lambda as shorthand for anonymous class?

33 views
Skip to first unread message

czap...@gmail.com

unread,
Sep 15, 2014, 7:58:41 AM9/15/14
to yeti...@googlegroups.com
I wanted to ask if you would consider adding a feature like described below to Yeti, or accepting a patch/pull-request for it? I'm not sure if I'd be able to implement this, and whether I will try at all in the end, but I wanted to ask what's your view, to know if there'd be sense for me to start experimenting at all. The description/feature idea is stolen from the Kawa language (a Scheme variant for JVM):

An anonymous class is commonly used in the Java platform where a function language would use a lambda expression. Examples are call-back handlers, events handlers, and run methods. In these cases Kawa lets you use a lambda expression as a short-hand for an anonymous class. For example:

(button:addActionListener
  (lambda (e) (do-something)))

is equivalent to:

(button:addActionListener
  (object (java.awt.event.ActionListener)
    ((actionPerformed (e ::java.awt.event.ActionEvent))::void
     (do-something))))

This is possible when the required type is an interface or abstract class with a Single (exactly one) Abstract Methods. Such a class is sometypes called a SAM-type, and the conversion from a lambda expression to an anonymous class is sometimes called SAM-conversion.

Note that Kawa can also infer the parameter and return types of a method that overrides a method in a super-class.

http://www.gnu.org/software/kawa/Anonymous-classes.html#Lambda-as-shorthand-for-anonymous-class

So, in case of Yeti I'd see this probably as something like:

BEFORE:
button#addActionListener((class X extends java.awt.event.ActionListener
        void actionPerformed(java.awt.event.ActionEvent e)
            // do something
    end;
    new X()));

AFTER:
button#addActionListener(_ e is ~java.awt.event.ActionEvent -> () = /* do something */);
// IIUC, e's type cannot be inferred here, so "do e: ... done" syntax can't be used either

/Mateusz.

Madis

unread,
Sep 15, 2014, 8:24:30 AM9/15/14
to yeti...@googlegroups.com

On Mon, 15 Sep 2014, czap...@gmail.com wrote:

> So, in case of Yeti I'd see this probably as something like:
>
> BEFORE:
> button#addActionListener((class X extends java.awt.event.ActionListener
>         void actionPerformed(java.awt.event.ActionEvent e)
>             // do something
>     end;
>     new X()));
>
> AFTER:
> button#addActionListener(_ e is ~java.awt.event.ActionEvent -> () = /* do
> something */);
> // IIUC, e's type cannot be inferred here, so "do e: ... done" syntax can't
> be used either

Usually there is limited set of classes, where the anonymous instances
would be used, so following pattern can be used to define a constructor
function once for each callback type:

action f is (~ActionEvent -> ()) -> ~ActionListener =
(class Listener extends ActionListener
void actionPerformed(ActionEvent e)
f e
end;
new Listener());

button#addActionListener(action do e: ... done);
button2#addActionListener(action do e: ... done);

The type inferencer works in such way, that declaring the argument type in
lambdas used as argument to another function (action) is usually not
needed even for Java class types.

This pattern in some ways represents the Yeti-Java integration philosophy
in the language - wrapping the commonly used Java interfaces into Yeti
ones is preferred to mixing everywhere the Yeti/Java APIs.

Mateusz Czaplinski

unread,
Sep 15, 2014, 7:18:25 PM9/15/14
to yeti...@googlegroups.com
~/tmp/platform_frameworks_base/core/java$ grep -I -r -P 'public.*\b(set|add)On[a-zA-Z]*Listener\b' . -o |sed 's/.*://'|sort|uniq |wc -l
87
~/tmp/platform_frameworks_base/core/java$ grep -I -r -P '\bclass\b\s*\b[a-zA-Z]*Listener\b' . |wc -l
61
-- that's part of Android containing the GUIs, from: https://github.com/android/platform_frameworks_base/

~/tmp/jdk7src/src/share/classes$ grep -I -r -P 'public.*\b(add|set)[a-zA-Z]*Listener\b' . -o|sed 's/.*://'|sort|uniq |wc -l
112
:~/tmp/jdk7src/src/share/classes$ grep -I -r -P '\b(class|interface)\b\s*\b[a-zA-Z]*Listener\b' . |wc -l
234
-- that's from OpenJDK (I forgot about interfaces in Android, and already deleted the repository): http://hg.openjdk.java.net/jdk7/jdk7/jdk/

Is it not ok to try and offload the boring and repetitive parts to computer? Couldn't I use a little help instead of mechanically writing/reading OnSomeLongAndRepetitiveEventNameListener three times in a row? :/ besides, I believe that this pattern actually has to be used in Java exactly because it does not have lambdas, and "everything is a class", so lambda must be wrapped in this kind of tedious triple-declaration there.

Christian Essl

unread,
Sep 16, 2014, 8:42:29 AM9/16/14
to yeti...@googlegroups.com
If you can grep all the Interface/method names how about writing a code-generator which wirtes you a yeti-module whith all the yeti-wrapers as Madis suggested (you could also use a dynamic proxy to save code-size)

I know that is not the ideal solution however IMO the problem is that there are many whishes which only Madis can fullfill and he is a bit short of time - AFAIK - and would rather need some help from us. And having such a module for Android would certainly help.

Chrisichris

--
You received this message because you are subscribed to the Google Groups "yeti-lang" group.
To unsubscribe from this group and stop receiving emails from it, send an email to yeti-lang+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mateusz Czaplinski

unread,
Sep 16, 2014, 9:05:11 AM9/16/14
to yeti...@googlegroups.com
On Tue, Sep 16, 2014 at 2:42 PM, Christian Essl <christ...@gmail.com> wrote:
If you can grep all the Interface/method names how about writing a code-generator which wirtes you a yeti-module whith all the yeti-wrapers as Madis suggested (you could also use a dynamic proxy to save code-size)

I know that is not the ideal solution however IMO the problem is that there are many whishes which only Madis can fullfill and he is a bit short of time - AFAIK - and would rather need some help from us. And having such a module for Android would certainly help.

I totally understand that Madis may not have time and/or will to do this, thus I tried to ask whether he'd accept a patch/pull-request if I developed such a thing. Actually I'd even prefer to try doing this myself, to learn a bit about Yeti internals and have some fun! That said, I preferred to ask before even starting to maybe do something, as if he opposed, I'd have to reconsider whether there's sense in me trying this at all.

As to the rest of your suggestions: what do you mean by dynamic proxy? As to writing "static" modules/script for Android, and Swing, and every other potential library employing this pattern, I'm not very keen on this and see quite some disadvantages (hm, unless the script itself was in Yeti; this could be some compromise.... hm....)

Thanks,
/M.

Christian Essl

unread,
Sep 16, 2014, 11:09:31 AM9/16/14
to yeti...@googlegroups.com
I think lambdas as a shorthand would be a good addtion, especially because Java8 supports that as well and so propably many Java apis designed for that will come along.

Anyway wheter Madis wants that if you implent it I don't know.

Regarding the proxy: this was just an implementation detail using j.l.reflect.Proxy but that's propably not the best idea.

--

Madis

unread,
Sep 17, 2014, 5:51:53 AM9/17/14
to yeti...@googlegroups.com

On Tue, 16 Sep 2014, Mateusz Czaplinski wrote:

>On Tue, Sep 16, 2014 at 2:42 PM, Christian Essl <christ...@gmail.com>
>wrote:
> If you can grep all the Interface/method names how about writing
> a code-generator which wirtes you a yeti-module whith all the
> yeti-wrapers as Madis suggested (you could also use a dynamic
> proxy to save code-size)
>I know that is not the ideal solution however IMO the problem is that
>there are many whishes which only Madis can fullfill and he is a bit
>short of time - AFAIK - and would rather need some help from us. And
>having such a module for Android would certainly help.
>
>
>I totally understand that Madis may not have time and/or will to do this,
>thus I tried to ask whether he'd accept a patch/pull-request if I developed
>such a thing. Actually I'd even prefer to try doing this myself, to learn a
>bit about Yeti internals and have some fun! That said, I preferred to ask
>before even starting to maybe do something, as if he opposed, I'd have to
>reconsider whether there's sense in me trying this at all.

Well you can do it, if you want. It would be a new automatic cast,
and easiest way to implement it would be probably an AST transformation in
analyze phase. Basically you would detect a method-argument cast, and
if the types are suitable, generate an AST or code objects corresponding
to the following:

(class generated-id extends RequiredType
abstract-method-signature lambda argument
end; new generated-id())

Since it's use case is argument to foreign methods, you have to consider
method overrides.

Note that I suggest not touching the given lambda and just producing an
application - afaik the code generation stage should inline such lambda
application, so there is no benefit in trying to do it before.

I personally don't see much benefit from it (counting all the callback
interfaces in Java APIs is imho meaningless, as you are unlikely to use
more than fraction of those in any given application), but it won't
clutter the syntax, and it seems that the implementation shouldn't take
more than about hundred lines of additional code in the compiler.

So if you produce working non-bloated patch, I'll accept it.

Mateusz Czaplinski

unread,
Sep 17, 2014, 9:28:55 AM9/17/14
to yeti...@googlegroups.com
On Wed, Sep 17, 2014 at 11:51 AM, Madis <ma...@cyber.ee> wrote:
Well you can do it, if you want. It would be a new automatic cast, and easiest way to implement it would be probably an AST transformation in analyze phase. Basically you would detect a method-argument cast, and if the types are suitable, generate an AST or code objects corresponding to the following:

(class generated-id extends RequiredType
    abstract-method-signature lambda argument
 end; new generated-id())

Yay, awesome! :) Yesterday evening I already hacked a bit, and seem to have managed to force the typesystem to accept the input code (by expanding the "case YetiType.FUN" in JavaType.isAssignableJT()). However I'm not clear on how to continue with the AST generation yet. In the isAssignableJT() method, I don't seem to have enough context to generate code; on the other hand, in YetiAnalyzer.analSeq() I don't know how to detect this case yet. I'm starting to suspect I'm expected to do this in analSeq() (is that right?), so I'll probably need to think more on how to do the detection aspect here.
 
Since it's use case is argument to foreign methods, you have to consider method overrides.

Hm, I'm afraid I don't understand this sentence, or at least what do you mean by "method overrides" here? (and thus not sure what kind of concerns do you envision). I'd be very grateful if you could elaborate on this point.
 
Note that I suggest not touching the given lambda and just producing an
application - afaik the code generation stage should inline such lambda application, so there is no benefit in trying to do it before.

Not sure about how could I touch the lambda, and thus how I should not; I believe I only thought about generating AST yet, as that seemed most straightforward to me given the initial picture I had (i.e. "auto-inserting some code").
 
I personally don't see much benefit from it (counting all the callback interfaces in Java APIs is imho meaningless, as you are unlikely to use more than fraction of those in any given application), but it won't clutter the syntax, and it seems that the implementation shouldn't take more than about hundred lines of additional code in the compiler.

So if you produce working non-bloated patch, I'll accept it.

Thanks. As to the "non-bloated" part: if you wouldn't mind, I'd be grateful if I could show you some early version of the code at some point, and ask for your opinions as to my implementation approach, whether it fits you etc. etc. As to the technical means for that, I thought of a "draft" pull-request on Github -- not intended for merging, just for ease of use of their in-context diffs + inline annotations features.

Thanks,
/Mateusz.

Madis

unread,
Sep 17, 2014, 10:31:32 AM9/17/14
to yeti...@googlegroups.com

On Wed, 17 Sep 2014, Mateusz Czaplinski wrote:

>Yay, awesome! :) Yesterday evening I already hacked a bit, and seem to have
>managed to force the typesystem to accept the input code (by expanding the
>"case YetiType.FUN" in JavaType.isAssignableJT()). However I'm not clear on
>how to continue with the AST generation yet. In the isAssignableJT() method,
>I don't seem to have enough context to generate code; on the other hand, in
>YetiAnalyzer.analSeq() I don't know how to detect this case yet. I'm
>starting to suspect I'm expected to do this in analSeq() (is that right?),
>so I'll probably need to think more on how to do the detection aspect here.

Probably somewhere else, like mapArgs, that handles method calls.

> Since it's use case is argument to foreign methods, you have to
> consider method overrides.
>
>Hm, I'm afraid I don't understand this sentence, or at least what do you
>mean by "method overrides" here? (and thus not sure what kind of concerns do
>you envision). I'd be very grateful if you could elaborate on this point.

Considere you have instance of following class:

class Foo {
public void add(TheCallback cb);
public void add(String handle);
}

class Foo2 {
public void add(TheCallback cb);
public void add(yeti.lang.Fun fun);
}

and have following code in yeti:

fooInstance#add(_ id is number = println id);
foo2Instance#add(_ id is number = println id);

The JavaType.resolveByArgs must decide, which one should be called, but
if you modify the isAssignableJT to return > 0 value, then it might even
work as is.

czap...@gmail.com

unread,
Sep 19, 2014, 5:47:11 PM9/19/14
to yeti...@googlegroups.com, ma...@cyber.ee
I've uploaded the first draft of my proposed approach, at: https://github.com/mth/yeti/pull/12 .

I'd be grateful if you could take a look, and comment with any suggestions/concerns you might have!

Thanks,
/Mateusz.
Reply all
Reply to author
Forward
0 new messages