- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF134:Vala
Материал из Linuxformat.
Содержание |
Vala и Genie Амбициозные новички
- Уважаете C# как язык программирования, но не доверяете технологиям Microsoft? У Семёна Есилевского есть для вас кое-что.
При написании графических приложений от языка программирования требуется наличие многих понятий и конструкций, которые редко используются в консольных программах. К ним относятся, например, сигналы, лямбда-функции и возможность узнавать тип объекта во время выполнения программы. В той или иной степени эти возможности реализованы только в скриптовых языках, таких как Python или Ruby, а также в языках, использующих виртуальные машины — C# и Java. Однако использование скриптовых языков оправдано далеко не всегда, из-за их невысокой производительности. Язык Java явно не прижился в настольных приложениях для Linux, а «тяжелое наследие» Microsoft определяет предвзятое отношение к языку С# у разработчиков свободного ПО.
Если же обратится к компилируемым языкам, то в мире Linux безраздельно царят C и C++. Именно на них реализованы все популярные GUI-инструментарии, несмотря на то, что эти языки плохо приспособлены для программирования графических приложений. В результате разработчики библиотек вынуждены тем или иным способом обходить ограничения этих языков.
Создатели Qt пошли по пути модификации языка С++, дополнив его системой сигналов и слотов и метаобъектной информацией. Строго говоря, программы, использующие Qt, написаны не на С++, а на некоем особом языке, который сначала транслируется в С++ с помощью метаобъектного компилятора (MOC).
Библиотека GTK+ написана на стандартном С, который еще менее удобен для GUI-приложений, чем С++. Чтобы внести в GTK+ объектно-ориентированные абстракции и информацию о типах объектов, была создана библиотека GObject (http://library.gnome.org/devel/gobject/stable/). Использовать GObject в С напрямую сложно и неудобно, однако эта библиотека позволяет легко создавать стандартизированные объектно-ориентированные привязки [bindings] для других языков программирования. Желание использовать готовую объектную систему GObject и удобный высокоуровневый синтаксис привели к появлению сразу двух новых языков – Vala и Genie, о которых и пойдет речь в этой статье.
Идеология Vala и Genie
Создатели Vala и Genie довели идею, заложенную в Qt, до логического завершения. Раз уж нужно дополнять базовый язык новыми конструкциями, то лучше просто создать новый язык более высокого уровня с максимально удобным синтаксисом. Программы, написанные на Vala и Genie, сначала транслируются в стандартный C, а затем компилируются. Объектная модель языков базируется на GObject, скрытой в недрах компилятора.
Vala и Genie часто позиционируются как языки, облегчающие написание программ для Gnome. Может создаться впечатление, что они – сугубо специализированные. Это совсем не так! Vala и Genie могут использоваться в любой системе, куда портирована библиотека Glib (т. е. практически везде), а графический интерфейс на них можно создавать везде, где есть библиотеки GTK+. Компилятор Vala прекрасно работает в Linux, Mac OS Х и Windows, что делает этот язык удобным средством для разработки кросс-платформенных GUI-приложений с помощью GTK+. У компилятора имеется режим POSIX-совместимости, в котором устраняется даже зависимость от Glib, но ценой потери некоторых возможностей языка.
Функционально оба языка практически идентичны и отличаются только синтаксисом. Vala очень похож на C#, тогда как Genie является своеобразной амальгамой Python, D и Delphi. Компилятор, утилиты и библиотеки являются общими для обоих языков. Компилятор различает тип синтаксиса по расширению файлов – .vala и .gs, соответственно.
Программы на Vala превращаются в код на C и поэтому непосредственно компонуются с внешними библиотеками без дополнительных «оберток». Для использования любой библиотеки в Vala достаточно создать интерфейсный файл *.vapi с инструкциями для компилятора. Библиотеки, написанные на Vala и Genie, имеют автоматически сгенерированные заголовочные файлы и также могут сразу же использоваться в С/С++.
В отличие от C# и Java, Vala – настоящий компилируемый язык (хотя и с предварительной стадией трансляции кода). Производительность и расходы памяти у программ на Vala мало отличаются от изначально написанных на C.
Vala предоставляет все возможности современных объектно-ориентированных языков и многие удобные конструкции: классы и интерфейсы, свойства, сигналы, цикл foreach, лямбда-функции, неявную типизацию [type inference], обобщенные классы [generics], автоматическое или «ручное» управление памятью, исключения, контрактное программирование и регулярные выражения как часть языка.
Библиотеки, IDE, проекты
Язык Vala очень молод, но несмотря на это, он уже «оброс» впечатляющим инструментарием и даже обзавелся собственной IDE. В стандартной инсталляции Vala имеются привязки к десяткам библиотек (http://live.gnome.org/Vala/BindingsStatus). Среди них – все библиотеки GTK+, основные библиотеки платформы Gnome, а также alsa, gsl, webkit, zlib, sqlite, OpenGL и многие другие. В Vala имеется собственная библиотека обобщенных контейнеров Gee (http://live.gnome.org/Libgee), напоминающая STL в C++ (подробнее о ней ниже).
Поддержка Vala имеется в нескольких популярных IDE (MonoDevelop, Anjuta, Eclipse), но самой интересной является IDE Val(a)ide, написанный целиком на Vala.
Val(a)ide очень проста, но содержит практически все необходимое для комфортной работы с Vala. Имеется поддержка проектов, подсветка синтаксиса, встроенная система сборки наподобие Make. Очень удобно реализовано подключение к проекту нужных библиотек. Единственным существенным недостатком Val(a)ide является отсутствие интегрированного отладчика, хотя компилятор Vala поддерживает отладку через стандартный Gdb. Поскольку Val(a)ide не достигла даже версии 1.0, отладчик в ней должен еще появиться.
Примеры программ на Vala (http://live.gnome.org/Vala#Sample_Code), использующие привязки к библиотекам Cairo и Clutter.
Несмотря на молодость языка, на Vala написаны десятки программ (http://live.gnome.org/Vala/#Projects_Developed_in_Vala). Vala проник и в область «дистрибутивостроения» – на нем базируется инсталлятор Paldo Linus, а на Genie – многие утилиты для Puppy Linux.
В Ubuntu Vala устанавливается из ppa-репозитория. Достаточно ввести в консоли следующие команды:
sudo add-apt-repository ppa:vala-team sudo apt-get update sudo apt-get install valide-dev libgee-dev
Это установит библиотеку контейнеров Gee, IDE Val(a)ide, а также сам компилятор и все нужные файлы в качестве зависимостей.
В других системах Vala устанавливается из репозиториев или исходных кодов, как описано на официальном сайте (http://live.gnome.org/Vala#Download). Процесс сборки компилятора прост, так как единственной зависимостью является GLib версии 2.14 или выше.
Первая программа
Традиционно мы напишем простейшую программу «Hello world!», но не в совсем скучном консольном варианте, а с использованием GUI на GTK+ и в двух вариантах – для Vala и Genie. Официальнаяn документация по Genie очень скудна, поэтому дополнительно использовались сторонние ресурсы (http://bkhome.org/genie/index.html). В Genie для структурирования кода используется отступы (как в Python). Они создаются табуляциями, или пробелами, если в начале файла имеется директива [indent=число пробелов]. Начнем с варианта на Vala:
hello.vala: using Gtk; int main (string[] args) { Gtk.init (ref args); var window = new Window (WindowType.TOPLEVEL); window.title = “First GTK+ Program”; window.set_default_size (300, 50); window.position = WindowPosition.CENTER; window.destroy.connect (Gtk.main_quit); var button = new Button.with_label (“Click me!”); button.clicked.connect ((source) => { source.label = “Thank you”; }); window.add (button); window.show_all (); Gtk.main (); return 0; }
Скомпилировать программу можно командой
valac --pkg gtk+-2.0 hello.vala
Выполнение любой программы в Vala начинается с функции main(), принимающей массив строк, содержащий параметры командной строки. Сначала, с помощью Gtk.init(ref args), инициализируется GTK+. Директива ref указывает, что параметр передается по ссылке, а не по значению. Далее создается объект Window с использованием стандартных констант GTK+:
var window = new Window (WindowType.TOPLEVEL);
Возникает впечатление, что переменная window создается без указания типа. На самом деле здесь срабатывает механизм неявной типизации для локальной переменной [type inference] по правой части оператора присваивания. Можно было бы объявить тип переменной явно:
// Явная типизация в Vala Window window = new Window (WindowType.TOPLEVEL);
но для сложных типов, особенно обобщенных, неявная типизация становится просто спасительной. Сравните, например, эти два объявления в Vala:
MyFoo<string, MyBar<string, int>> foo = new MyFoo<string, MyBar<string, int>>(); var foo = new MyFoo<string, MyBar<string, int>>();
Первое читается с трудом, тогда как второе (с неявной типизацией) гораздо понятнее.
Далее задаются свойства окна и назначается функция-обработчик для сигнала окна destroy – в данном случае это Gtk.main_quit:
window.destroy.connect (Gtk.main_quit);
Эта лаконичная запись намного проще и понятнее жутковатого кода на чистом C в GTK+ и даже макроса connect() в Qt.
Далее создается кнопка и назначается обработчик ее сигнала clicked. Вместо применения отдельной функции-обработчика здесь «на лету» создается безымянная лямбда-функция. Конструкция
(source) => { source.label = “Thank you”; }
возвращает объект, являющийся функцией одной переменной (source), а именно такую сигнатуру ожидает сигнал clicked. Тип переменной указывать не нужно: он определяется компилятором автoматически и является ссылкой на объект, вызвавший сигнал, то есть нашей кнопкой.
Наконец, мы добавляем кнопку в окно, показываем его содержимое функцией show_all() и входим в цикл обработки событий GTK+ с помощью Gtk.main(). Выход из этого цикла осуществляется только после закрытия окна программы.
Та же программа на языке Genie выглядит еще более лаконично:
hello.gs: uses Gtk init Gtk.init (ref args) var test = new TestWindow () test.show_all () Gtk.main (); class TestWindow : Window init title = “Test Window” set_default_size (300, 50) window_position = WindowPosition.CENTER destroy += Gtk.main_quit var button = new Button.with_label (“Click Me”) button.clicked += def (btn) btn.label = “Thank you!” add (button)
Компилируется она аналогичным образом
valac --pkg gtk+-2.0 hello.gs
Выполнение программы в Genie начинается с блока init, находящегося вне определений классов и функций. В отличие от предыдущего примера, используется немного другой подход – мы создаем класс TestWindow, унаследованный от стандартного Window. В конструкторе класса (блок init) выполняются те же действия, что и в предыдущем примере. Синтаксис присоединения функций-обработчиков к событиям в Genie еще более прост, чем в Vala, и использует оператор +=:
destroy += Gtk.main_quit
Объявление лямбда-функции также на вид отличается, но по сути – все в точности как в Vala:
button.clicked += def (btn) btn.label = “Thank you!”
Явное объявление переменных в Genie выглядит по-своему, так как используется синтаксис Pascal/Delphi с указанием типа после переменной:
int_val: int = 10 double_val: double = 12.5 button: Button = new Button.with_label (“Click me!”)
В основном блоке init мы создаем экземпляр нового класса, показываем элементы окна функцией show_all() и входим в цикл обработки событий.
Еще один нетривиальный момент в обоих примерах – это отсутствие операторов delete после динамического создания объектов оператором new. Vala и Genie – языки с автоматическим управлением памятью, однако, в отличие от Java, предусмотрено «ручное» управление с помощью указателей, если необходимо.
Особенности языков
Полностью описать синтаксис двух языков в короткой статье невозможно, но к счастью, это и не нужно. Все конструкции языка Vala будут понятны любому программисту, знакомому с С++, С# или Java, а синтаксис Genie отличается от стандартного Python только в деталях. В то же время некоторые особенности этих языков заслуживают упоминания.
Строки
Все строки в Vala и Genie имеют формат UTF-8. Тип string задает строку произвольной длины, которая является неизменяемым [immutable] объектом. Строку можно объявить как стандартным образом, так и в стиле «строки в тройных кавычках», знакомом по языку Python:
string text = “Обычная строка”; string str = “””Строка в тройных кавычках (“verbatim string”).
В таких строках не обрабатываются специальные символы \n, \t, \\ и т. д. Они могут содержать кавычки и занимать несколько строк.”””;
Строки, предваряемые символом @, являются «строковыми шаблонами». В них можно вставлять переменные и выражения с помощью префикса “$”, как это делается во многих скриптовых языках. Значения встроенных в строку переменных и выражений автоматически преобразуются к строковому типу. Удобство такого способа форматирования строк трудно переоценить:
int a = 10, b = 20; string str = @”$a * $b = $(a * b)”; // Результирующая строка: “10*20 = 200”
Получить подстроку или отдельный символ можно с помощью «срезов». Отрицательные значения индексов отсчитываются от конца строки:
string greeting = “hello, world”; string s1 = greeting[7:12]; // => “world” string s2 = greeting[-4:-2]; // => “or” unichar c = greeting[7]; // => ‘w’
Поддерживаются также многие стандартные строковые функции библиотеки GLib.
Массивы
Массивы в Vala могут быть динамическими (созданными в куче) и статическими (созданными в стеке).
int[] a = new int[10]; // Динамический массив int f[10]; // Статический массив
Для динамических (но не для статических) массивов поддерживается добавление элементов «на лету»:
int[] e = {} e += 12; e += 5; e += 37;
Нужно иметь в виду, что такая возможность рассматривается исключительно как еще один способ инициализации – стандартные массивы не предназначены для использования в качестве контейнеров с переменным количеством элементов. Для этих целей лучше использовать классы-коллекции из библиотеки Gee (массивы, списки, хэши и т. п.).
Поддерживаются также многомерные массивы
int[,] c = new int[3,4]; int[,] d = {{2, 4, 6, 8}, {3, 5, 7, 9}, {1, 3, 5, 7}}; d[2,3] = 42;
Управляющие конструкции
Синтаксис управляющих конструкций if, for, while, switch в Vala полностью аналогичен синтаксису C/C++, а в Genie очень похож на синтаксис Python. Отдельного внимания заслуживает только конструкция foreach, позволяющая легко перебирать элементы массивов и коллекций:
foreach (int a in int_array) { stdout.printf(“%d\n”, a); }
В Genie в качестве foreach выступает разновидность цикла for:
for s in args do print s
Тип переменной s компилятор определяет автоматически.
Делегаты
В Vala и Genie нет указателей на функции. Если нужно передать одну функцию в виде параметра другой, используются делегаты:
delegate void DelegateType(int a); void f1(int a) { stdout.printf(“%d\n”, a); } void f2(DelegateType d, int a) { d(a); // Вызов делегата с переданным параметром } void main() { f2(f1, 5); // Функция f1 передается в параметре-делегате }
Код с делегатами читается куда лучше, чем с указателями в стиле С/С++.
Обобщенные классы
Vala и Genie поддерживают обобщенные классы, то есть классы, способные работать с разными типами данных. Конкретный тип указывается при создании экземпляра класса. Обобщенные классы [Generics] не являются полным аналогом шаблонов [Templates] в С++; единственное их назначение – избежать написания одного и того же кода для нескольких типов данных, которые обрабатываются одинаковым образом. Обобщенные классы не предназначены для шаблонного метапрограммирования, как в С++. Синтаксис обобщенных классов в Vala похож на шаблоны в С++, но более прост. Имя обобщенного типа задается в угловых скобках:
public class Adder<G> : GLib.Object { private G data; public void do(G a, G b) { this.data = a+b; } public G get_sum() { return this.data; } } var v = new Adder<int>();
В Genie имя обобщенного типа задается конструкцией «оf имя_типа»:
class Adder of G : Object _data : G def do(a: G, b: G) _data = a+b; def get_sum(): G return _data var v = new Adder of int
Переменные, начинающиеся со знака подчеркивания, автоматически получают атрибут private.
Контрактное программирование
Vala поддерживает базовые конструкции контрактного программирования requires и ensures:
double method_name(int x, double d) requires (x > 0 && x < 10) requires (d >= 0.0 && d <= 1.0) ensures (result >= 0.0 && result <= 10.0) { return d * x; }
Специальная «магическая» переменная result имеет тип, возвращаемый функцией.
Библиотека обобщенных контейнеров Gee
Библиотека Gee в Vala выполняет ту же функцию, что и STL в C++. В Gee имеются структуры данных и итераторы, но, в отличие от STL, нет алгоритмов. Синтаксис шаблонных классов Gee очень похож на синтаксис C++, так что программисты, привыкшие к шаблонным контейнерам в C++, будут чувствовать себя как дома. Gee содержит несколько классов-коллекций, самыми часто используемыми из которых являются вектор ArrayList<G>, список LinkedList<G> и ассоциативный контейнер HashMap<K,V>. Контейнеры Gee намного сильнее интегрированы в базовый язык, чем контейнеры STL. Например, любой контейнер можно использовать в циклах foreach:
var list = new ArrayList<int> (); list.add (1); // Добавление элементов list.add (2); list.add (4); list.insert (2, 3); // Вставка элемента list.remove_at (3); // Удаление элемента foreach (int i in list) { // Интеграция в цикл foreach stdout.printf (“%d\n”, i); } list[2] = 10; // Доступ по индексу
Проверить наличие элемента в любом контейнере можно оператором in:
if (2 in list) { // Аналогично list.contains(2) stdout.printf (“Двойка в списке есть!\n”); }
В итоге
Появление любого нового языка программирования обычно вызывает опасения, что он окажется очередным нежизнеспособным курьезом. Vala и Genie благополучно избежали этой участи. Эти языки базируются на стабильной и очень популярной библиотеке GLib, но позволяют программисту работать с современным компилируемым языком высокого уровня, при полной совместимости с C. Vala и Genie отлично приспособлены для написания GUI-приложений с использованием GTK+ и кардинально облегчают работу с этим графическим инструментарием. Можно сказать, что Vala– это «C# без виртуальной машины», а Genie – «компилируемый Python». Производительность этих языков близка к чистому C, а удобство синтаксиса и богатство конструкций – к скриптовым языкам. Vala и Genie будут достойным дополнением арсенала любого программиста, особенно работающего с GTK+.