A really cool, interesting project, a new language being presented by Fridtjof called
Fusion on the OpenJDK.
Final session of the day, thank you so much for being with us, some of you, all day.
So let's start with Fusion or end with Fusion.
OK, thank you for staying so long, thank you for Gertjans, he just downloaded the latest
version of my slide deck and was a bit shocked that it's almost one hundred slides and only
have twenty minutes left, so let's see how that will work out.
For those who came for the Fusion stickers, please pass them around and take one.
So Fusion, a new language, and it's different, it's more from a Java perspective.
But there's some overlap, you will see.
So basically the idea, the original idea of Fusion was to have something like a simpler
Java to simplify Java's features into Fusion features.
Bit of my background, I did work on compilers for about thirty years, a big part of that
working on read time Java implementations, read time garbage collection and so on.
Start with motivating a quote from John Bacchus, the inventor of Fortran, who worked a lot
on functional programming but was very disappointed because his work on functional programming
basically failed and would likely always fail because functional programming is easy to
do hard things but incredibly difficult to do simple things.
Fusion has evolved into a functional language and I think, I hope, I find ways to even make
the easy things easy with that.
So the motivation of Fusion is we see that languages like Java get more and more things
packed in there.
We already have classes, methods, interfaces, constructors, traits in other languages, records,
structs, packages and so on.
In Fusion, all of these map to one single concept, which is the concept of a Fusion feature.
Then I see today's compilers are much more powerful, so actually to distinguish whether
some feature is used like a method or like a class or like a constructor is something
that the compiler decides, then it is not needed that the developer decides that.
And we see that more and more systems are becoming safety critical, so we need to ensure
correctness.
And I see that tools have to play in a very important role in ensuring this correctness
by static analysis.
Fusion is available on GitHub, there is a website flung.dev that gives an introduction into
the language with lots of examples, lots of design documents, lots of ideas collections.
Please go through that.
I can't give a language introduction here, but yeah, you'll find more there.
Fusion is backed by a small company, Tokiwa, with currently four employees.
One of them is sitting here with us in the group, Michael.
Now coming actually to this talk.
So I will start with a very quick introduction into what the Fusion language looks like from
a Java perspective, then talk a bit about side effects and their dangers, then propose
algebraic effects as a solution to manage side effects and give lots of code examples
how you could do these things in Fusion.
So here a small example in Fusion, I will give a Java equivalent on the right side and
the Fusion code on the left side that you can quickly understand what it's about.
So I said Fusion maps Java features to Fusion features.
So in Java, if you have a package in Fusion, it's just a Fusion feature, in this case Demo.
If you have a class in Java, it is also a Fusion feature that is nested in another Fusion
feature.
If you have a method in Java, it is again a Fusion feature that is nested in this case
in the Hello surrounding feature.
In this case, what makes this feature different is that it's a function that returns a result
which you can see from the result type here, which is unit.
Unit type in Fusion is pretty much what the void type is in Java, but with the exception
that it is a real type, so you can declare variables of type unit, you can return this
as a value.
It is not a very interesting value, but you can have this as a full-fetched type with
only one single value.
In contrast to that, void is also a type in Fusion, but that gets interesting again because
void is a type that has no values.
So basically the result type of something like system exit in Fusion would be void,
which means it will never return.
Then printing something is easy, there is a standard library function say that, in this
case, prints hello world.
Fusion uses a lot of type inferencing, so the result type unit here actually can be
inferred because that's also the result type of say, so we don't need to explicitly note
this.
Then, I'll go back here, very similar to Java.
If you have code like that, you don't have anything to run yet.
You need in Java, you need some main function.
In Fusion, there is one feature which is called the universe which surrounds everything and
code put in the universe like here gets executed if you run your application.
You can pass arguments to features and arguments are fields within those features and fields
are also features, so they come in the same class, but features that are pre-calculated
that hold a value.
Fusion has inheritance, so you can define a feature hello to that inherits from hello.
You can create an instance of that and call features on that.
That much to a quick introduction into the language syntax and how it works.
There's a number of things that Fusion does not have and mostly because these are things
that are considered to a certain extent harmful in a safe to critical environment.
There's no dynamic loading, there's nothing like macros, no reflection, no pointer arithmetic.
Many of these things also Java doesn't have.
There is no uncontrolled mutual abilities, so you cannot easily change variables.
There's no direct support for exceptions in the language.
The reason for this is we must know what the code does.
We want to do static analysis of the code to ensure safety and also to a certain extent
to allow better optimizations to increase the performance.
Bitmare more on side effects and security.
We learned a lot about security today already in earlier talks, but mostly addressing security
aspects of the software development process and managing of security issues.
I now come from the language side and say what we could do from the programming language
to improve the security.
If you look back at recent securities a lot, we learned about lock for J today, but there
are similar things with Spring Shell, even the Rust community has similar issues.
What these issues have in common is that library code that is used has unexpected side effects.
You use a logging library, you don't expect this to go to the Internet and make an arbitrary
connection and download code from somewhere else in the world.
That is the common problem.
One way that is used by many new upcoming language to control side effects is to use
algebraic effects.
Let me quickly explain to you what algebraic effect is.
An algebraic effect is basically a set of non-functional operations that code might
perform.
These are operations that do not have an effect on the actual calculation on the return value
of a function.
Java already has one kind of algebraic effect built into the language, which is throws for
methods that throw exceptions.
But algebraic effect is more a broader concept.
This is just one example that Java supports.
Any operation in an algebraic effect can either resume or abort.
So typically, if an algebraic effect is reading some data from some external input, it would
return the read data and resume operation with the value that was read, while an operation
that would be something like throw an exception would perform an abort, so it will not return
but jump back to the corresponding handler.
Side effects can be implemented by different implementations of the effect handlers.
So there is no strict fixed wiring from the operations to a particular implementation.
And very similar to exception handlers, effects may be nested.
There's a kind of contrary view to two words algebraic effects.
You can see algebraic effects.
What I've presented so far is as the effects that the code might have, but you could also
see them as capabilities that the code might require.
Martin Odelski is starting a big research project in that area.
What I do is I define my exception, which is our exception implementation, as a feature
inheriting from the base library feature simple effect, which is just a basic standard
effect, and our implementation of throw is just abort.
So the simplest way to stop an operation.
And now we define one feature that throws an exception, and what we do here is we call
the operations throw, but we need to have an instance of the algebraic effect.
And the syntax we use in Fusion for that is we use the type of the effect, which is my
exception, from the environment.
So.n means taking the innermost instance of that effect in the current environment and
calling that operation on it.
When we do that, we should declare in the signature of the function that this function
requires the effect my exception.
So this is very similar to a throws clause in Java.
If I throw an unchecked exception, I need to declare that.
Here if I require a certain effect, I declare this with the exclamation mark.
Now I add some prints just to show what this code actually does, and I want to call this
function, this feature F now, to call it.
I have to first install an instance of the effect.
So I create an instance of the my exception effect here called use on is, which is a standard
effect function that takes a lambda expression, which then calls the code that is executed
while this effect is installed.
So adding some more prints that you see what is happening, and if I now run this code here,
you see that it prints, the exception is installed, it prints the before throw, throw directly
jumps, very similar like an exception, out of the use here, and we continue with the
we are done.
So the code after an operation that aborts here will not be executed at all, very similar
to exceptions.
Yeah, now let me talk a bit about mutation.
I told you that fusion doesn't allow direct mutation of fields, so fields are immutable,
which means if we do have code like that, we declare a field x, assign 1, 2, 3 to it,
print it, and then assign 2 times x to another field x.
We see the expected behavior, but if we create a feature that prints this field x, and try
to compile this, or try to run this, we actually get an error, because the problem is this
x here is not clear which one is referenced here, because we have two different variables
here, there's two axes here, the first and the second, and they are only visible for
the code following that.
So we get an error message that there are two different axes, and that source code position
here doesn't know which one to choose.
So really every assignment creates a new field, and these fields are immutable.
To make them mutable, to get actually the desired effect that we can print x here, we
would have to create a mutable integer value, which is with the base library function mute,
creates a mutable instance, assign this to the variable x, and now if we want to update
this, we don't create a new field, which would be the colon equals operator, but we have
an error operator which updates the value with a new value.
If we run this now, behaves first like the code before, but this time the show x function
can actually access this single variable, because now we have only one field left.
We can now analyze this code for the effects that this code requires, and if we do that,
we see there's two effects, there's IO out, this performs output, and there's the mutate
effect because we have an update of a mutable field in our code.
Now not all variables, very few variables actually usually need to be mutable.
Here's an example of a small loop with an index variable counting from 0 to 9, and printing
them, if we analyze this code for effects, we see that this only depends on the IO out
effect.
The reason is that every loop iteration creates a new instance of that variable, so we don't
update the i variable here, but we have one independent instance for every iteration of
the loop.
So no variable is mutated, a new instance is created for every iteration.
I want to talk a bit about error handling now, and show how the function can produce
an error, and show them three different ways of how error handling could be done.
The function I use is just to divide, that divides two integers, and I call this in a
show div function that calls divide and prints the result, and then I call this with three
different value pairs, and if I call this, I get, not very surprising, I get an error,
there is a division by zero, the precondition of the division is not fulfilled.
So that's the standard error handling in fusion, but it's not very nice because you
have the whole application for failing.
If you want to now somehow treat that error, what we could do is return an outcome, which
is similar to Rust's result, which is basically a choice type between an error and an actually
32-bit integer, and check the case, if B is zero, we return an error, otherwise we return
the result of the division, and if we run this, now the application runs through, it
doesn't terminate, and in the middle case, we print the outcome, which is an error here.
But if we want to now actually, after calling the divide, want to know was this divide successful
or not, we would need to check the cases, so we need to distinguish whether we actually
got a value, or we got an error, we can do this with a match over the different choices.
Now an alternative would be to use the standard library try effect, which is kind of the default
exception based on algebraic effects in fusion, and to do that, instead of returning an outcome,
this would be just a function returning a 32-bit integer, but requiring the try effect
to be installed, and now instead of causing an error, we would raise the error of the
try instance in the current environment, so we don't need the else anymore because the
raise would abort and would return immediately, so we could just continue with the code there.
And when we call the divide now, we have to call it with an instance of the try effect
being installed, so just like before, this can be done through a base library function.
Try that installs an instance and calls the lambda, which is provided as a parameter,
and this can then be matched very similarly to the outcome, but the big difference is
that now the code in between, in between the position where the error is, and where we
have this call, does not need to pass along these outcomes all the way, I'll come to an
end very soon, but we can directly work with the i32s and the try would jump out directly,
so we would see this outcome.
So the penultimate slide, the current slide, the status of fusion, it's still very much
in development, the language is getting a bit more stable recently, but there's still
a lot of work, mostly also in the base library.
The current implementation has two backends, one running on a JVM, and there's also a C
code backend, and there's basic analysis tools available, as I've shown you, the effects
analysis.
Java maps actually very well to fusion, there's a tool that allows calling all of Java APIs,
creating Java APIs from a fusion APIs from a Java module that we can call into Java,
what doesn't work yet well is calling back from Java into fusion, but there's at least
in one way, it's one-to-one mapping.
We have effects to encapsulate non-functional aspects, and I ask everyone please have a
look, we're happy for any feedback.
Thank you for staying so long, I think time is over.
The match is still needed because this try here installs the effect, and an effect in
the case of an abort has to provide some way to join the value that is returned in the
non-abort case with the value that is returned in the abort case, and for the try effect
this join is just made by producing a value of type outcome, which is the choice between
error and the value, but there could be other effects that would just replace it by a default
value in that case, so it depends on the effect, but here it's definitely still needed, yeah.
Do we have time, yeah?
Yeah, I saw that at some point you showed that there was an IO effect, and I also saw
a lot of code that uses the same function, which I presume uses that effect, but can
you see the effect using any of the examples?
Okay, yes, you took very good care, thank you.
Yeah, it is not decided yet where the compiler should be strict and require this annotation.
The current idea is that for basic code we should not require this annotation, but for
a public library function we definitely want to know what are the effects.
So, I don't want to enforce this for everything or for all the intermediate values, and there's
also some cases where only a static analysis of a whole application can actually determine
what the effects are, so static analysis plays a very important role there.
I don't want to enforce too much typing basically for these effects here.
Another one, there is, like John for example has this distinction between runtime exceptions
and checked exceptions, and there are just these kind of exceptions that can have pretty
much any code, like out of memory exception or static load, and I wonder how do you handle
these kind of cases?
Oops, they're shutting us down here.
Okay.
It's a small hint.
I actually, it's not nothing of that is done yet, but I think I would like to get one step
further and make it user configurable.
What are the effects that you want to have considered acceptable in your environment?
Like you want to have maybe add some debugging print or some logging in somewhere nested
in some internal function that shouldn't have forced you to add effects all over through
the code.
So we must have some set, some way to define for the debugging build.
These are the effects that are in there, and please don't complain about that.
But we have to still see how we actually will do that.
Thank you so much.
Thank you for saying so long.
Thank you.
Thank you very much for attending the freegear room.
This will be a room next year again.
Hopefully we'll have two days and we'll have more time for sessions and hopefully many
of you will submit proposals.
You will all be very welcome to present in the freegear room next year.
Thank you very much for coming.