Fuzion: A New Language For The OpenJDK Unifying Java's Concepts
<p>Fuzion is a modern general purpose programming language that unifies concepts
found in structured, functional and object-oriented programming languages into
the concept of a Fuzion feature. It combines a powerful syntax and safety
features based on the design-by-contract principle with a simple intermediate
representation that enables powerful optimizing compilers and static analysis
tools to verify correctness aspects.</p>
<p>This talk will explain how Java's concepts such as classes, interfaces, methods,
constructors, packages, etc. are mapped to the single concept of a Fuzion
feature. The fzjava tool will be explained that provides Fuzion interfaces to
Java libraries. Finally, the Fuzion interpreter and a (planned) Java byte-code
back-end are presented.</p>
Introduction
Fuzion is a modern general purpose programming language that unifies concepts
found in structured, functional and object-oriented programming languages into
the concept of a Fuzion feature. It combines a powerful syntax and safety
features based on the design-by-contract principle with a simple intermediate
representation that enables powerful optimizing compilers and static analysis
tools to verify correctness aspects.
Fuzion was influenced by many other languages including Java, Python, Eiffel,
Rust, Go, Lua, Kotlin, C#, F#, Nim, Julia, Clojure, C/C++, and many more. The
goal of Fuzion is to define a language that has the expressive power present in
these languages and allow high-performance implementations and powerful analysis
tools. Furthermore, Fuzion addresses requirements for safety-critical
applications by adding support for contracts that enable formal specification and
enable detailed control over run-time checks.
Many current programming language are getting more and more overloaded with new
concepts and syntax to solve particular development or performance issues.
Languages like Java/C# provide classes, interfaces, methods, packages, anonymous
inner classes, local variables, fields, closures, etc. And these languages are
currently further extended by the introductions of records/structs, value types,
etc. The possibility of nesting these different concepts results in
complexity for the developer and the tools (compilers, VMs) that process and
execute the code.
For example, the possibility to access a local variable as part of the closure
of a lambda expression may result in the compiler allocating heap space to hold
the contents of that local variable. Hence, the developer has lost control over
the allocation decisions made by the compiler.
In Fuzion, the concepts of classes, interfaces, methods, packages, fields and
local variables are unified in the concept of a Fuzion feature. The decision
where to allocate the memory associated with a feature (on the heap, the stack
or in a register) is left to the compiler just as well as the decision if
dynamic type information is needed. The developer is left with the single
concept of a feature, the language implementation takes care of all the rest.
Fuzion Feature Declarations
A Fuzion feature has a name, similar to the name of a class or a function.
The main operation that can be performed on a feature is a feature call. The
constituents of a feature declaration are as follows:
Formal Arguments
Features may have a list of formal arguments, which are themselves features
implemented as fields. On a call to a feature with formal arguments, actual
arguments have to be provided to the call, unless the list of formal arguments
is empty.
Feature Result
The result of a feature call is an instance of the feature. Alternatively, a
feature may declare a different result type, then it must return a value of that
type on a call.
Closures
Features are nested, i.e., every feature is declared within the context of an
outer feature. The only exception is the universe, which is the outermost
feature in Fuzion. A feature can access features declared in its
outer feature or, recursively, any outer feature of these outer features. This
means, a feature declaration also defines a closure of the feature and its
context.
When calling a feature f1 declared as an inner feature of f2, the call must
include a target value which is the result of a call to f2, e.g., f2.f1.
Generics
Features may have generic type parameters. E.g. a feature declaration may leave
the actual type used within that feature open and to be defined by the user of
the feature.
The list of generic type parameters may be open, i.e., the number of actual
generic type parameters is not fixed at feature declaration. This turns out to
be useful in the declaration of choice types and functions as explained below.
Inheritance
Fuzion features can inherit from one or several other features. When inheriting
from an existing features, all inner features of the parent automatically become
inner features of the heir feature. It is possible to redefine inherited
features. In particular, when inheriting from a feature with abstract inner
features, one can implement the inherited abstract features.
A redefinition of an inherited feature may implement an inherited feature as a
routine or as a field. An inherited feature that is implemented as a field,
however, cannot be redefined as something else since fields might be mutable.
Inheritance may result in conflicts. An example would be two features with the
same name that are inherited from two different parents. In this case, the heir
must resolve the conflict either by redefining the inherited features and
providing a new implementation or by renaming the inherited features resulting
in two inner features in the heir feature.
Inheritance and redefinition in Fuzion does not require dynamic binding. By
default, the types defined by features are value types and no run-time overhead
for dynamic binding is imposed by inheritance.
A Contract
A feature may declare a contract that specifies what the features does and under
which conditions the feature may be called.
An implementation
Features must have one of the following implementations
a routine is a feature implementation with code that is executed on a call
a field is a memory slot that stores a value and whose contents are returned on a call
an abstract feature has no implementation and cannot be called directly, but can be implemented by heir features
an intrinsic feature is a low-level feature implemented by the compiler or
run-time system, e.g., the infix + operator to add two 32-bit integer values
may be an intrinsic operation.
A feature implemented as a routine can contain inner feature declarations.
Feature examples
Here is an example that declares a feature point that functions similar to a
struct or record in other languages:
point(x, y i32) is # empty
p1 := point 3 4
say "p1.x is {p1.x}" # will print "p1.x is 3"
say "p1.y is {p1.y}" # will print "p1.y is 4"
The next example shows a feature base that provides an inner feature plus
that adds its argument to the value passed to the enclosing base:
base(v i32) is
plus(w i32) => v + w
b1 := base 30
b2 := base 100
say (b1.plus 23) # will print "53"
say (b2.plus 23) # will print "123"
Fuzion FZJava Tool
Fuzion provides a tool fzjava that takes a Java module file and converts it
into Fuzion features. In the spirit of Fuzion, Java's packages, classes,
interfaces, methods, constructors, static methods and fields are all converted
into Fuzion features. Java methods that may throw an exception are
converted into features resulting in a choice type that is either the exception
type or the result type of that method.
A few intrinsic functions in the Java interpreter back-end use Java's reflection
API to access the corresponding Java code.
Basic approach
The FZJava tool converts each Java class into three Fuzion Features. Say you
have a Java class as follows
package x.y;
class MyClass
{
MyClass(String arg)
{
...
}
void myMethod()
{
}
void myStaticMethod()
{
}
}
This will be converted into a Fuzion feature x.y.MyClass that may not be
instantiated directly:
Java.x.y.MyClass(redef forbidden void) ref : Java.java.lang.Object(forbidden), fuzion.java.JavaObject(forbidden) is
unit myMethod is
...
This feature defines a Fuzion type x.y.MyClass and contains wrappers for the
instance methods. It is, however, not permitted to directly create instances of
this feature, which is ensured the forbidden parameter of type void (which
makes this feature 'absurd', it cannot be called directly since void values
cannot be created).
Additionally, a Fuzion feature containing features for static methods and
constructors is generated as follows:
Java.x.y.MyClass_static is
new(arg string) is
...
myStaticMethod is
...
This Feature defines a unit type. Finally, a Fuzion feature returning an
instance of this unit type is generated for convenience
Java.x.y.MyClass => x.y.MayClass_static
With this, the following code can be used to create an instance of MyClass
within Fuzion and call myMethod and myStaticMethod of this class:
o := Java.x.y.MyClass.new "test"
o.myMethod
Java.x.y.MyClass.myStaticMethod
The counterpart to import a Java class in Fuzion would be to declare a field
and assign the class' unit type value to it, i.e., the code above could be
simplified as
MyClass := Java.x.y.MyClass
o := MyClass.new "test"
o.myMethod
MyClass.myStaticMethod
or even
mc := Java.x.y.MyClass
o := mc.new "test"
o.myMethod
mc.myStaticMethod
using mc as an alias of Java.x.y.MyClass. Note that since the value of
field mc is a unit type, this assignment or any accesses of mc will not
execute any code at runtime.
Small Example
Here is a example how Java code can be used from Fuzion:
javaString := java.lang.String.new "Hello Java 🌍!" # create Java string, type Java.java.lang.String
javaBytes := javaString.getBytes "UTF8" # get its UTF8 bytes, type is fuzion.java.Array<i8>
match javaBytes
err error => say "got an error: $err"
bytes fuzion.java.Array =>
say "string has {bytes.count} bytes: $bytes"
javaString2 := java.lang.String.new bytes 6 bytes.count-6 # create Java string from bytes subset,
say "Hello "+javaString2 # append Java string to Fuzion string and print it
Web Server Example
The following code shows how Java APIs can be used to create a minimalistic web
server in Fuzion.
webserver is
# declare short hands to access Java net and io packages
net := Java.java.net
io := Java.java.io
# open socket
port := 8080
serversocket := net.ServerSocket.new port
match serversocket
err error => say "#### $err ####"
ss Java.java.net.ServerSocket =>
for n in 1.. do
say "accepting connections to localhost:$port"
match accept
unit => say "ok."
err error => say "#### $err ####"
accept outcome<unit> is
# accept and handle connection
s := serversocket.accept?
input := io.BufferedReader.new (io.InputStreamReader.new s.getInputStream?)
output := io.DataOutputStream.new s.getOutputStream?
req := read?
say "got request ({req.byteLength} bytes): $req"
if req.startsWith "GET "
(send200 "<html>Hello Fuzion $n!</html>")?
# close streams
input.close?
output.close
# helper to read request
#
read outcome<string> is
for
r := "", "$r$s\n"
s := input.readLine?
ready := input.ready?
until s = "" || !ready
r
# helper to send data in HTTP response with status 200
#
send200(data string) outcome<unit> is
output.writeBytes ( "HTTP/1.1 200 OK\n"
+ "Connection: close\n"
+ "Server: Fuzion demo WebServer v0.01\n"
+ "Content-Length: " + data.byteLength + "\n"
+ "Content-Type: text/html\n"
+ "\n"
+ data)?
Java methods that may result in an exceptions are represented by Fuzion features
that result in the type outcome<T> where T is the Fuzion type corresponding to
the Java result type of the method. outcome is a union type that may be either
error, which wraps the Java exception, or the actual result type T.
Java methods that result in void are mapped to Fuzion features that result in
unit, or, outcome<unit> in case the method has any declared exception.f
Fuzion Interpreter
Fuzion currently supports two back-ends: An interpreter implemented in Java and
running on top of OpenJDK and a C code generator implemented in Java using clang
to create machine code.
The goal of the interpreter was to quickly obtain a way to execute Fuzion
applications. Performance was not the main concern. The interpreter operates
directly on Fuzion's abstract syntax tree.
For better performance, a byte-code back-end is planned that will operate on
Fuzion's intermediate code instead.
Fuzion Byte-Code Back-End
Similar to Fuzion's C back-end, the byte-code back-end is planned to work on top of
Fuzion's intermediate code.
Fuzion intermediate code
Fuzion uses intermediate files during different stages of compilation: module
files that contain library code, application files for whole applications and
Fuzion intermediate representation files that serve as input to the back ends.
Fuzion uses a simple intermediate code to represent pre-compiled modules and
whole applications. This intermediate code serves as the input for static
analysis tools, optimizers and for different back-ends that produce executable
applications. The goal in the design of the intermediate file format was high
performance and simplicity for tools using this code.
The intermediate code is a binary format containing features and types in a way
that may be mapped to memory and used directly, so overhead of parsing this
format into an in-memory representation is avoided. In particular, if only parts
of a pre-compiled module are used by an application, there is no need to read and
unpack parts of the module intermediate representation that are not used.
For features containing code, a very simple stack-based format is used. There
are currently only ten different instructions:
* Unit -- produce a value of unit type
* Current -- produce the current instance as a value
* Constant -- produce a constant value
* Assign -- perform an assignment to a field
* Call -- perform a call to a given feature
* Tag -- convert a value into a tagged value of a choice type
* Match -- match a choice type
* Box -- convert a value type to a ref type
* Unbox -- extract the value from a ref type
* Pop -- drop the top element from the stack
The intermediate code uses indices to refer to features and types within
intermediate files. This means that lookup is very efficient, but it also means
that a change in a library module requires recompilation of all dependent
modules. Any incompatibilities would be found at compile time instead of
resulting in something like Java's IncompatibleClassChangeError at run-time.
Instance Implementation
Fuzion instances should be mapped directly to instances of Java classes
generated for the corresponding features. Fuzion fields should be mapped to Java
fields and Fuzion routines to Java methods.
Fuzion instances that are accessed only locally by their defining features code,
i.e., they do not live longer than their code is executed and they are not
passed to any other features, might be optimized and implemented as Java methods
using local variables instead of fields.
Call Implementation
Fuzion's support multiple inheritance similar to Eiffel. Java support multiple
inheritance only for interfaces, so one way to implement dynamic binding in
calls would be to define interfaces for every Fuzion feature and use the JVM's
invokeinterface byte-code for calls.
A more flexible alternative might be to use invokedynamic to implement dynamic
dispatch, but this will likely result in higher overhead compared to highly
optimized invokeinterface implementations.
Conclusion and Next Steps
The Fuzion language definition and implementation are far from stable, but are
getting closer to become useful. Big improvements come from the ability to
pre-compile modules and from the foreign language interface for Java, which
makes a giant code base accessible for Fuzion applications to build on.
Additionally, new projects such as the language server implementation for Fuzion
by Michael Lill help by integrating Fuzion support in popular IDEs and editors.
Main points that are missing right now are
a powerful standard library
additional library modules for all sorts of application needs
low-level foreign language interface for C
actual implementations of static analyzers and optimizers
highly optimizing back-ends
garbage collection for the C back-end
documentation, tutorials
enthusiastic contributors and users!
Please feel free to contact me in case you want to use Fuzion or want to help
making it a success!
Weitere Infos