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.
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.
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.
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:
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.
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.
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.
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.
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 feature may declare a contract that specifies what the features does and under which conditions the feature may be called.
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.
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 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.
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.
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
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 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.
Similar to Fuzion's C back-end, the byte-code back-end is planned to work on top of Fuzion's 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.
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.
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.
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
Please feel free to contact me in case you want to use Fuzion or want to help making it a success!
Speakers: Fridtjof Siebert