Generalised interfaces for Nemerle

6 views
Skip to first unread message

Alexey Romanov

unread,
Dec 27, 2006, 1:50:55 PM12/27/06
to nemer...@googlegroups.com
There is a very interesting new paper "JavaGI: Generalized Interfaces
for Java" by Stefan Wehr, Ralf Lämmel, Peter Thiemann, describing how to
get the power of Haskell typeclasses for Java interfaces.

In particular, their extensions to Java allow for retroactive and
conditional interface implementations, binary methods, static methods,
default methods, interfaces over families of types, and existential
quantification for interface-bounded types.

Even some part of this would be very nice to have in Nemerle, so I am
wondering if macros are expressive enough for the translation the paper
gives.

Kamil Skalski

unread,
Dec 27, 2006, 5:44:53 PM12/27/06
to nemer...@googlegroups.com
You would have problem with capturing the invocations of methods
constrained with "generalized interfaces" - as I can see they do
something like type-driven template mechanism for instantiating the
appropriate helper objects. Our macro mechanism allows only the code
generation part, but adding specially crafted arguments to selected
method invocations require modification of compiler's code.

The thing to note is that extension methods offer some degree of
similar extensibility, but with different approach. Examples from
paper:

module Extensions {
public myMax (this x : int, y : int) : int {
if (x.CompareTo (y) > 0) x else y
}
public myMax (this x : list [int], y : list [int]) : int {
| (x :: xs, y :: xs) => if (x.CompareTo (y)) ....
| ...
}
// the versions for list should be generated (maybe by macro) for
every desired
// parameter type,which should be enumerated by user, like
[GenerateDeepVersions (T, [int, list[int], string, list[string]])]
public myMax (this x : list [T], y : list [T]) : int {
| (x :: xs, y :: xs) => if (x.CompareTo (y)) ....
| ...
}
---> would yield
public myMax (this x : list [int], y : list [int]) : int { .. }
public myMax (this x : list [list[int]], y : list [list[int]]) : int { .. }
public myMax (this x : list [string], y : list [string]) : int { .. }
...
}

/// usage

assert (4 == 3.myMax (4));
assert ([6,7].Equals ([6,7].myMax ([1,2])));


