Back to Home

Why Fantom?

fantom jvm

Why Fantom?

    Fantom is an object-oriented, statically-typed general-purpose language developed by the Frank brothers ( Brian Frank, Andy Frank ). One of the key features of Fantom is its powerful standard library, abstracted from the specific environment in which it will be executed. Currently, code written in Fantom can be run in the Java Runtime Environment (JRE), .NET Common Language Runtime (CLR), or compiled into JavaScript code.

    class HelloWorld
    {
      static Void main() { echo("Hello, World!") }
    }
    

    Portability


    The main reason for creating Fantom was writing software that can run on two Java VM and .NET CLR platforms. The reality is that most companies develop their software for one of these platforms. Even dynamic languages ​​like Python and Ruby run on one of these virtual machines. Fantom was created to solve the problem of portability from one virtual machine to another. Fantom source code is compiled into fcode - a bytecode that can easily be translated into Java bytecode or IL. Broadcasting occurs at runtime, which allows you to deploy the Fantom module as a separate file and run it on any VM.

    Portability means much more than just Java or .NET. As mentioned above, Fantom can be compiled in JavaScript to work in browsers. At the same time, Fantom is not going to stop there, the following goals are Objective-C for iPhone, LLVM , Parrot .

    Elegant API


    Although they don’t argue about tastes (Beauty is in the eye of the beholder), the creators of Fantom are really obsessed with a beautiful and convenient API. One of the basic principles of Fantom. Java and .NET share the same tendency to maximize functional division into small independent and abstracted units (classes). Fantom has the opposite philosophy - they believe that you can do with a small but powerful number of units.



    A good example is the java.io package , which contains more than 60 classes and interfaces; in Fantom, everything you need lies in four classes: File , Buf , InStream and OutStream . And this is how its use looks:

    Void textIO()
      {
        f := File(`text-io.txt`)
        // write text file (overwrites existing)
        f.out.printLine("hello").close
        // append to existing text file
        f.out(true).printLine("world").close
        // read text file as big string
        echo(f.readAllStr)
        // read text file into list of lines
        echo(f.readAllLines)
        // read text file, line by line
        f.eachLine |line| { echo(line) }
      }
    

    Typing


    The whole world has split into supporters of static and dynamic typing. The creators of Fantom believe that both sides are extremely critical of each other and choose the middle between them - a moderate approach to the typing system.

    From the side of static typing, Fantom requires a description of fields and method signatures indicating the types. It is good practice to fix the communication format between the components. For example, if I want to write a method that works with a string and a number, then this should be fixed directly in the code. Unlike static typing of method and field signatures, in code it often only interferes, forcing you to write unnecessary code. Type inference in Fantom avoids this problem. For example, cumbersome dictionary creation in java:

    Map map = new HashMap();
    map.put(1, "one");
    map.put(2, "two");
    

    Equivalent to one line in Fantom:
    map := [1: "one", 2: "two"]

    But sometimes you really need dynamic typing, so one of Fantom's key features is the ability to invoke a method using static or dynamic typing. If you call the method using the "." Operator, the call will be checked by the compiler and compiled into efficient machine code.

    Alternatively, you can use the "->" operator to specify a dynamic call. In fact, "->" will be redirected to a call to Obj.trap . By default, trap works like ".", But only at runtime. You can change this behavior by defining your dynamic design.

    if (a is Str) { return a->toInt }
    obj->foo         // obj.trap("foo", [,])
    obj->foo(2, 3)   // obj.trap("foo", [2, 3])
    obj->foo = 7     // obj.trap("foo", [7])
    

    Generics


    Interestingly, while Fantom is trying to make the code less typed, Java and C # are moving towards stronger typing, generics illustrate this trend. A fully parameterized system is directly related to the complexity of the system, so Fantom is now trying to find a balance between benefit and complexity.

    Fantom currently has limited generic support — the user cannot use their own. However, the three built-in classes can be List, Map, and Func. For example, a list of integers in Fantom is declared as Int []. The creators of Fantom believe that they are in the middle: there are generics, but without complicating the system.

    Impurities


    The issue of comparing a model from a subject area to a code is one of the most frequently resolved issues in software development. Usually in object-oriented programming, this phrase means modeling classes and interfaces. Java and C # use the same approach: classes support single inheritance, interfaces multiple inheritance, but do not support inheritance of implementations.

    Everyone who has worked with Java or C # knows that choosing between creating a class or interface is very important. Because if you choose a class, then you are using your only chance of inheriting the implementation. If you have a large and complex model, then the interfaces become an additional burden. For example, if there are two objects that have different heirs and equally implement the same interface, the functionality will have to be duplicated. In addition to duplication, there is the problem of changing the version of the interface, which affects all implementations.

    There are many good reasons why Java and C # use the class / interface model. Multiple inheritance opens many doors, but this happens due to increased complexity and rather unpleasant nuances. Fantom again takes the middle, called impurities(mixins). Impurities are interfaces that an implementation can store. To avoid multiple inheritance errors, some functions , such as state storing fields, are restricted for an impurity . Example impurities:

    mixin Audio
    {
      abstract Int volume
      Void incrementVolume() { volume += 1 }
      Void decrementVolume() { volume -= 1 }
    }
    class Television : Audio
    {
      override Int volume := 0
    }
    

    If interested, the java equivalent can be seen here .

    Modularity


    Modularity is an important aspect of software that a modern programming language needs.
    Unfortunately, the last decade in java we have been experiencing real hell with classpath . In addition to problems with the classpath , java made the wrong decision and switched to the J2SE monolith of 44Mb in size, which significantly slowed down our applications.
    .NET took this issue very seriously, so thanks to the version mechanism, GAC, and other tools, some of the problems from Java were resolved. But they lost the simplicity of the zip module and began to pack modules into DLLs that still contain many different spare parts, which makes it difficult to work with the module.

    In Fantom, everything is built on modules called pods.(pods). Like in java, under is just a zip file that you can easily see. The meta data is stored in a special file /meta.props , which is a key-value entry such as pod.name, pod.version, pod.depends, and so on . Dependencies of the pod are stored in its metadata and are described in an explicit and understandable way.

    To organize code in the namespace in java, packages are used, and the jar file is considered as a module. The mismatch between these concepts causes a big problem. You have the name of the java class, but it will not tell you in which jar file this class lives and where to load it from.

    Fantom made a simple name management solution: three hierarchy levels in the name “pod :: type.slot”, i.e. at the first level, always the name of the pod, then the type name (analogue of Class in java) and then the name of the slot (method or field). This consistent behavior in the namespace makes it easy to control the assembly process of large systems.

    Functional programming


    Java and C # are moving towards full support for closures, but after themselves they leave a big mark on history as the old API. Fantom was created with support for closures at the initial stage. Closures are a key feature of the language that is used everywhere and always.

    // print a list of strings
    list := ["red", "yellow", "orange"]
    list.each |Str color| { echo(color) }
    // print 0 to 9
    10.times |i| { echo(i) }
    // create a function that adds two integers
    add := |Int a, Int b->Int| { return a + b }
    nine := add(4, 5)
    // map Int to Str
    map := [0:"zero", 1:"one", 2:"two"]
    // empty Int:Str map
    Int:Str[:]
    // map
    [1, 2, 3].map { "f" + it * 2 } // ["f2", "f4", "f6"]
    //reduce
    ["2":2, "3":3, "4":4].reduce(0) |Int sum, Int v->Int| { sum + v } // 9
    

    Declarative description


    The best description would be a couple of examples:

    Window
    {
      title   = "Example"
      size    = Size(300, 200)
      content = EdgePane
      {
        center = Label  { text = "Hello world"; halign = Halign.center }
        bottom = Button { text = "Close"; onAction.add { it.window.close } }
      }
    }.open 
    homer := Person
    {
      name = "Homer Simpson"
      age  = 39
      children =
      [
        Person { name = "Bart";   age = 7 },
        Person { name = "Lisa";   age = 5 },
        Person { name = "Maggie"; age = 1 }
      ]
    }
    

    Parallelism


    Most languages ​​currently have one common model between threads. This means that the developer himself must take care of the memory lock. Incorrect blocking can lead to unpleasant errors, such as deadlocks, race conditions, etc. All this is a pretty low level to concurrency management.

    Fantom supports concurrency using the following techniques:
    1. Immutable Objects (Streaming Security)

      const class Point
      {
        new make(Int x, Int y) { this.x = x; this.y = y }
        const Int x
        const Int y
      }
      p := Point(0, 0)  // ok
      p.x = 10              // throws ConstErr
      vowels := ['a','e','i','o','u'].toImmutable
      

    2. Static fields can only store immutable objects, so different threads cannot access shared mutable data.
    3. Message model (actors) for communication between threads (Erlang-style)

          echo("\n--- echoActor ---")
          // this actor just echos messages sent to it
          a := Actor(ActorPool()) |msg| { echo(msg); return msg }
          // send some messages and have them printed to console
          f1 := a.send("message 1")
          f2 := a.send("message 2")
          f3 := a.send("message 3")
          // now block for the result of each message
          echo("Result 1 = " + f1.get) // message 1
          echo("Result 2 = " + f2.get) // message 2
          echo("Result 3 = " + f3.get) // message 3
      


    Syntactic sugar


    1. Default values for options

      class Person
      {
        Int yearsToRetirement(Int retire := 65) { return retire - age }
        Int age
      }
      

    2. Type inference - local variable types can be inferred
    3. Implicit access to fields

      class Thing
      {
        Int id := 0
        {
          get { echo("get id"); return &id }
          set { echo("set id"); &id = it }
        }
      }
      

    4. Zero types - separation of types into those that cannot accept null as a value, and which can.

      Str   // never stores null
      Str?  // might store null
      x := str.size   =>  x is typed as Int
      x := str?.size  =>  x is typed as Int?
      

    5. Checked exceptions removed, in C # Anders Hejlsberg also did not enable them (and did it right)
    6. Numeric Accuracy - Only 64-bit Int and Float Support Exists


    Resources


    1. Fantom eclipse-based IDE F4


    2. Web applications framework Tales
      //Hello world written in tales
      using tales
      class HelloWorld : Page{
        @Route{uri="/hello-world"}
        Void main(){
          response.writeStr("Hello World")
        }
      }
      

    3. Logic-free template engine Mustache
      using mustache
      Mustache template := Mustache("Hello, {{ name }}!".in)
      template.render(["name":"world"])
      

    4. Fantom Pod repository


    5. fantom.org


    Conclusion


    A common mistake is that Fantom is the next improved version of Java. But one has only to look at it, as a language with its own philosophy and concepts, as you begin to understand all its charm. In addition to stability, Fantom includes a number of features such as a friendly community , fully open source code , a nice IDE and many more pleasant things.

    Read Next