-
Notifications
You must be signed in to change notification settings - Fork 149
Description
This is a proposal which introduces a new syntax that allows Frege programmers to implement Java Interfaces with Frege Record declarations.
Proof of Concept
Supports the "Pure" case only: #361
Short description and Examples
native new functions take Frege records as their arguments and uses their fields to implement the methods of Java interface.
Pure
public interface J {
public int succ(int i);
public int succ(int i, int n);
}data JImpl = JImpl
{ succ :: Int -> Int
, succN :: Int -> Int -> Int
}
data J = pure native J where
pure native new "extends" :: JImpl -> J
pure native succ :: J -> Int -> Int
pure native succN succ :: J -> Int -> Int -> Int
main = do
let j = J.new $ JImpl { succ = (+1) }
println $ j.succ 1 -- 2
let j = J.new $ JImpl { succ = (*10) }
println $ j.succ 1 -- 10
println $ j.succN 5 2 -- 500Mutable
public interface Counter {
public int get();
public void increment();
}data Impl = Impl
{ get :: ST s Int
, increment :: ST s ()
}
data Counter = native Counter where
native new "extends" :: Impl -> STMutable s Counter
native get :: Mutable s Counter -> ST s Int
native increment :: Mutable s Counter -> ST s ()
main = do
c <- do
internal <- Ref.new 0
Counter.new $ Impl
{ get = internal.get
, increment = internal.modify succ
}
println =<< c.get -- 0
c.increment
println =<< c.get -- 1Abstract class
public abstract class AC {
public AC(int i) {
// does something with i...
}
public abstract void doFoo();
}data Impl = Impl { doFoo :: ST s () }
data AC = native AC where
native new "extends" :: Impl -> Int -> AC
native doFoo :: AC -> ST s ()
-- ...Long description
Motivation
Java libraries and frameworks tend to provide Java interfaces, which client code must implement to work with them. Currently, in order to implement Java interfaces in Frege, Frege programmers must either (a) use the native module declarations, or (b) write Java source files separately. In either case, Frege programmers must work out the laziness of Frege arguments/return values and write boilerplates such as .call(), TST.performUnsafe(...), Thunk.lazy(...), etc. Since implementing interfaces and extending classes are the essential part of Java programming, having the compiler automate these tasks should greatly help writing Frege against existing huge Java codebase.
Concept
Implementing a Java interface is essentially providing a set of functions that implements its methods. "A set of (named) functions" can be represented with the record syntax in Frege, so currently one can write something like this:
module Foo where
native module where {
public static I mk_(final TImpl impl) {
return new I() {
@Override
public int getValue() {
return TImpl.getValue(impl);
}
};
}
}
data Impl = Impl { getValue :: Int }
data I = pure native I
pure native mk "Foo.mk_" :: Impl -> IFor concrete, working, real examples, please see modules such as this.
The native module part in the above example is a boilerplate and is to be automatically generated in the proposal. With the new syntax, it can be rewritten to:
data Impl = Impl { getValue :: Int }
data I = pure native I where
pure native getValue :: I -> Int
pure native new "extends" :: Impl -> ISyntax
The new syntax introduced in this proposal is the Java-name "extends" in native function declarations:
[pure] native fregeName "extends"
:: RecordType
[-> constructor arguments (abstract classes only) ...]
-> [STMutable s] NativeType (LHS of native data declaration)
Specifying "extends" instead of "new" generates an anonymous class declaration instead of a plain new expression.
Field/Method correspondence and Overload resolution
Since Java has method overloading, the syntax must support overload resolution. For this, it relies on the other native member declarations.
data Impl = Impl
{ add2 :: Int -> Int -> Int
, add3 :: Int -> Int -> Int -> Int
, ignored :: Int
, garbage :: Int -> Bool
}
data JavaI = pure native JavaI where
pure native new "extends" :: Impl -> JavaI
pure native add2 add :: JavaI -> Int -> Int -> Int
pure native add3 add :: JavaI -> Int -> Int -> Int -> Int
pure native garbage :: JavaI -> Int -> IntMethods for an anonymous class are generated in the following algorithm:
- For each fields in a record type (
Impl), - Members of the target native type (
JavaI) are looked up by Frege name
(add2,add3, etc., but notadd). - If a matching member is found for the field, a method is generated with the Java name. For example, because
Impl.add2matchesJavaI.add2, andJavaI.add2has Java-nameadd,public int add(int arg1, int arg2) { return TImpl.add2(impl, Thunk.lazy(arg1), Thunk.lazy(arg2)).call(); // where `impl` is the parameter of `JavaI.new` }
- Superfluous fields in the record (
Impl.ignored) are ignored. - If signatures are incorrect (
Impl.garbage), it's a javac-time error. - Both record fields and native members must exist for every methods of the interface. otherwise, it's a javac-time error.