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 поддерживает параллелизм, используя следующие техники:
- Неизменяемые объекты (потоковая безопасность)
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
- Статические поля могут хранить только неизменяемые объекты, поэтому разные потоки не могут получить доступ к общим изменяемым данным.
- Модель сообщений (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
Синтаксический сахар
- Значения по умолчанию для параметров
class Person { Int yearsToRetirement(Int retire := 65) { return retire - age } Int age }
- Вывод типов — типы локальных переменных могут быть выводимыми
- Неявный доступ к полям
class Thing { Int id := 0 { get { echo("get id"); return &id } set { echo("set id"); &id = it } } }
- Нулевые типы — разделение типов на те, которые не могут принимать 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?
- Убраны проверяемые исключения, в C# Anders Hejlsberg также не включил их (и правильно сделал)
- Числовая точность — существует только 64 разрядная поддержка Int и Float
Ресурсы
- Fantom eclipse-based IDE F4
- Web applications framework Tales
//Hello world written in tales using tales class HelloWorld : Page{ @Route{uri="/hello-world"} Void main(){ response.writeStr("Hello World") } }
- Logic-free template engine Mustache
using mustache Mustache template := Mustache("Hello, {{ name }}!".in) template.render(["name":"world"])
- Fantom Pod repository
- fantom.org
Заключение
Распространённой ошибкой является мнение, что Fantom — это очередная улучшенная версия Java. Но стоит только взглянуть на него, как на язык со своей философией и концепциями, как начинаешь понимать всю его прелесть. Помимо стабильности, к Fantom можно отнести ряд таких особенностей, как дружелюбное сообщество, полностью открытый исходный код, приятную IDE и ещё множество приятных мелочей.
ссылка на оригинал статьи http://habrahabr.ru/post/175979/
Добавить комментарий