-
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 handfull 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. Natives types do not normally implement Lazy
, and you have 3 guesses to find out why this is so.
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, as return values or as 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 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:
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:
biconst = BiFunction.from const
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