19 февраля 2011

Clojure. Программирование GUI

Clojure. Программирование GUI.

Давайте начнем с размышлений. Лучше всегда сначала подумать, а то может и ничего делать не придется. GUI - это такое способ общения с пользователем, когда ему предоставляются рамочки, кнопочки, менюшки, окна ввода, а от него получаются нажатия клавиш и движения мыши. Достаточно долгое время я был приверженцем исключительно десктоп-приложений и отрицал всяческие интепретируемые и байт-компилируемые языки. Я был неправ. Я полгода делал программу для автоматизированного учета автошколы используя Qt, EcmaScript и Postgresql и сейчас я понимаю, что изобрел web-броузер заново.
Я пытался создать свои виджеты на qt/c++, а обобщить виджеты в формы с помощью ecmascript. Почему? Потому что, c++ не позволял мне сделать все с помощью него. Многие функции, которые я делал не требовали перекомпиляции, но мне приходилось ждать раз за разом. Я не компания Google и не могу себе позволить изобрести свою систему сборки ninja.
Я стал всерьез рассматривать PyGTK, но мой друг подтолкнул меня к Clojure. Clojure - активно развивающийся диалект лиспа, работающий на виртуальной машине java. При этом все написанные java вкусняшки уже в коробке.

Как написать GUI на clojure?

Первый способ - десктоп-приложение, использующее awt, swing или swt. Второй, как вы уже догадались web-приложение, с генерацией некоторого html контента. html контент - статическая сущность. Для того чтобы сделать его более подвижным придумали добавление javascript. А уже на этом javascript появилось несколько инструментов для быстрого создания обычных виджетов прямо как в Qt или GTK. Хм, как-то я все сложно описал. Ну что же, давайте заставим поволноваться Пола Грэма и создадим первый сайт на clojure.

Будем делать опять hello world.

Только этот hello world мы будем модифицировать на "лету", без перезагрузки сервера и упаси господи компиляции/линковки. Как? Сейчас узнаете.
Скачайте и установите leiningen.
Создайте новый проект:
cd ~/webspace
~/bin/lein new pgbase
cd pgbase
Программа lein имеет несколько функций, и их список может дополняться плагинами.
Мы воспользовались функцией new, которая создала нам проект с именем pgbase.
Настройки проекта храняться в файле project.clj. Этот файл представляет собой clojure программу. Вам не кажеться удобным, когда сборка проекта пишеться на том же языке, что и сам проект?
В качестве IDE мы будем использовать emacs.
Добавте в проект следующие зависимости:
(defproject pgbase "1.0.0-SNAPSHOT"
  :description "First web project"
  :dependencies [ [org.clojure/clojure "1.2.0"]
                  [org.clojure/clojure-contrib "1.2.0"]
                  [compojure "0.6.0-RC3"]
                  [ring/ring-core "0.3.5"]
                  [ring/ring-jetty-adapter "0.3.5"]]
  :dev-dependencies [ [swank-clojure "1.3.0-SNAPSHOT"] ])
Выполните lein deps. Менеджер проекта скачает в кеш необходимые пакеты, а затем скопирует их в каталог lib. Есть два вида зависмостей: для работы и для разработки, dependencies и dev-dependencies. Все просто, не так ли? Оказывается нам не надо даже броузер открывать. А тем более повторять как мантру:
./configure --help
./configure --same-options --very-very-many
--do-you-customize-qt-build-process?
make && sudo make install
Пакет swank-clojure предоставит нам команду lein swank, которая будет запускать swank сервер, к которому мы будем подключаться из emacs с помощью slime. Звучит сложно, но если Вы настроили emacs как я уже рассказывал, Вы удивитесь ущербности eclipse.
Откройте src/pgbase/core.clj. Сделайте его таким:
(ns pgbase.core
  (:use clojure.contrib.json)
  (:use compojure.core)
  (:use ring.adapter.jetty)
  (:require [compojure.route :as route]))

(defroutes main-routes
  (GET "/" [] "<h1>Hello world!!</h1>")
  (route/not-found "<h1>Page not found</h1>"))

(defn run-server
  "Run jetty web server with options on port 8080"
  [options]
  (let [options (merge {:port 8080 :join? true } options)]
    (run-jetty (var main-routes) options)))

(defonce server-thread (run-server {:join? false}))
Теперь, если вы послушались моего совета о emacs, прямо в буфере core.clj выполните M+x elein-swank. Elein это простой модуль, который выполнит lein swank, а затем подключит slime к данному swank серверу.
Получилось? Вот уже должна быть строка с приглашением.
; SLIME 2011-02-13
user>
Выполните в repl загрузку вашего кода, а затем запустите поток server-thread, например, так:
user> (use 'pgbase.core :reload)
nil
user> (.start server-thread)
nil
user>
Кстати если Вы в процессе набора будете нажимать M+tab, то автодополнение будет экономить Вам время.
Давайте смотреть результаты:
C+! opera http://localhost:8080 &
Если Вы все сделали правильно, то должны увидеть страницу с приветсвием.
Теперь перейдите в буфер emacs core.clj и поменяйте строку Hello World на что-то менее тривиальное.
Нажмите C+c C+k. Перезагрузите страницу в opera. Ну как Вам? Давайте считать строчки web приложения.
(ns pgbase.core
  (:use clojure.contrib.json)
  (:use compojure.core)
  (:use ring.adapter.jetty)
  (:require [compojure.route :as route]))
Здесь мы обозначаем пространство имен и подключаем необходимые библиотеки.
- use - вливает то, что мы просим сразу к нам в пространство.
- require - загружает пространство имен, и доступ к его функциям мы получаем только через его имя. Мы попросили clojure слегка упростить это имя для нас.
(defroutes main-routes
  (GET "/" [] "<h1>Hello world!!</h1>")
  (route/not-found "<h1>Page not found</h1>"))
Этим макросом мы реализуем механизм обработки запросов пользователя. На GET запрос по адресу "/" мы возвращаем строку с приветствием. Иначе мы возвращаем сообщение об ошибке.
(defn run-server
  "Run jetty web server with options on port 8080"
  [options]
  (let [options (merge {:port 8080 :join? true } options)]
    (run-jetty (var main-routes) options)))
Создаем функцию, которая запускает jetty web server с необходимыми параметрами. Функция var реализует механизм "мутирующей" структуры. Это значит, что если изменится main-routes, то измениться и поведение программы в соответствие нового кода.
(defonce server-thread (run-server {:join? false}))
Заводим переменную связанную с формой запуска сервера. Суффикс once намекает нам, что второй раз этого сделать не получиться. Так как функции clojure реализуют три java интерфейса: Callable, Runnable и Comparator, то запуск сервера будет выглядеть так:
widget->show();
file.write(smth);
А теперь так:
(show widget)
(write :in file "Hello")
(set widget :text (read :from file))
Если ООП-ники хотят литературно программировать, то уж пускай добавят к методам своих объектов возвратный постфикс youself.
widget->showYouself();
// Я затрудняюсь преобразовать OOP file api.

Список использованных источников

Статья основана на блогах, сообщениях stackoverflow, списках рассылки google, emacs wiki.

1 комментарий:

  1. "Заставим поволноваться Пола Грэма" -- Миха, ты жжошь! :-D
    Интересный пост, кстати.

    ОтветитьУдалить