Also, the concepts from this paper look a bit similar to what Scala is
doing and maybe what we would like to have in traits
(http://nemerle.org/Traits)

2006/12/27, Alexey Romanov <alexey.v...@gmail.com>:


--
Kamil Skalski
http://nazgul.omega.pl

Alexey Romanov

unread,
Dec 27, 2006, 6:41:45 PM12/27/06
to nemer...@googlegroups.com
Kamil Skalski wrote:
> You would have problem with capturing the invocations of methods
> constrained with "generalized interfaces" - as I can see they do
> something like type-driven template mechanism for instantiating the
> appropriate helper objects. Our macro mechanism allows only the code
> generation part, but adding specially crafted arguments to selected
> method invocations require modification of compiler's code.

Right, thanks. That's what I was afraid of.

> The thing to note is that extension methods offer some degree of
> similar extensibility, but with different approach. Examples from
> paper:
>

snip


>
> Also, the concepts from this paper look a bit similar to what Scala is
> doing and maybe what we would like to have in traits
> (http://nemerle.org/Traits)

The three things I really want are default methods (which would be
pretty much equivalent to traits), retroactive implementations (which
could let me write something like Haskell's Quickcheck) and static
methods in interfaces. Conditional implementations and/or explicit
self-types (which Scala also has) would be icing on the cake.

If I understand right, default methods are doable; retroactive
implementations, static methods, and conditional implementations are
not. I am not sure about self-types.

Extension methods offer some extensibility, but they are still pretty
limited. Let's say I want to write a Quickcheck-like program.
In the paper's syntax it would look like (changing a bit to look more
like .Net):

interface IArbitrary {static This Arbitrary(int randomNumber);}

implementation IArbitrary [String] {//a way to return a random String}
implementation IArbitrary [Int32] {//a way to return a random Int32}
...

I can't define extension static methods, so I can't even define the
implementations. And if I could, I still wouldn't have IArbitrary
interface to refer to (but I would be able to use late binding macro,
though I don't like the idea).

> 2006/12/27, Alexey Romanov <alexey.v...@gmail.com>:
>> There is a very interesting new paper "JavaGI: Generalized Interfaces

>> for Java" by Stefan Wehr, Ralf L mmel, Peter Thiemann, describing how to

Michal Moskal

unread,
Dec 28, 2006, 4:50:10 AM12/28/06
to nemer...@googlegroups.com
On 12/27/06, Kamil Skalski <kamil....@gmail.com> wrote:
> You would have problem with capturing the invocations of methods
> constrained with "generalized interfaces" - as I can see they do
> something like type-driven template mechanism for instantiating the
> appropriate helper objects. Our macro mechanism allows only the code
> generation part, but adding specially crafted arguments to selected
> method invocations require modification of compiler's code.

I think that by using a bit of Reflection here and there one could get
away from passing the additional parameters. They had to do that,
because Java doesn't really pass type parameters anywhere. .NET OTOH
does. So I think it would make sense to use the following trick, for:

interface My { foo () : void; }
implementation My on int { ... }

do:

class MyDict[T]
{
public static Instance : MyDict[T] ;
public static this ()
{
// use reflection on typeof(T) to initialize Instance, e.g. with MyDict_int
}
public abstract foo(x:T) : void;
}

class MyDict_int : MyDict[int] {
public override foo(x:int) : void { }
}

then when you have:

foo (x : My) { x.foo(); }

you could generate:

foo[T] (x : T) : void
{
MyDict[T].Instance.foo (x);
}

or something like this. The problem is that method dispatch would be
static. The good thing is performance -- after the initial reflection
the cost of call is very low.

Another implementation strategy could be to use structs -- when you
need to use int as My, you put it inside some special struct
MyStruct[int]. It is possible there would be some unavoidable boxing
during calls.

Whatever, I need to think about it more :)

The paper seems very interesting.

--
Michał

Michal Moskal

unread,
Dec 28, 2006, 12:23:54 PM12/28/06
to nemer...@googlegroups.com
On 12/28/06, Michal Moskal <michal...@gmail.com> wrote:
> The good thing is performance -- after the initial reflection
> the cost of call is very low.

After some more thought: the runtime needs to do a hashtable lookup to
find out the proper static class. So this is not as fast as it might
have seemed. However if the runtime does the hashtable lookup, we can
as well do it ourselves. This would also fix the nasty problems with
dispatch being static and lift the limitation that all the
"implementation" things need to sit in one compilation unit. So it
would look something like this:

class MyDict[T] {
static dictionaries : Dictionary[System.Type,object];

static public LookupDict (x : T) : MyDict[T]
{
// fill dictionaries with some reflection tricks
dictionaries [x.GetType ()] :> MyDict[T]
// or dictionaries [typeof (T)] :> MyDict [T]
}
}

// use: x.foo(3) -> LookupDict(x).foo(x,3)

Now there is a problem that x.GetType() and typeof(T) can be different
things (T can be wider). For the cast to succeed we need typeof(T),
for the dynamic dispatch we need x.GetType(). So probably we should
compare T and x.GetType(), and maybe have the hashtable indexed by
both. There would be need for a proxy class here.

Anyway I stumbled upon some nasty problems, for example how do they
handle dynamic dispatch of binary methods?

If we have:

interface IComparable { compare (This that); }

classes:

class A : IComparable {
compare (a : A) { ... }
}

class B : A, IComparable {
compare (a : B) { ... }
}

then what happens if we call:

def foo(a : A) { a.compare (A()) }
foo(B())

Dynamic dispatch would result in B.compare being called, but this
violates type safety. Binary methods cannot be dispatched dynamically.

There probably should be some distinction between method dispatched
dynamically and statically. Maybe it should be at the interface and
not the method level.

--
Michał

Kamil Skalski

unread,
Dec 28, 2006, 12:49:50 PM12/28/06
to nemer...@googlegroups.com
>
> def foo(a : A) { a.compare (A()) }
> foo(B())
>
> Dynamic dispatch would result in B.compare being called, but this
> violates type safety. Binary methods cannot be dispatched dynamically.
>
> There probably should be some distinction between method dispatched
> dynamically and statically. Maybe it should be at the interface and
> not the method level.
>

In the paper this distinction is described as "external methods are
dispatched dynamically". I'm not sure if that means that they cannot
use interfaces with "This" types from separate compilation units...

Reply all
Reply to author
Forward
0 new messages