Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

The Adventurous Developer’s Guide to JVM Languages – Fantom

Continuing our series about JVM languages, this post is about a less-known JVM language called Fantom. Fantom is a bit different from the languages we looked at previously–including Ceylon, Kotlin, Xtend, Groovy and even Java 8’s new lambdas — as it targets multiple platforms. Compilation for JVM, .Net and JavaScript are currently supported, and given the infrastructure they’ve put into place, it should be possible to target other platforms as well.

But despite the fact that portability and platform maturity are important issues for Fantom’s authors (Brian and Andy Frank), it’s not what defines the language for them. They claim that Fantom is a practical language, for getting things done.

Keeping this philosophy in mind, I’ll follow the tradition set by previous posts in the series to write a simple, file-serving HTTP server. The first step to do that is to setup an environment and the tooling. Fortunately, Fantom made this very easy for me. Xored makes an Eclipse-based IDE called F4, which includes everything I needed to get Fantom up and running.

Pods / Scripts

Fantom can execute files as scripts, you just need to put a class with main method into a file and there’s an executable fan to run it.

class HelloWorldishScript
{
  static Void main() { echo("Whoah! Is it that easy?") }
}

However that’s not the main way of structuring Fantom programs. For larger projects and production systems precompiled modules, called pods, are created using Fantom’s build toolkit.

The build is orchestrated by a build script, which is essentially just another piece of Fantom code. Take a look on a build script for http server mentioned above.

using build
class Build : build::BuildPod
{
  new make()
  {
    podName = "FantomHttpProject"
    summary = ""
    srcDirs = [`./`, `fan/`]
    depends = ["build 1.0", "sys 1.0", "util 1.0", "concurrent 1.0"]
  }
}

There are a couple of things to notice straight away, dependency specification for example. This way building larger system becomes easier and may I say less jar-hellish. Additionally, a pod does not only define deployment namespace, but also type namespace, unifying and simplifying these two. Now you can see that my server depends on pods: sys, util and concurrent.

Standard Library and Elegance

Fantom poses itself not as just a language for the JVM platform (or any other platform in fact), but more as a platform itself relying on the JVM. A platform offers API and Fantom makes sure that API is beautiful and elegant. On the most basic level it offers several literals like:

Duration d := 5s
Uri uri := `http://google.com`
Map map := [1:"one", 2:"two"]

And it’s a little thing having a duration literal, but when you want to set a timeout to something it just feels so right that someone has thought about that for you.

IO API is collected under several base classes like Buf, File, In/OutStreams which is very pleasant to use. Network communication is also supplied, JSON support, DOM manipulation and graphic library. Essential things are there for you. Util pod contains some useful things as well. Instead of having a class with main method, file server extends AbstractMain class and gets parameters passing, logging setup for free. Another API which is very pleasant to use is Fantom’s concurrency framework, but I’ll talk about that in a few minutes.

Interop

Any language that builds on top of JVM platform offers some interoperability with plain Java code. It is essential to make use of the enormous ecosystem Java has. And it is said that creating a language better than Java is easy. Creating a language better than Java that offers decent interoperability with Java is harder. Part of that is dealing with the collections (which is kinda old and plain, and sometimes a pain to use).

Fantom offers an Interop class with a toFan and toJava methods to convert types back and forth.

// socket is java.net.socket
InStream in := Interop.toFan(socket.getInputStream)
OutStream out := Interop.toFan(socket.getOutputStream)

Here you can see that we have a plain Java Socket and it naturally provides us with Java Input and OutputStreams. Using Interop we convert them to Fantom counterparts and use later just them.

Static/Dynamic typing

Another major topic in any language post is if the language supports static / dynamic typing. Fantom here hits the middle ground and I personally liked it a lot. Fields and method signatures feature strong static typing. But for local variables, types are inferred. This leads to a mix that is really intuitive, method contracts are spelled out, but you don’t actually need to type everything.

And naturally there are two method invoke operations in Fantom. The dot (.) invocations go through a compiler check and are strongly typed, and arrow (->) invocations do not. This enables duck-typing and everything you want from a dynamically typed language.

Immutability and Concurrency

Fantom offers actors framework to handle concurrency. Message passing and chaining asynchronous calls are easily incorporated into code. To create an actor (which would be backed by some ActorPool and subsequently by a thread pool), you need to extend an Actor class and override (curiously enough you must explicitly type the override keyword) a receive method, an example can be found in the same HttpServer.fan.

Please note that to share no state between threads, Fantom will insist that you only pass immutable messages to your actors. Immutability is built in the language by design, so you can define your classes and all their fields as const. Compiler verifies that the message for the actor is in fact immutable and will throw an exception otherwise.

A cool and somewhat difficult to find tip is that if you really need to pass a mutable object you can wrap it into Unsafe (no, not that Unsafe :), Fantom’s Unsafe).

while(true) {
  socket := serverSocket.accept
  a := ServerActor(actorPool)
  //wrap a mutable socket to sign that we know what are we doing
  a.send(Unsafe(socket))
}

Later you can obtain the original object back.

override Obj? receive(Obj? msg) {
  // Unsafe is just a wrapper, get the socket
  log.info("Accepted a socket: $DateTime.now")
  Socket socket := ((Unsafe) msg).val
  …
}

