Pull to refresh

Почему Fantom?

Reading time8 min
Views18K
Fantom — это объектно-ориентированный, статически-типизированный язык общего назначения, разработанный братьями Фрэнками (Brian Frank, Andy Frank). Одна из ключевых особенностей Fantom — это мощная стандартная библиотека, абстрагированная от конкретной среды, в которой она будет исполняться. В данный момент код, написанный на Fantom, можно запустить в Java Runtime Environment (JRE), .NET Common Language Runtime (CLR) или же скомпилировать в код на JavaScript.

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

Переносимость


Основной причиной создания Fantom было написание программного обеспечения, которое может запускаться на двух платформах Java VM и .NET CLR. Реальность такова, что большинство компаний разрабатывают свое программное обеспечение для одной из этих платформ. Даже такие динамические языки, как Python и Ruby работают на одной из этих виртуальных машин. Fantom был создан решить проблему переносимости с одной виртуальной машины на другую. Исходный код Fantom компилируется в fcode — байткод, который легко может быть транлирован в Java байткод или IL. Транслирование происходит во время выполнения, что позволяет развертывать Fantom модуль, как отдельный файл, и запускать на любой VM.

Портативность означает значительно больше, чем просто Java или .NET. Как было сказано выше, Fantom может компилироваться в JavaScript для работы в браузерах. При этом Fantom не собирается останавливаться на достигнутом, следующие цели — Objective-C для iPhone, LLVM, Parrot.

Элегантное API


Хотя о вкусах и не спорят(«Beauty is in the eye of the beholder»), создатели Fantom по-настоящему одержимы красивым и удобным API. Один из основных принципов Fantom. Java и .NET имеют одну общую тенденцию максимального деления функционала на маленькие независимые и абстрагированные единицы (классы). Fantom имеет противоположную философию — они верят, что можно обойтись малым, но мощным количеством единиц.



Хорошим примером является пакет java.io, который содержит больше 60 классов и интерфейсов, в Fantom все необходимое лежит в четырех классах: File, Buf, InStream и OutStream. И вот так выглядит его использование:

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) }
  }

Типизация


Весь мир раскололся на сторонников статической и динамической типизации. Создатели Fantom считают, что обе стороны крайне критично относятся друг к другу и выбирают середину между ними — умеренный подход к системе типизации.

Со стороны статической типизации, Fantom требует описание полей и сигнатур методов с указанием типов. Это хорошая практика, зафиксировать формат общения между компонентами. К примеру, если я хочу написать метод, который работает со строкой и числом, то это должно быть зафиксировано прямо в коде. В отличие от статической типизации сигнатур методов и полей, в коде она часто только мешает, заставляя писать ненужный код. Вывод типов в Fantom позволяет избежать этой проблемы. Например, громоздкое создание словаря в java:

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

Эквивалентно в Fantom одной строчке:
map := [1: "one", 2: "two"]

Но иногда вам действительно нужна динамическая типизация, поэтому одной из ключевых особенностей Fantom является возможность вызвать метод, используя статическую или динамическую типизацию. Если вы вызываете метод с помощью оператора ".", вызов будет проверен компилятором и скомпилирован в эффективный машинный код.

С другой стороны, вы можете использовать оператор "->", для указания динамического вызова. На самом деле, "->" будет перенаправлен на вызов Obj.trap. По умолчанию trap работает как ".", но только во время выполнения. Вы можете изменить это поведение, определив свой динамический дизайн.

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])

Дженерики


Интересно, что пока Fantom пытается сделать код менее типизированным, Java и C# идут в сторону более строгой типизации, дженерики иллюстрируют этот тренд. Полностью параметризированная системы напрямую связана со сложностью системы, поэтому в Fantom сейчас пытаются найти баланс между пользой и сложностью.

В настоящий момент у Fantom ограниченная поддержка дженериков — пользователь не может использовать свои собственные. Однако, три встроенных класса могут List, Map и Func. К примеру, список целых чисел в Fantom объявляется как Int[]. Создатели Fantom считают, что попали в середину: дженерики есть, но без усложнения системы.

Примеси


Вопрос сопоставления модели из предметной области в код — один из самых часто решаемых вопросов в разработке программного обеспечения. Обычно в объектно-ориентированном программирование данная фраза означает моделирование классов и интерфейсов. Java и C# используют одинаковый подход: классы поддерживают одиночное наследование, интерфейсы множественное наследование, но не поддерживают наследование реализаций.

