-
Notifications
You must be signed in to change notification settings - Fork 145
Calling Frege Code from Java
It is easy to use Java classes and methods from Frege, however, it is also possible the other way. This guide provides some documentation, tips and tricks.
The Frege Run Time System (see also this drawing) consists of a number of mostly abstract classes and interfaces, and a handful of static methods that implement some primitives needed in Frege code. The Javadocs for package frege.runtime can be found here.
The run time system is stateless, i.e. one does not need to initialize or setup anything.
The main concern of the run time system is to provide for lazy evaluation and partial function application.
Results from function calls as well as components of Frege values can be lazy.
The Java type of such values will be Lazy
or Object
, where Lazy
will evaluate to some algebraic data type or function type, while Object
may be a lazy native (i.e. Java) value or a type represented by a type variable.
For example, while tuples of different types can be distinguished by the Frege compiler, the information about type arguments gets lost in Java, and there is only one class (TTuple2
) that is used for all tuples. Hence, both members of a tuple have the Java type Object
, and this is also the return type of functions fst
and snd
. The same is true for list elements: because in the most general case we do not know anything about them, their type is Object
.
To evaluate a lazy value it
with Java type Lazy
that will evaluate to TMaybe
, write:
it.<TMaybe>forced()
To evaluate a lazy value it
with Java type Object
that will evaluate to a string, write:
Delayed.<String>forced(it)
This works no matter if the value is actually lazy or not. However, it's the final type that matters, and one gets a class cast exception if the evaluated value has an incompatible type.
It is never necessary to do the conversion in the other direction, because:
- every non-native Frege value, algebraic or functional, implements interface
Lazy
- every value, even a primitive one, is silently converted or casted to
Object
by Java
Every non lazy function value will have type Lambda
. Again, further type information like argument and return types are lost in translation to Java.
The only interesting thing one can do with a Lambda
is to apply it to an argument:
Lambda fun = ......
Applicable inter = fun.apply(42);
All we know about the result of apply
is that it could be another Lambda
or, if all arguments have been supplied, that it is a Lazy
value. This is encapsulated in interface Applicable
, which offers only two methods: apply(Object)
, which results in yet another Applicable
and result()
, which tells that we are done with applying arguments and want to have the Lazy
result.
The following code assumes that our original function was of type Int -> Int -> (String, Int)
and shows how to get at the String
value.
String s = Delayed.<String>forced(
inter.apply(43) // supply second argument
.result() // get the `Lazy` value
.<TTuple2>forced() // evaluate result
.mem1); // get the first component
- Know the types of the Frege functions you are going to call.
- Restrict yourself to a handful of functions.
- If possible, specialize functions with polymorphic types as much as possible.
- It is safe to assume defensively that a function takes all arguments strict, but returns a lazy result. This way, as long as the Frege type signature of the function remains the same, your Java code that calls the function will work regardless of the implementation of the function.
If unsure as of how to call a certain function, write a small Frege program that does just that, compile it and study the resulting java code.
Better yet, use Marimuthu's invaluable REPL or online REPL and use the :java
command to show the code immediately.
-
Never pass
null
into Frege code. There are no exceptions to this rule. - Just because a function argument is of type
Object
doesn't mean you can pass anything.
A Frege module is compiled to a Java class that acts as namespace for the items defined in the module.
module tutorial.Example where
import frege.prelude.Floating
quadrt :: Double -> Double
quadrt = sqrt . sqrt
Here is an outline of the corresponding Java code (comments introduced manually):
package tutorial; // missing if Frege module name is a simple one
import frege.prelude.PreludeList; // Import of Frege modules
import frege.prelude.Arrays; // you didn't know they existed ...
import frege.Prelude;
import frege.prelude.Floating; // ... and also explicitly imported ones.
import frege.prelude.Maybe;
import frege.prelude.PreludeBase;
import frege.prelude.PreludeNative;
import frege.prelude.PreludeMonad;
import frege.prelude.PreludeText;
@SuppressWarnings("unused") // We'll have lots of unused local vars. Sorry.
@frege.runtime.Meta.FregePackage( // Meta information used when this
// package is ever imported.
source="/home/.../Example.fr", time=1357511227564L,
ops={
@frege.runtime.Meta.Operator(name="<$>", kind=0, prec=13),
// ... and so on and on ....
)
final public class Example { // the module namespace
final public static double quadrt(final double arg$1) {
return java.lang.Math.sqrt(java.lang.Math.sqrt(arg$1));
}
}
As one can see in the previous example, a top level function is translated to a public static method that is a member of the module namespace class. But it is, unfortunately, not always that easy, see below.
Types appear as static classes or interfaces that are members of the module class. Their names always starts with a capital T
followed by the original name.
An enumeration type is one that has only nullary constructors:
data Color = Red | Green | Blue
where
favored Blue = true
favored _ = false
This compiles to:
final public static class TColor {
private TColor() {}
final public static short Blue = 2;
final public static short Green = 1;
final public static short Red = 0;
final public static boolean favored(final short arg$1) {
if (arg$1 == TColor.Blue) {
return true;
}
return false;
}
}
Here, the class TColor
merely works as namespace for the methods that correspond to the Frege functions defined in the where
block of the data
definition.
All enumeration values are mapped to constants of type short
, and hence values of different enumeration types cannot be distinguished any more on the Java level.
A type with exactly one constructor that has exactly one field is called a "newtype"
(named after the keyword used to create this in Haskell, in Frege we use an ordinary data
declaration).
It is special, because it exists only at compile time. At runtime, all values of such a type appear
with the type of the single component:
--- type to hold a currency name like @"EUR"@ or @"NZD"@
--- useful to avoid confusion with ordinary 'String's
--- Note: construction and deconstruction is a no op at runtime.
data Currency = Currency String where
paying :: Currency -> String
paying (Currency s) = "We pay in " ++ s
main _ = println euro.paying
where euro = Currency "EUR"
This compiles as follows:
public static abstract class TCurrency {
final public static java.lang.String paying(final java.lang.String arg$1) {
return PreludeBase.TStringJ._plus_plus("We pay in ", arg$1);
}
}
final public static frege.runtime.Lambda _main(final frege.runtime.Lazy arg$1) {
return Prelude.println(PreludeText.IShow_String.it, TCurrency.paying("EUR"));
}
The TCurrency
class is just a namespace to hold items declared in the where
clause,
in our case the function paying
. Note how the argument is just a String
.
Note also that the construction of the euro
value has vanished and just the String
is passed.
Like with all functions, the method implementing the paying
function is a static one.
You'll need a qualified name like TCurrency.paying
to access it from java code.
Product types are algebraic data types with just one constructor. The classic example are tuples. In Frege, there are quite some variants: you can have anonymous fields or you can use record syntax. In the latter case, you can make certain fields strict. Here is an example:
data Person = P {!born :: Int, name :: String} where
--- tell if the person is born in this century
thisCentury P{born} = born > 2000
This compiles to a class named TPerson
, that has a static constructor mk
and several functions to manipulate the fields. Finally, the thisCentury
function lives as static method in this class:
final public static class TPerson extends frege.runtime.Algebraic {
private TPerson(final int arg$1, final java.lang.Object arg$2) { ... }
final public int _constructor() { return 0; }
final public static TPerson mk(final int arg$1, final java.lang.Object arg$2) { ... }
final public int mem$born ;
final public java.lang.Object mem$name ;
final public static int born(final TPerson arg$1) { ...}
final public static TPerson chg$born(final TPerson arg$1,
final frege.runtime.Lambda arg$2) { ... }
final public static TPerson chg$name(final TPerson arg$1,
final frege.runtime.Lazy arg$2) { ... }
final public static boolean has$born(final java.lang.Object arg$1) { ... }
final public static boolean has$name(final java.lang.Object arg$1) { ... }
final public static java.lang.String name(final TPerson arg$1) { ... }
final public static boolean thisCentury(final TPerson arg$1) {
return arg$1.mem$born > 2000;
}
final public static TPerson upd$born(final TPerson arg$1, final int arg$2) { ... }
final public static TPerson upd$name(final TPerson arg$1, final java.lang.Object arg$2) { ... }
}
Note how the strict field born
is represented as an unboxed primitive int
, whereas the name
is Object
. It can't be String
, because it is a lazy field and could thus hold either a String
or a reference to a computation that eventually yields a String
(i.e. Lazy
). Hence, looking for the most specific superclass, we have Object
.
You might ask: Why are all the methods
(except _constructor
which is due to inheritance of Algebraic
) static?
Certainly this is no good Java style?!
The answer is that a Frege function is supposed to be more than a plain Java method. For example, you may want to compare a list of Person
s by name:
sortBy (comparing Person.name) [ .... ]
But instance methods simply do not exist unless we do have an object. Hence all Frege functions are static methods, and there is a canonical way to make function objects out of static methods. This may be bad Java style, but remember, from Frege's point of view, Java is just "machine code".
Sum types are algebraic data types with at least 2 constructors, where at least one of them has at least one field. Prominent examples are Maybe a
, Either a b
and of course the list type, who has 2 constructors :
and []
.
For an example, we use this re-implementation of Maybe:
data Option a = Some a | None where
--- function to check if this is something
isSome (Some _) = true
isSome _ = false
and get:
public interface TOption extends frege.runtime.Value, frege.runtime.Lazy {
public TOption.DNone _None() ;
public TOption.DSome _Some() ;
final public static class DNone extends frege.runtime.Algebraic implements TOption {
private DNone() {}
final public int _constructor() { return 1; }
final public static TOption mk() { return it; }
final public static DNone it = new DNone();
final public DNone _None() { return this; }
final public TOption.DSome _Some() { return null; }
}
final public static class DSome extends frege.runtime.Algebraic implements TOption {
private DSome(final java.lang.Object arg$1) { mem1 = arg$1; }
final public int _constructor() { return 0; }
final public static TOption mk(final java.lang.Object arg$1) {
return new DSome(arg$1);
}
final public DSome _Some() { return this; }
final public TOption.DNone _None() { return null; }
final public java.lang.Object mem1 ;
}
final public static class M {
final public static boolean isSome(final TOption arg$1) {
final TOption.DSome $100000 = arg$1._Some();
if ($100000 != null) { return true; }
return false;
}
}
}
Our Option type appears as an interface TOption
with two classes DSome
and DNone
that implement it.
It takes no genius to guess that the latter are encodings of the alternatives Some
and None
.
Note how DNone
, having no data, implements the singleton pattern.
The classes for the alternatives implement interface Algebraic
, just like the classes generated for product types. The layout is the also the same. To create instances, use the mk
method. Anonymous fields have names like mem1
, numbered from one from left to right.
The interesting thing when one works with sum types is to find out which alternative it is. There are actually two ways to do this:
- switch on the return value of
_constructor()
and cast the value to the appropriate type. - (the preferred one) Use the disambiguation methods (here
_Some()
,_None()
) that give you eithernull
or a properly typed instance of the corresponding subclass.
The latter approach is also used by the code for our function isSome
. All member functions of a sum type X
are in class TX.M
(m like member).
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