-
Notifications
You must be signed in to change notification settings - Fork 145
Calling Frege from Java (from release 3.24 on)
This guide explains how to call code generated by the Frege compiler from Java. It is valid for releases since 3.24.
- generated Java code is now fully generic, including higher kinded types.
- code for Java8 and above can be generated with lambdas instead of anonymous functions.
- certain run time classes have been dropped (
Algebraic
,Applicable
) or replaced (Delayed
,Lambda
) or they are generic now (Lazy
).
The Frege Run Time System consists of a handful of interfaces and abstract classes, which are used to implement laziness, function types, and higher kinded types, as explained below. In addition, there are some provisions for IO, parallelism through the Fork-Join framework, and concurrency with thread pools. However, none of this requires any initialization, it'll just be there when the Frege code needs it.
Some of the most important runtime types, like functions, lazy values, and thunks exist twice in different packages frege.run7
and frege.run8
. The reason is that there is no way to share them between code that uses Java lambdas and code that is compatible with Java7.
This is, for the time being, a dilemma for the author of Java code that calls into Frege. The following solutions are conceivable:
-
Write your Java code inline in Frege. Pro: the Frege compiler will supply the appropriate imports, and the code will run in any Java version. Con: inline code must be written without help from an IDE, and this may become hard. (Workaround: write your code first in a separate Java class, then copy relevant portions to the Frege source)
-
Be future oriented: just forget Java7. Pro: it's the future. Con: Unfortunately, due to long-standing unfixed bugs, javac8 is currently unable to reliably compile Frege generated code (while javac9 compiles the same code without complaints), so this strategy will win only when Java9 becomes the standard.
-
Make a master version for the JVM level you want to support most, but in such a way that you can derive the other version by simply replacing the relevant imports, changing
frege.run7
tofrege.run8
or vice versa. Pro: you can support all Frege users. Con: higher order functions will be difficult, and if you need them, you must use java7 compatible anonymous classes.
In the following, I write
frege.run?
when I mean:frege.run7
orfrege.run8
depending on your compilation target.
The type for modelling lazy values is basically (I show here the Java8 variant):
@FunctionalInterface
public interface Lazy<R> extends Callable<R> {
/**
* <p> Compute the value if it is needed. </p>
*
* @see java.util.concurrent.Callable#call()
*/
public R call();
}
From this, it should be clear, that when you have Lazy<X>
and want an X
, you need to invoke the call()
method.
But quite as often, you have an X
, but want a Lazy<X>
, for example when you want to pass a value to a Frege function that expects a Lazy<X>
. In this case, it is important to know that the Java incarnations of Frege algebraic data types as well as Frege function types implement Lazy
(their call()
method returns just this
), so they can simply be passed where their Lazy
form is needed.
The only exception are enumeration types that are mapped to java type short
and type renamings of native types, since natives types do, of course, not normally implement Lazy
.
So, how do we make a lazy value? This is the time to introduce Box
es and Thunk
s.
While Lazy
is just an interface that doesn't specify how call()
is implemented, Thunk
is a class that implements Lazy
and guarantees that the evaluation cost of call()
(if any) is incurred at most once, because the result gets cached, waiting for the next call()
where the previously computed value is just returned again. In addition, it is guaranteed that this remains true even in the presence of multiple threads, who try to evaluate that value. Only one thread will evaluate the value, which is afterwards available to all threads.
Consider the following example:
(Lazy<Integer>) (() -> /* some expensive computation */)
One can pass this to a function or data constructor that expects a Lazy<Integer>
as argument. But every time the function invokes call()
on that argument, the expensive computation will run. We can do better by creating a Thunk
thus:
Thunk.<Integer>shared( () -> /* some expensive computation */ )
Now, the Thunk
evaluation mechanism ensures that /* some expensive computation */
is performed at most once, and the result is shared among all interested parties.
Clearly, Thunk.shared
is overkill if we already have a value. We would not want to write
Thunk.<Integer>shared( () -> 42 )
just to place an int
in a list or tuple! Instead, the lazy
method comes in handy here:
Thunk.<Integer>lazy( 42 )
This will actually create an instance of class Box
, another instantiation of Lazy
that is specialized on just boxing values.
-
The java type of a lazy value is
Lazy<T>
ifT
is the java type of the value. -
To get the value out of a
Lazy
, invokecall()
on it. -
Algebraic data types and function types implement
Lazy
-
Enumeration types, native types and renamings (
newtype
) of native/enumeration types do not implementLazy
-
To get a
Lazy<R>
of a value with typeR
, pass the value toThunk.<R>lazy
. This is needed only ifR
is or could be a type that doesn't implementLazy
. -
To delay a computation, use
(Lazy<T>)( () -> /* compute a T */ )
-
To create a delayed computation that is evaluated at most once (a thunk), use
Thunk.<T>shared( () -> /* compute a T */ )
-
Don't assume anything about the actual class of a
Lazy
value! Stick with theLazy
interface and theThunk.shared
andThunk.lazy
operations, and apart from the latter two, forget thatThunk
andBox
exist.
- Fields of algebraic datatypes are lazy, unless they are marked strict with a bang. For example, list and tuple elements are lazy.
- Higher order functions take lazy arguments and return a lazy result.
- Class operations invoked through an instance dictionary take lazy arguments.
- Top level functions are compiled to static methods. Their arguments and the return types can be lazy or strict, depending on the judgement of the strictness analyzer. Your IDE can help you identify the actual types.
Functional values appear as arguments in function calls, return values or components in data structures. They are curried and represented as lambda expressions implementing the functional interface Func.U
(or anonymous classes that extend Func.U
for Java7).
public class Func {
@FunctionalInterface public interface U<𝓐, 𝓑>
extends Lazy<Func.U<𝓐, 𝓑>>,
Kind.U<Func.U<𝓐, ?>, 𝓑>, Kind.B<Func.U<?, ?>, 𝓐, 𝓑>
{
public Lazy<𝓑> apply(final Lazy<𝓐> a) ;
public default Func.U<𝓐, 𝓑> call() { return this; }
}
@SuppressWarnings("unchecked") final public static <𝓐, 𝓑> Func.U<𝓐, 𝓑> coerceU(
final Kind.U<Func.U<𝓐, ?>, 𝓑> it) {
return (Func.U<𝓐, 𝓑>)it;
}
@SuppressWarnings("unchecked") final public static <𝓐, 𝓑> Func.U<𝓐, 𝓑> coerceU(
final Kind.B<Func.U<?, ?>, 𝓐, 𝓑> it) {
return (Func.U<𝓐, 𝓑>)it;
}
}
As can be seen from the signature of method apply
, one can apply a lazy value and gets another lazy value back. The return value can also be another (lazy) function that takes further arguments.
Partial application is easily attained by constructing a Func.U
that picks up the remaining argument and in the innermost lambda body just calls the appropriate static method. Here is the code that corresponds to
foo xs = map (1+) xs
final public static PreludeBase.TList<Integer> foo(final PreludeBase.TList<Integer> arg$1) {
return PreludeList.<Integer, Integer>map(
(Func.U<Integer, Integer>)((final Lazy<Integer> η$19697) -> Thunk.<Integer>lazy(
1 + (int)η$19697.call())
),
arg$1
);
}
If you need to pass a function, treat it like a partial application without fixed arguments. That is, nest as many Func.U
as the function has arguments, and in the innermost body, call the function with all collected arguments. See also the example below.
Frege doesn't make use of the functional interfaces provided by Java8 and higher in java.util.function
for a variety of reasons:
- We need backwards compatibility with Java7.
- The functional interfaces have no notion of laziness in arguments and return types. This makes it difficult to enforce correct use through the java type system.
- The functional interfaces don't implement Lazy themselves.
- Most functional interfaces are just too specialized to be useful in Frege.
However, conversion between Frege functions and those interfaces should be particularly easy.
As a showcase we use java.util.function.BiFunction
(The following examples are not in the standard library, due to Java7 backwards compatibility issues. However, we will provide functionality in a source file later this year.)
-- make the interface type known to Frege
-- we deal only with pure functions here!
data BiFunction t u r = pure native java.util.function.BiFunction {t,u,r} where
pure native apply :: BiFunction t u r -> t -> u -> r
pure native from frege.run8.Utils.bifunction{t,u,r} :: (t -> u -> r) -> BiFunction t u r
The code to make a BiFunction
from a Frege function is the following (and is provided in frege.run8.Utils
):
public static<T,U,R> BiFunction<T, U, R> bifunction(Func.U<T, Func.U<U, R>> f) {
return (T a, U b) -> f
.apply(Thunk.lazy(a))
.call()
.apply(Thunk.lazy(b))
.call();
}
Now suppose we want to enrich the Java ecosystem with a bi-functor that runs the const
function:
public static<A,B> BiFunction<A, B, A> biconst() {
return bifunction(
(Func.U<A, Func.U<B,A>>)
((Lazy<A> a) -> (Func.U<B, A>)((Lazy<B> b) -> Thunk.lazy(
PreludeBase.<A, B>$const(a.call(), b)))));
}
Too easy, yet in Frege this is, as always, a tiny bit more concise given the definitions from above:
biconst = BiFunction.from const
Here is the ultimate guide how to handle monadic values.
It is important to follow the rules laid down in the following paragraphs. Please consider: even if your IDE discloses the actual representation of
IO
actions, it is unwise to employ this knowledge. The representation can change with the next release, and your code will be worthless. However, the recipes given below shall work regardless of the actual representation.
When dealing with IO
actions, it is important to keep the types in mind. Here is an easy example:
putStrLn :: String -> IO () -- not an IO action, but a function that constructs one
putStrLn "foo" :: IO () -- this is an IO action, actually
Construction of an IO
action does not execute it yet. To actually run the action, we need to use IO.performUnsafe
thus:
PreludeBase.TST.<>performUnsafe( Prelude.putStrLn("foo") ).call();
The trailing call
is needed only when the action gives back a useful result.
Note how the construction of the action in the argument of the performUnsafe
method avoids writing down the Java type associated with IO ()
. All we need to know is that performUnsafe
will take any IO
or ST
action. Also, the "diamond operator" should infer the result type of the IO
action.
From the point of view of Java code calling into Frege, ST
actions are just IO
actions, just like in Frege where you can use ST
actions freely inside IO
actions.
These are easy monads whose representation is the same as the corresponding data types. See the section about data types below to learn how to work with algebraic data types.
Consider the following function constructing a State
action that increments its Int
state by the amount given and returns a Bool
(this return value is just there to make the difference between the state and the result of running a state action better visible):
increment ∷ Int → State Int Bool
increment n = State.modify (n+) >> pure true
To run a State
action, we have three convenience functions in frege.control.monad.State
-
runState
takes aState
action and an initial state, and returns the result and the new state in a tuple -
execState
takes aState
action and an initial state but returns only the new state -
evalState
takes aState
action and an initial state but returns only the result
As before, we need to construct the action first and pass it to one of the functions mentioned, along with a state. If this is done in one go, we again are independent of the actual Java representation of a State
action.
import frege.prelude.PreludeBase;
import frege.prelude.PreludeBase.TTuple2;
import frege.control.monad.State;
TTuple2<Boolean, Integer> result = State.TState.<Boolean, Integer>run(
increment(2), // construct action
Thunk.<Integer>lazy(40) // initial state
).call();
System.out.println("new state is " + PreludeBase.<Boolean, Integer>snd(result));
The only difficulty here is that runState
is aliased to State.run
and this appears then in Java as State.TState.run
. Its cousins evalState
and execState
are not aliased and translate to State.evalState
and State.execState
.
StateT
is very similar to State
, the difference is just that the convenience functions runStateT
, evalSTateT
and execStateT
return another monadic value. Suppose we have
incr ∷ Int → StateT Int IO ()
incr n = do
st ← StateT.get
StateT.put (st+n)
liftIO . putStrLn $ (if odd (st+n) then "new state is odd" else "new state is even")
then execStateT
will return a IO
action that, when executed, returns the new state. Unfortunately, we need to learn two more things before we can do this in Java.
Home
News
Community
- Online Communities
- Frege Ecosystem
- Frege Day 2015
- Protocol
- Simon Peyton-Jones Transcript
- Talks
- Articles
- Books
- Courses
Documentation
- Getting Started
- Online REPL
- FAQ
- Language and API Reference
- Libraries
- Language Interoperability
- Calling Frege From Java (old)
- Calling Java From Frege
- Calling Frege From Java (new)
- Compiler Manpage
- Source Code Doc
- Contributing
- System Properties
- License
- IDE Support
- Eclipse
- Intellij
- VS Code and Language Server
- Haskell
- Differences
- GHC Options vs Frege
- Learn You A Haskell Adaptations
- Official Doc
Downloads