You can find a bit more information about actors in this little doc. However it won’t disclose all the details. In general, documentation for the Fantom is decent, but the Internet is not exactly full of examples, so some things take tinkering to get right.

Functions and Closures

Fantom is an object-oriented language and, as in many modern languages, functions are first-class citizens here. A following example shows an Actor creation, where we specify the receive function implicitly and then send a message several times.

pool := ActorPool()
a := Actor(pool) |msg|
{
  count := 1 + (Int)Actor.locals.get("count", 0)
  Actor.locals["count"] = count
  return count
}

100.times { a.send("ignored") }
echo("Count is now " + a.send("ignored").get)

Fantom’s syntax is friendly enough and doesn’t get in the way. This is probably the right place to notice that variable declarations have := syntax, which would be a disaster for me (I don’t have the best memory for small syntax details). However, the IDE supports this well and notified me every time I made this mistake.

Little things

Now the whole time I explored Fantom I was pleasantly surprised by small things that help make the language better. For example, null supporting types: I can declare method to accept null as an argument or not.

Str   // never stores null
Str?  // might store null

This way, the code is not polluted with null checking and interoperability with Java is easier.

There are other features that are worth mentioning. Multi-line strings with variable interpolation:

       header :=
                "HTTP/1.1 $returnCode $status
                 Server: Fantom HTTP Server 1.0
                 Date: ${DateTime.now}
                 Content-type: ${contentType}
                 Content-length: ${content.size}

                 ".toBuf

Parameters can have default values, mixins and declarative programming support, and operator overloading. Everything is in place. One thing I didn’t find and felt the lack of were tuples. However I only needed that for multiple returns, so a list was enough.

Conclusion

Fantom is a mature and stable programming language with a very fluent and elegant API. By no means do I claim to be an expert, so please show me Fantom better if you like: my github repo is open to comments and pull requests.

PS.

Oh, and I almost forgot: if you judge a horse by their teeth, then so would you judge a programming language by its syntax. Fantom has a very Java-like syntax, with no semicolons and with a number of its own keywords thrown in. Another important thing here is function definitions and closures; remember, methods are statically typed, so you provide a signature using post (| |) notation, like this:

// pass receive to constructor as a closure function
a := Actor(pool) |Int msg->Int| { msg + 1 }

Otherwise, its a nice syntax to use coming from a Java land for sure.

Psst! If you liked this post, we wrote a 50-page RebelLabs report on Java 8, Scala, Groovy, Fantom, Clojure, Ceylon, Kotlin & Xtend.

Get the FULL REPORT on all 8 JVM languages

  • arhan

    Some features look exactly like in the other languages from the series: multi-line strings and interpolation in Groovy is very similar. Also the nullable types in Ceylon and Kotlin. Constant vs variable assignment ( “=” vs “:=” ) in Ceylon. Collection literals in Groovy, etc.

    Seems like all the languages have a common convergence at some point :)

  • Yes, and it felt sooo right, when I wrote that sample server. It’s like, “hmm, I want to use this feature”, boom, it goes exactly as I expected, felt like being home.

    And a fun fact about Fantom multiline strings: it’s a compilation error if you indent the subsequent line less than the first one. for example if I do:
    a := “foo
    bar”

    It gives me: “Leading space in multi-line Str must be 6 spaces”, so I don’t need to guess how it will treat those spaces and don’t need to do formatting sacrifices, like Mirko did with groovy (check out sendHtmlResponse @ line61 here: http://goo.gl/OL4un)

  • Guest

    @Oleg: while I agree that Fantom’s multi-line feature with automatic indentation feels more natural than Groovy’s let me remind readers of two GDK (http://groovy.codehaus.org/groovy-jdk/) methods available on java.lang.String: stripIndent() and stripMargin().

    The following snippets produce the well formatted output without forcing multi-line strings to skip left indentation

    def html = “””

    ${params.title}

    ${params.body}

    “””.stripIndent(4)

    def html2 = “””|
    |
    | ${params.title}
    |
    | ${params.body}
    |
    |”””.stripMargin(‘|’)

  • @Oleg: while I agree that Fantom’s multi-line feature with automatic indentation feels more natural than Groovy’s let me remind readers of two GDK ( http://groovy.codehaus.org/groovy-jdk ) methods available on java.lang.String: stripIndent() and stripMargin().

    The following snippets produce the well formatted output without forcing multi-line strings to skip left indentation

    https://gist.github.com/aalmiray/4944681

  • I like .stripMargin(‘|’) version a lot! It is very nice. Also I didn’t want to belittle groovy in any way :)

    it’s just when F4 threw me that compilation error, I was like, wow, you guys really knew that I would make a mistake there. So I thought it’s a fun fact to share. And thank you for pointing out Groovy awesomeness here!

  • Andy Frank

    One correction that might be misleading ;) You should consider Unsafe an absolute last resort – since it can undermine the entire concurrency model in Fantom. If you need to pass mutable state b/w Actors – you would use serialization – which is a built-in feature:

    http://fantom.org/doc/docLang/Actors.html#messages

  • The Fantom erro reporting is fantastic across the board. every time I go back to Java I feel the compiler errors are so poor.