Каждый, кто работал с Java или C#, знает, что выбор между созданием класса или интерфейса очень важен. Потому что, если вы выберете класс, то вы используете свой единственный шанс на наследование реализации. Если у вас большая и сложная модель, то интерфейсы становятся дополнительной нагрузкой. К примеру, если есть два объекта, имеющих разных наследников, и одинаково реализующие один и тот же интерфейс, функционал придется продублировать. Кроме дублирования есть проблема изменение версии интерфейса, которая затрагивает все реализации.

Есть множество хороших причин почему Java и C# используют модель классов/интерфейсов. Множественное наследование открывает многие двери, но происходит это за счет увеличения сложности и довольно неприятных нюансов. Fantom снова занимает середину, называемую примеси (mixins). Примеси — это интерфейсы, которую могут хранить в себе реализацию. Чтобы избежать ошибок множественного наследования, у примеси ограничены некоторые функции, такие как поля, хранящие состояние. Пример примеси:

mixin Audio
{
  abstract Int volume
  Void incrementVolume() { volume += 1 }
  Void decrementVolume() { volume -= 1 }
}

class Television : Audio
{
  override Int volume := 0
}

Если интересно, java эквивалент можно посмотреть здесь.

Модульность


Модульность — важный аспект программного обеспечения, который необходим современному языку программирования.
К сожалению, последнюю декаду в java мы испытываем настоящий ад с classpath. Кроме проблем с classpath, java приняла неправильное решение и перешла к монолиту J2SE размером 44Mb, что значительно замедлило наши с вами приложения.
В .NET подошли к этому вопросу крайне серьезно, поэтому благодаря механизму версий, GAC и другим средствам часть проблем из Java была решена. Но они утеряли простоту zip модуля и начали паковать модули в DLL, которые содержат еще много разных запчастей, что не позволяет легко работать с модулем.

В Fantom все строится на модулях, называемых подами (pods). Как и в java, под — это просто zip файл, который можно легко посмотреть. Мета данные пода хранится в специальном файле /meta.props, представляющее собой записи вида ключ-значение, такие как pod.name, pod.version, pod.depends и так далее. Зависимости пода хранятся в его мета данных и описаны в явном и понятном виде.

Для организации кода в пространстве имен в java используются пакеты, а jar файл рассматривается как модуль. Несоответствие между этими понятиями вызывает большую проблему. У вас есть имя java класса, но оно вам не подскажет в каком jar файле живет этот класс и откуда его загружать.

В Fantom было принято простое решение для управления имен: три уровня иерархии в имени «pod::type.slot», т.е. на первом уровне всегда имя пода, затем имя типа (аналог Class в java) и затем имя слота (метод или поле). Такое согласованное поведение в пространстве имен позволяет легко управлять процессом сборки больших систем.

Функциональное программирование


Java и C# движутся в направлении полноценной поддержки замыканий, но после себя они оставляют большой след истории, в качестве старого API. Fantom создавался с поддержкой замыканий на начальной стадии. Замыкания — ключевая особенность языка, которая используется везде и всегда.

// 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

Декларативное описание


Наилучшим описанием будет пара примеров:

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 }
  ]
}

Параллелизм


Большинство языков в настоящий момент имеют одну общую модель между потоками. Это означает, что разработчик сам должен позаботиться о блокировке памяти. Некорректная блокировка может привести к неприятных ошибкам, таким как взаимные блокировки, состояние гонки и т.д. Все это довольно низкий уровень к управлению параллелизма.

Fantom поддерживает параллелизм, используя следующие техники:
  1. Неизменяемые объекты (потоковая безопасность)

    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. Статические поля могут хранить только неизменяемые объекты, поэтому разные потоки не могут получить доступ к общим изменяемым данным.
  3. Модель сообщений (actors) для общения между потоками (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
    


Синтаксический сахар


  1. Значения по умолчанию для параметров

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

  2. Вывод типов — типы локальных переменных могут быть выводимыми
  3. Неявный доступ к полям

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

  4. Нулевые типы — разделение типов на те, которые не могут принимать null, как значение, и которые могут.

    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. Убраны проверяемые исключения, в C# Anders Hejlsberg также не включил их (и правильно сделал)
  6. Числовая точность — существует только 64 разрядная поддержка Int и Float


Ресурсы


  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


Заключение


Распространённой ошибкой является мнение, что Fantom — это очередная улучшенная версия Java. Но стоит только взглянуть на него, как на язык со своей философией и концепциями, как начинаешь понимать всю его прелесть. Помимо стабильности, к Fantom можно отнести ряд таких особенностей, как дружелюбное сообщество, полностью открытый исходный код, приятную IDE и ещё множество приятных мелочей.
Tags:
Hubs:
Total votes 45: ↑34 and ↓11+23
Comments48

Articles