Learn how to work with Java
You must have a basic understanding of sequenceables/monads to understand the rest of the section. Please check out the chapter on Sequenceable in Tour of Eta for better understanding.
When interfacing with Java, you should import the Java module from the standard library:
1 | import Java
|
This will import the Java monad and related helper functions for working inside the monad.
Consider the following Java code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package eta.example;
public class Counter {
private int counter;
public int publicCounter;
public static final int COUNTER_MAX = 1000;
public static int numCounters;
public Counter() {}
public Counter(int initial) {
this.counter = initial;
}
public void increment() {
this.counter = Math.min(this.counter + 1, COUNTER_MAX);
this.publicCounter = counter;
}
public int get() {
return counter;
}
public void set(int value) {
this.counter = Math.min(value, COUNTER_MAX);
this.publicCounter = counter;
}
}
|
A Java method is simply a function that takes an object as an implicit argument bound to the this variable. Implicit contexts such as these can be represented as a monad, a state monad to be specific. A state monad threads state through each monadic action so the state is being passed around internally even though it’s not visible in the code.
This correspondence is the basis for the built-in Java monad in Eta.
The above example can be imported as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | data Counter = Counter @eta.example.Counter
deriving Class
foreign import java unsafe "@new" newCounter :: Java a Counter
foreign import java unsafe "@new" newCounterWith :: Int -> Java a Counter
foreign import java unsafe increment :: Java Counter ()
foreign import java unsafe get :: Java Counter Int
foreign import java unsafe set :: Int -> Java Counter ()
foreign import java unsafe "@static @field eta.example.Counter.COUNTER_MAX"
cOUNTER_MAX :: Java a Int
foreign import java unsafe "@field publicCounter" getPublicCounter
:: Java Counter Int
foreign import java unsafe "@field publicCounter" setPublicCounter
:: Int -> Java Counter ()
foreign import java unsafe "@static @field numCounters" getNumCounters
:: Java a Int
foreign import java unsafe "@static @field numCounters" setNumCounters
:: Int -> Java a ()
|
When working with the FFI, you need a way to refer to a given Java class inside of Eta. This is done through Java Wrapper Types (JWTs).
1 2 | data X = X @[class-name]
deriving Class
|
[class-name] should be the fully qualified Java class name and X should be the Eta name you would use to refer to the corresponding Java class in foreign imports. Note that [class-name] can also be converted to an array type by appending [].
The Class typeclass is a built-in typeclass that is a marker for a JWT. Make sure all your JWTs derive a Class instance.
In Eta, there is a clear distinction JWTs and normal Eta types. Moreover, only JWTs can be used in foreign imports/exports.
JWTs are inconvenient to use directly in Eta because they are just wrappers of native Java objects. So, the following typeclass is defined in the standard library to help convert JWTs to common Eta types like lists.
1 2 3 4 5 | -- The `a` type variable should be a normal Eta type
-- The `b` type variable should be a JWT or a primitive type (Byte, Short, Int, ...)
class JavaConverter a b where
toJava :: a -> b
fromJava :: b -> a
|
Many instances are provided for you by default so you can simply use toJava or fromJava whenever you want to perform a conversion.
String is a notable exception to that rule because it’s so commonly used that there’s a special case that allows it an automatically converts it to JString.
As mentioned before, the Java monad is used to contain the implicit this context. It can be effectively thought of as a state monad with a given Java object as the state.
1 | newtype Java c a = Java {- Internal definition -}
|
As can be seen from the above definition, the Java monad has two type parameters c and a. The c parameter is the type of the implicit this context, and should be some JWT, and the a parameter is the return type of the monad.
In the Java module in the base package, the following functions are available:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | -- Execute a Java action in the IO monad.
java :: Java c a -> IO a
-- Execute a Java action in the IO monad with respect to the
-- given object.
javaWith :: (Class c) => c -> Java c a -> IO a
-- Execute a Java action in the Java monad of another class
-- with respect to the given object.
(<.>) :: (Class c) => c -> Java c a -> Java b a
withObject :: (Class c) => c -> Java c a -> Java b a
-- Chain Java actions.
(>-) :: (Class b) => Java a b -> Java b c -> Java a c
-- Execute an IO action inside of the Java monad
io :: IO a -> Java c a
-- Execute a Java action purely, i.e. order of execution does not matter.
unsafePerformJava :: Java c a -> a
-- Analagous to `javaWith`, but pure.
unsafePerformJavaWith :: (Class c) => c -> Java c a -> a
|
For instance, if the following functions are available:
1 2 | newFile :: String -> Java a File
canExecute :: Java File Bool
|
Then it is possible to write the following program:
1 2 3 4 5 6 7 8 9 | main :: IO ()
main = do
executes <- java $ do
file <- newFile "./dir/prog.exe"
io $ putStrLn "Executing an IO action inside of Java!"
file <.> canExecute
if executes
then putStrLn "File can execute!"
else putStrLn "File cannot execute!"
|
Using different combinators, we can write it like this:
1 2 3 4 5 6 7 8 9 10 11 12 | main :: IO ()
main = do
-- Similar to Java code:
-- File file = new File("./dir/prog.exe");
file <- java $ newFile "./dir/prog.exe"
putStrLn "Executing an IO action inside of Java!"
-- Similar to Java code:
-- boolean executes = file.canExecute();
executes <- javaWith file canExecute
if executes
then putStrLn "File can execute!"
else putStrLn "File cannot execute!"
|
Or:
1 2 3 4 5 6 7 8 9 | main :: IO ()
main = java $ do
-- Similar to Java code:
-- boolean executes = new File("./dir/prog.exe").canExecute();
executes <- newFile "./dir/prog.exe" >- canExecute
io $ putStrLn "Executing an IO action inside of Java!"
if executes
then io $ putStrLn "File can execute!"
else io $ putStrLn "File cannot execute!"
|
Foreign import declarations are used to import a Java method as an Eta monadic action, typically in the Java monad.
The full syntax of the foreign import declaration is quite complex, but only a subset is needed here:
1 2 | foreign import java unsafe "[import-string]" [eta-identifier]
:: [arg-type-1] -> [arg-type-2] -> .. -> [return-type]
|
[eta-identifier] should be a valid Eta identifier that will be used for calling the corresponding Java method inside of Eta code.
[argTypeN] should be a marshallable Eta type. See Marshalling Between Java and Eta Types.
For the full syntax, see General Syntax.
Just as you can import Java methods into Eta, you can also export Eta functions into Java.
1 2 | foreign export java "[export-string]" [eta-identifier]
:: [arg-type-1] -> [arg-type-2] -> .. -> [return-type]
|
[eta-identifier] should be a valid Eta identifier for an existing Eta function that is the target of the export.
[arg-type-n] should be a marshallable Eta type.
Exporting static methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 | foreign export java "@static eta.example.MyClass.sayHello" sayHelloEta :: IO ()
sayHelloEta = putStrLn "Hi"
foreign export java "@static eta.example.Numbers.zero" zero :: IO Int
zero = do
putStrLn "Returning zero from eta"
return 0
foreign export java "@static eta.example.Numbers.one" one :: Int
one = 1
foreign export java "@static eta.example.Numbers.addTwo" addTwo :: Int -> Java a Int
addTwo x = return $ x + 2
|
Exporting an instance method for a new class that inherits from an existing one. Given an existing class eta.example.Counter like the defined above we can create another class in eta that inherits from it, adding one or more methods that can use the definitions of the superclass:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | -- Importing an existing class
data JavaCounter = JavaCounter @eta.example.Counter
deriving Class
-- eta.example.EtaCounter will be generated by eta
data EtaCounter = EtaCounter @eta.example.EtaCounter
deriving Class
-- Required to make EtaCounter a subclass of JavaCounter
type instance Inherits EtaCounter = '[JavaCounter]
-- Importing methods from JavaCounter with ( c <: JavaCounter ) constraint
-- to make it works for its subclasses (including EtaCounter)
-- see: https://eta-lang.org/docs/user-guides/eta-user-guide/java-interop/arrays-subclasses#problem-resolution
foreign import java unsafe get :: ( c <: JavaCounter )
=> Java c Int
foreign import java unsafe set :: ( c <: JavaCounter )
=> Int -> Java c ()
-- implement an eta function that uses methods and state from the superclass
decrement :: Int -> Java EtaCounter Int
decrement x = do
c <- get
let c' = max (c - x) 0
set c'
return c'
foreign export java decrement :: Int -> Java EtaCounter Int
|
Use of exported methods from the java side:
1 2 3 4 5 6 7 8 9 10 11 | // static methods
eta.example.MyClass.sayHello();
System.out.println("Zero: " + eta.example.Numbers.zero());
System.out.println("One: " + eta.example.Numbers.one());
System.out.println("Two: " + eta.example.Numbers.addTwo(0));
// instance method
eta.example.EtaCounter d = new eta.example.EtaCounter();
d.set(10);
System.out.println(d.decrement(5));
System.out.println(d.get());
|