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.

Emacs. Дар провидения

Для того, чтобы превратить emacs в небольшую clojure ide, скачайте следующие расширения в папку ~/.emacs.d/site-lisp
auto-complete
ac-slime
clojure-mode
slime
elein
highlight-symbol
linum [linum+]
paredit
swank-clojure
sr-speedbar
Многие из них доступны на github.org, поэтому лучшим способом будет git clone —depth 1 project-url.

auto-complete

ac-slime

Как Вы уже догадались расширения предоставляют функцию автодополнения для различных языков программирования. У разработчика ac-slime я позаимствовал метод настройки emacs. Настройки разместите в файле ~/.emacs.d/init-auto-complete.el
;; Autocomplete
(require 'auto-complete-config nil t) ;; don't break if not installed
(add-to-list 'ac-dictionary-directories "~/.emacs.d/ac-dict")
(setq ac-comphist-file  "~/.emacs.d/ac-comphist.dat")
(ac-config-default)
(provide 'init-auto-complete)

clojure-mode

Данный режим осуществляет подсветку синтаксиса и автоматические отсупы для кложурного кода. Настройки в ~/.emacs.d/init-clojure-mode.el
;; Clojure mode
(require 'clojure-mode)

(defun slime-clojure-repl-setup ()
  (when (string-equal "clojure" (slime-connection-name))
    (message "Setting up repl for clojure")
    (set-syntax-table clojure-mode-syntax-table)
    (clojure-mode-font-lock-setup)
    (setq lisp-indent-function 'clojure-indent-function)
    (when (and (featurep 'paredit) paredit-mode (>= paredit-version 21))
      (define-key slime-repl-mode-map "{" 'paredit-open-curly)
      (define-key slime-repl-mode-map "}" 'paredit-close-curly))))

(add-hook 'slime-repl-mode-hook 'slime-clojure-repl-setup)

(eval-after-load 'clojure-mode
  '(font-lock-add-keywords
    'clojure-mode `(("(\\(fn\\>\\)"
                     (0 (progn (compose-region (match-beginning 1)
                                               (match-end 1) "ƒ")
                               nil))))))

(provide 'init-clojure)

slime

Пакет предоставляет возможность подключиться к swank repl серверу и взаимодействовать с ним. Например загружать содержимое буфера emacs, отображать документацию по объектам, ну и собственно экспериментировать без компилирования/линкования. Настройка в файле ~/.emacs.d/init-slime.el
(load "slime-autoloads")
(require 'slime)
(slime-setup '(slime-repl))
;; ac-slime
(require 'ac-slime)
(add-hook 'slime-mode-hook 'set-up-slime-ac)
(eval-after-load "slime"
  `(progn
     (slime-setup '(slime-repl))
     (custom-set-variables
       '(inhibit-splash-screen t)
       '(slime-complete-symbol*-fancy t)
       '(slime-net-coding-system 'utf-8-unix)
       '(slime-startup-animation nil)
       '(slime-protocol-version 'ignore))))
(provide 'init-slime)

elein

Небольшой вспомогательный модуль для запуска lein swank сервера, либо "глобального" `~/.lein/bin/swank-clojure`, либо локального `lein swank` Оформим его загрузку отдельно, мало ли что. Мне кажеться, что lein будет только прибавлять в мощности в будущем. ~/.emacs.d/init-elein.el
(require 'elein)
(provide 'init-elein)

highlight-symbol

Минорный режим для подсветки одинаковых слов. Возможно более продвинут. Мы добавили к нему несколько популярных "горячих" клавиш. ~/.emacs.d/init-highlight-symbol.el
(require 'highlight-symbol)
(global-set-key [(control f3)] 'highlight-symbol-at-point)
(global-set-key [f3] 'highlight-symbol-next)
(global-set-key [(shift f3)] 'highlight-symbol-prev)
(global-set-key [(meta f3)] 'highlight-symbol-prev)
(global-set-key [(control meta f3)] 'highlight-symbol-query-replace)
(provide 'init-highlight-symbol)

linum

Пакет для нумерации строк открытого окна emacs. Для версии emacs >= 23.2 уже встроен. Нам остается только подключить linum+.el ~/.emacs.d/init-linum.el
;; Line numbers
(custom-set-variables
 '(linum-format "%d "))

(require 'linum+)
(global-linum-mode 1)

(provide 'init-linum)

paredit

Минорный режим для более удобной работы с s-выражениями. ~/.emacs.d/init-paredit.el
(require 'paredit)
(provide 'init-paredit)

swank-clojure

Не подключайте данный пакет. Для того, чтобы воспользоваться сервером swank, Вам достаточно в файле project.clj указать "девелоперскую" зависимость и выполнить lein deps.
:dev-dependencies [ [ swank-clojure "1.3.0-SNAPSHOT" ] ]

lein compile
lein install

sr-speedbar

~/.emacs.d/init-speedbar.el
(custom-set-variables
 '(speedbar-mode-specific-contents-flag t)
 '(speedbar-show-unknown-files t)
 '(speedbar-use-images nil))

(require 'sr-speedbar)

(provide 'init-speedbar)

Измененный ~/.emacs.d/init.el

;;----------------------------------------------------------------------------
;; Set load path
;;----------------------------------------------------------------------------
(if (fboundp 'normal-top-level-add-subdirs-to-load-path)
    (let* ((my-lisp-dir "~/.emacs.d/site-lisp/")
           (default-directory my-lisp-dir))
      (progn
        (setq load-path (cons my-lisp-dir load-path))
        (normal-top-level-add-subdirs-to-load-path))))
(setq load-path (cons (expand-file-name "~/.emacs.d") load-path))

;;----------------------------------------------------------------------------
;; Load configs for specific features and modes
;;----------------------------------------------------------------------------
(require 'init-linum)
(require 'init-highlight-symbol)
(require 'init-slime)
(require 'init-clojure)
(require 'init-speedbar)
(require 'init-paredit)
(require 'init-auto-complete)
(require 'init-elein)
;;----------------------------------------------------------------------------
;; Allow access from emacsclient
;;----------------------------------------------------------------------------
(server-start)

;;----------------------------------------------------------------------------
;; Variables configured via the interactive 'customize' interface
;;----------------------------------------------------------------------------
(setq custom-file "~/.emacs.d/custom.el")
(load custom-file)

(require 'init-locale)

(provide 'init)
После просмотра некоторого количества статей/заметок/wiki касаемо emacs, я даже и не знаю как охарактеризовать эту программу одним словом. Вы можете всё. Причем не Вы создаете программы, программа создает Вас. Буквально сейчас я подумал: "Eclipse лучше, он обладает современным графическим интерфейсом, системой рефакторинга,". И Emacs ответил мне:
Я прост, чтобы не отвлекать тебя.
Я прост, чтобы ты учился разрабатывать простые программы.
Я мощен, и ты должен чувствовать гармонию мощности и простоты.
Я расширяем, и это может продолжаться бесконечно.
Хм, наверно не зря в него включили психотерапевта. Я даже и знаю получиться ли у меня когда-нибудь в жизни сделать что-либо подобное на эту программу.

17 февраля 2011

Lisp. Операционная система Emacs

Начнем с настройки emacs. Сразу скажу, что эта настройка никогда больше не закончится. Я дам Вам всего лишь несколько советов, которые увидел у гуру.

1. Не редактируйте .emacs.

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

2. Создайте папки ~/.emacs.d и ~/.emacs.d/site-lisp

В первой папке будут находиться конфигурационные скрипты для расширений, во-второй сами расширения.

3. Создайте файлы ~/.emacs.d/init.el и ~/.emacs.d/custom.el

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

4. Располагайте emacs плагины в поддиректориях ~/.emacs.d/site-lisp.

Например ~/.emacs.d/site-lisp/muse-mode или ~/.emacs.d/site-lisp/git-blame

Для каждого расширения создайте ~/.emacs.d/init-[name].el

Теперь давайте заложим скелет Вашей будущей операционной системы Emacs.

~/.emacs

(add-to-list 'load-path "~/.emacs.d/")
(require 'init)

~/.emacs.d/init.el

;;----------------------------------------------------------------------------
;; Set load path
;;----------------------------------------------------------------------------
(let ((default-directory "~/.emacs.d/site-lisp/"))
  (normal-top-level-add-to-load-path '("."))
  (normal-top-level-add-subdirs-to-load-path))

;;----------------------------------------------------------------------------
;; Load configs for specific features and modes
;;----------------------------------------------------------------------------
;; TODO: load configuration scripts, for example (require 'init-javascript)

;;----------------------------------------------------------------------------
;; Allow access from emacsclient
;;----------------------------------------------------------------------------
(server-start)

;;----------------------------------------------------------------------------
;; Variables configured via the interactive 'customize' interface
;;----------------------------------------------------------------------------
(setq custom-file "~/.emacs.d/custom.el")
(load custom-file)

(require 'init-locale)

(provide 'init)
Как видите все просто. Первая секция перечисляет все папки в ~/.emacs.d/site-lisp. Далее будут загружаться наши конфигурации. Затем запускаем emacs-server, будем потом все файлы открывать в одном экземпляре программы. Загружаем файл custom.el с общими настройками. Загружаем файл с настройками локали.

custom.el

(custom-set-variables
  ;; custom-set-variables was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
 '(column-number-mode t)
 '(cua-mode t nil (cua-base))
 '(display-time-mode t)
 '(indent-tabs-mode nil)
 '(inhibit-startup-screen t)
 '(interprogram-paste-function (quote x-cut-buffer-or-selection-value) t)
 '(keyboard-coding-system (quote utf-8-unix))
 '(linum-format "%d ")
 '(lisp-indent-offset 2)
 '(mouse-drag-copy-region nil)
 '(muse-mode-auto-p t)
 '(qooxdoo-compile-on-save nil)
 '(scroll-preserve-screen-position nil)
 '(select-active-regions t)
 '(show-paren-mode t)
 '(standard-indent 2)
 '(tab-width 2)
 '(x-select-enable-clipboard t)
 '(x-select-enable-primary nil)
 '(x-select-request-type (quote (UTF8_STRING COMPOUND_TEXT TEXT STRING)) t))

(custom-set-faces
  ;; custom-set-faces was added by Custom.
  ;; If you edit it by hand, you could mess it up, so be careful.
  ;; Your init file should contain only one such instance.
  ;; If there is more than one, they won't work right.
)

init-locale.el

(custom-set-variables
 '(current-language-environment "UTF-8")
 '(default-input-method "russian-computer")
 '(keyboard-coding-system 'utf-8-unix)
)


(set-terminal-coding-system 'utf-8)
(prefer-coding-system 'mule-utf-8)

(provide 'init-locale)

14 февраля 2011

Lisp. Как же я отупел с этим GUI.

Да-а-а-а... И жалеть себя ни в коем случае не стоит. Хотите знать как смотрят на экономику современные экономисты, купите книжку "Экономикс". Хотите знать почему за хлебом в супермаркете приходится ломиться через весь магазин - Котлер "Маркетинг". Хотите знать почему начальник тупой, но Вы с ним работаете уже три года - Мескон, Хедоури и др. "Основы менеджмента".
А вот если хотите узнать, что Вы ни черта не смыслите в программировании, Харольд Абельсон и др. "SICP".
Если хотите сделать программу с длиннющим жизненным циклом, возмите emacs и сделайте его для себя удобным. А потом скажите мне, что такое удобство и стремится ли оно к бесконечности. А Вы вообще уверены, что Вы готовы к идеальному удобству? Вы вообще уверены, что хотите чтобы программа все делала за Вас? Вот эта знаменитая программа с единственной кнопкой? Вы уверены, что Вы сможете ее познать?
К чему это я? Ах, да. Упражнение 1.11 - полтора часа. Я уже не уверен, что мне хватит жизни, для SICP, лиспа... А ведь еще семью заводить, детей воспитывать. Чему? Я же сам ничего не знаю. Может и не надо что-то знать?

07 февраля 2011

Lieningen через proxy.

Долго же я не понимал почему lein не закачивает зависимости.
 Lein использует maven для этого, а proxy для maven настраиваются в файле ~/.m2/settings.xml примерно так:

<settings>
  .
  .
  <proxies>
   <proxy>
      <active>true</active>
      <protocol>http</protocol>
      <host>proxy.somewhere.com</host>
      <port>8080</port>
      <username>proxyuser</username>
      <password>somepassword</password>
      <nonProxyHosts>www.google.com|*.somewhere.com</nonProxyHosts>
    </proxy>
  </proxies>
  .
  .
</settings>

Майкл Сандберг. Начало работы с compojure, часть 2

Перевод. Оригинал
Это вторая часть моего введения в вебпрограммирование с clojure. В первой части мы сделали небольшой "список задач", который получился не очень-то функциональным. Но мы сделали это сами!

В этой части мы добавим JavaScript и Ajax!
Для этого мы должны иметь возможность хранить статические файлы. Это очень просто. Начнем с создания папки "files" в папке Вашего проекта (например, в той же папке, что и project.clj)
В созданной папке, создайте javascript.js и введите в него некоторый текст.

Теперь вернемся к core.clj.
Необходимо сообщить compojure/ring что мы храним некоторые файлы. Мы сделаем это, обернув наш вызов my-routes в функции jetty, чтобы улучшить читабельность, мы также переместим это в новую функцию.


Мы также добавим: ring.middleware.file-info и ring.middleware.file в (:use ) часть декларации пространства.

Теперь если Вы перейдете по адресу http://localhost:8080/javascript.js вы увидите содержимое Вашего файла.

Пришло время для некоторой ajax магии, для упрощения жизни, мы будем использовать jquery, и jquery плагин jquery.form.
Вы можете найти его по адресу: http://github.com/malsup/form/raw/master/jquery.form.js?v2.43
и jquery с jquery.org и поместите их в Ваш каталог "files".

Попробуйте загрузить их в броузере: http://localhost:8080/jquery.min.js это должно сработать.

Для включения их в Ваш clojure код, мы можем использовать некоторые вспомогательные функции из hiccup.


Загрузите форму в Вашей вебстранице и Вы должны увидеть: и так далее.

Теперь мы можем немного попрограммировать на JavaScript!
Откройте ваш javascript.js и введите следующее:


#formresult, это css id тега в котором будет результат POST запроса от формы.
Мы хотим, чтобы это было в div под формой. Div выглядит так:
[:div {:id "formresult"} “form result will be here”]

Итак, вся функция должна выглядеть следующим образом:


Теперь скомпилируйте функцию и протестируйте форму. Когда Вы нажмете enter, перезагрузки страницы не произойдет. "form result here" будет заменен на task3 ["task1" "task2"], если Вы ввели task1,task2 and task3 в вашей форме. Круто, да? Мы хотим, чтобы это выглядело как в /view. Проще простого!


Мы просто перенесли часть создания вектора "списка задач" в let конструкцию. Теперь вектор у нас под рукой и можем снова использовать функцию (unordered-list) для придания красоты нашему списку. Примечание: (html) также был добавлен.

Убедитесь, что все сделано правильно, вот мой код:
 core.clj

Это еще не полноценный "список задач", мы не можем удалять задачи и выполнять их. Это будет наш следующий шаг:

Мы начнем с создания нового маршрута:

(GET “/delete/:task” {session :session, params :params} (delete-task (params “task”) session))

Примечание, если мы не нуждаемся в параметре session мы можем использовать сокращенную версию:

(GET “/delete/:task” [task] (delete-task task session)

:task помещает что-либо написанное на этом месте в параметр. Если Вы перейдете по ссылке http://localhost:8080/delete/mytask это вызовет функцию delete-task с параметром mytask.

Определим функцию delete-task.
(defn delete-task [task] )
Теперь вопрос: как удалить "задачу" из вектора? Вектора поддерживают удаление только по индексу. Простейший способ сделать это сконверитровать вектор во множество. Просто измените vector? на set? и (vector на (hash-set. Все заработало!

Теперь мы можем написать нашу функцию:



Создайте несколько "задач". Затем введите http://localhost:8080/delete/sometask и sometask удалиться! Вы можете проверить это используя /view.

Но давайте быть честными, это не самый удобный способ удаления. Мы хотим иметь возможность нажать на кнопку, чтобы удалить "задачу".

Мы решим это также с помощью JQuery:


Что мы сделали? Мы создали функцию с именем done, она добавляет clickListener для всех элементов списка. clickListener вызывает /delete/list-item-text и при успехе скрывает элемент.

Достаточно для второй части.
Мой полный core.clj

Запущенную версию можно увидеть здесь:  http://cljtasks.appspot.com/
Спасибо за великолепней руководства: http://compojureongae.posterous.com/
за помощью в развертывании приложения на app engine!

В следующей части мы попробуем сохранить наш "список задач" в NoSQL базу данных.

06 февраля 2011

Майкла Сандберг. Начало работы с compojure.


Перевод статьи. Оригинал

Цель данной статьи заключается в написании простого "списка задач"с помощью clojure и compojure. Я буду использовать Lein, поэтому если у Вас его еще нет перейдите на сайт проекта и следуйте инструкциям.
Когда он установлен давайте начнем с:

lein new SimpleTasks

Эта команда создат нам новый проект.
Давайте начнем с добавления некоторых зависимостей, которые нам необходимы. Откройте SimpleTasks/project.clj и добавьте следующие строки:

Сохраните файл и вызовите "lein deps". Команда скачает все необходимые нам зависимости. Если Вы не хотите использовать lein, enclojure в netbeans, например, отлично работает. Прим. переводчика: советую использовать всегда lein, который с помощью плагинов расширяется до генерации файлов проектов для "гламурных" eclipse, netbeans Без lein Вам придеться идти на clojure.org и искать/скачивать/подключать все необходимые зависимости вручную.
:dev-dependencies [[swank-clojure “1.2.1”]]
подключает плагин для lein для запуска swank-clojure сервера и подключения slime из emacs. Не хотите, не надо и заполнять. Прим. переводчика: здесь также размещаются плагины для генерации проектов. Например, [lein-eclipse "1.0.0"].

Теперь давайте программировать!

Откройте src/SimpleTasks/core.clj в вашем любимом редакторе. Я использую emacs с clojure-mode и slime. Но подойдет что угодно.

Вы увидите:

(ns SimpleTasks.core)

Теперь давайте запустим первую вебстраницу!



Это базовая настройка простого вебприложения compojure.
Если вы это запустите и загрузите ваш броузер по ссылке localhost:8080 Вы увидите "Hello!"
Если Вы не знаете как это запустить, простой способ это сделать: запустить "lein repl", подождать, а затем:
clojure.core=> (require ’SimpleTasks.core)

А что собственно делает этот код?

(defonce server (run-jetty #’myroutes
{:join? false
:port 8080}))

Запускает экземпляр jetty на порту 8080
join? false, заставляет вернуться в repl, вместо блокирующего вызова jetty.
#’myroutes сообщает compojure, какая функция должна быть вызвана, когда сервер получает запрос.

(defroutes myroutes
(GET “/” [] (display)))

Здесь мы определяем наши маршруты, как вы уже заметили, которые используются сервером jetty. Все вызовы "/", например "localhost:8080/" перенаправляются к функции display

(defn display []
(html [:h1 “hello!”]))
Обращение к серверу заканчивается в этой функции. Все что она делает, это отображает html страницу с "hello!" в >h1< теге.

Теперь, для лучшего понимания clojure и compojure отредактируйте функцию display так, чтобы она отображала другое, например:
(defn display []
(html [:h1 “hello world!”]))

Для того, чтобы compojure использовал новую версию функции, все что Вам необходимо, это скомпилировать эту функцию, и обновить страницу в броузере.

Ну что же, давайте приступим к "списку задач". Первое, что нам необходимо это форма для добавления задач:

Мы начнем с добавления другой функции к маршрутам:


И вот она render-form функция:


Примечание: Вы должны добавить: hiccup.form-helpers для в список зависимостей в декларации имени пространства.

Теперь, скомпилируйте defroutes и функция render-form будет размещаться по адресу http://localhost:8080/form вашем броузере.
Если вы используете repl вы можете использовать (require ’SimpleTasks.core :reload) для перекомпиляции.
Ваш core.clj сейчас должен выглядеть так: http://gist.github.com/555496

Попробуйте напечатать что-нибудь в редактор и нажать на submit. Вы получите ошибку "Not found" в Вашем броузере.
Это потому, что все маршруты у нас "GET", а форма использует "POST"

Итак мы должны добавить еще один маршрут:

(POST “/form” {params :params} (handle-form (params “task”)))

Примечание: если Вы хотите получать все виды запросов, Вы должны использовать (ANY "/" (myfunction))
{params :params} предаставляет нам словарь параметров из формы. Сейчас нам нам необходим только параметр "task". Мы берем его и посылаем в handle-form.

Давайте проверим, что форма работает и просто выведем "задачу":

(defn handle-form [task]
(html [:h2 task]))

Загрузите http://localhost:8080/form и что-нибудь введите. Вы должны увидеть "задачу, которую ввели.

В нормальном "списке задач", Вы должны сохранять "задачи" в базе данных или текстовом файле. Для упрощения, а также расширения сознания мы будем использовать "сессии".

Мы начнем с сохранения результата из формы ввода:

(defn handle-form [task]
{:body (str “stored” task)
:session {:tasks task}})

Вместо использованной ранее функции (html) мы определяем body и session. После этого, "задача" будет сохранена под ключем :tasks в пользовательской сессии.

Дополнительно мы должны настроить ring/compojure для сохранения данных в сессии.

(wrap! myroutes :session)

Мы также хотим способ просмотра, что сохраненного в сессии, и это очень просто. Мы начнем с добавления нового маршрута:

(GET “/view” {session :session} (view session))

Простой GET маршрут - это машрут, как вы уже заметили, по которому мы берем сессию и отображаем ее.

Функция, которая вызывается:

(defn view [session]
(html [:h1 (str "tasks: " (:tasks session))]))

Мой полный исходный код:
http://gist.github.com/557879

Просто сохранение одной "задачи" долго не праработает. Время для расширения наших задач и сохранения их в вектор.
Это значит, что мы хотим добавить больше "задач" в сессию, а не перезаписывать старую задачу новой.
Мы будем использовать простой вектор в качестве контейнера. Для того, чтобы все получилось нам необходим не только параметр из формы, но и теущая сессия. Мы изменим на POST маршрут:

(POST “/form” {session :session, params :params} (handle-form (params “task”) session)))

Теперь у нас есть сессия и параметр "задача" из формы.
Мы изменим обработчик формы:

Данная функция, кроме печати задачи из формы и задач из сессии, также проверяет если текущее значение сессии это вектор, то задача добавляется в вектор текущих задач, иначе, когда нет задач, создается новый вектор.

Нам ничего не надо менять в нашем маршруте просмотра.
Попробуйте это! Теперь мы можем добавлять несколько "задач" в наш простой "менеджер задач"!

Время сделать так чтобы просмотр вылядел как реальный список задач.
Мы пойдем простым путем. Добавьте hiccup.page-helpers в Ваш проект.
Просто оберните наш вектор "задач" в (unordered-list).



Теперь у нас есть простой список задач. Вот как это у меня выглядит после добавления трех задач.

Tasks


task1
task2
task3

Ну, хорошего по-немножку.
Нам еще осталось чего сделать. Мы не можем пометить задачи как завершенные или удаленные. Позже мы будет также использовать jquery для упрощения нашего кода.

Как перезагрузить пространство, если вы не используете emacs.
(require ’[foo.something :as something] :reload)
(require ’[foo.something :as something] :reload-all)

Lisp. Постигая границы

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

Скачайте leiningen

cd ~/bin
wget http://github.com/technomancy/leiningen/raw/master/bin/lein --no-check-certificate
chmod +x lein

Создайте новый проект

cd ~/webspace
lein new hellowww
cd hellowww
emacs project.clj &
Добавьте в строку с зависимостями пакеты compojure, ring.
(defproject hellowww "1.0.0-SNAPSHOT"
  :description "FIXME: write"
  :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"]])
Откройте исходный код:

emacs src/hellowww/core.clj &

Напишите следующее

(ns hellowww.core
  (:use compojure.core, ring.adapter.jetty)
  (:require [compojure.route :as route]))

(defroutes main-routes
  (GET "/" [] "Hello World")
  (route/not-found "Page not found"))

(run-jetty main-routes {:port 8080})
Вуаля, простейший web сервер на clojure готов.

03 февраля 2011

Анал из программы

Надоело думать этими контейнерами, виджетами, событиями и сигналами. Представьте себе некоторую готовую программу. Допустим она императивна. Общая схема работы программы - это граф. Вершины - состояния программы, дуги - это собственно Ваш код. Статистика работы программы может иметь следующие параметры: частота нахождения в некоторой вершине, частота "использования" дуги.

Давайте также возьмем к рассмотрению функциональную программу. Хм, состояний у нее нет. Есть только некоторый набор взаимодействующих функций. Функции взаимодействуют только через параметры и возвращаемые значения. Мне кажеться, что параметром работы программы может быть только частота обращений к функции. Вершин нет, дуг нет.

Теперь как собственно собрать статистику работы программы? В императивной программе даже и не знаю. Как отловить нахождение программы в некоторой вершине? Возможно в каждой функции/методе объекта выводить что-то в журнал. В эклипсе же есть некоторый газерер.
А вот в функциональной программе можно навесить декоратор на каждую функцию и в уже выводить что-то в журнал. А как навесить декоратор на каждую функцию? Организовать некоторый проходчик по коду.

К чему я это все? Ах, да, я действительно потрясен web'ом. Будучи приверженцем десктоп-гуй приложений, я увидел дизайнер своего блога от blogspot и признаю web может заменить десктоп. Но вот как только представлю исходный код странички, и количество сгенерированного html\javascript. Хотя я и сам люблю кодогенераторы, например, для qtscript. 

01 февраля 2011

20 бит от Джесси Фармера. Сетевое программирование в Эрланге.

2 мая 2008 года Так как я учусь Эрлангу, я подумал, что мой первый нетривиальный кусочек кода будет касаться отличительных особенностей языка: сетевого программирования.

Сетевое программирование (или программирование сокетов) является занозой в жопе в большинстве языков. Я впервые узнал, как это сделать в Си прочитав Руководство Сетевого Программирования от Beej. Прочтите и Вы, если осмелитесь.

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

Даже так называемые современные языки такие, как Ява, Руби или Пайтон не так хороши в параллелизме, хотя и избавляют от боли управления всеми мельчайшими подробностями сетевых соединений. Эрланг наоборот был спроектирован для разработки многопоточного программирования.

Я не хотел писать какие-либо пользовательские приложения в ближайшее время, но я подумал: «Если я собираюсь учить Эрланг, сначала я должен узнать его сильные стороны».

С этой целью я решил попробовать повторить набор классических демонов Юникс: echo и chargen.

Echo

Echo — это сервис, которая точь-в-точь возвращает назад все присланные по TCP данные. Код на Эрланге:

-module(echo).
-author(‘Jesse E.I. Farmer ’).

-export([listen/1]).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

% Call echo:listen(Port) to start the service.
% Вызываем echo:listen(Port) для запуска сервиса.
listen(Port) ->
    {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    accept(LSocket).

% Wait for incoming connections and spawn the echo loop when we get one.
% Ожидаем входящее соединение и запускаем процесс для обработки клиента.
accept(LSocket) ->
    {ok, Socket} = gen_tcp:accept(LSocket),
    spawn(fun() -> loop(Socket) end),
    accept(LSocket).

% Echo back whatever data we receive on Socket.
% Возвращаем все присланные данные обратно в сокет.
loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            gen_tcp:send(Socket, Data),
            loop(Socket);
        {error, closed} ->
            ok
    end.


Мы можем запустить сервис с помощью вызова echo:listen(<порт>) из оболочки Эрланга, например, echo:listen(8888) запустит echo сервер на порту 8888 на Вашем компьютере. Вы можете подключиться с помощью telnet к порту 8888 — telnet 127.0.0.1 8888 — и наблюдать результат.

Вот описание программы по функциям.
listen(Port)
Создает сокет, ожидающий входящих соединений на порту Port и отдающий контроль функции accept. accept(LSocket)
Ожидает входящие подключения на сокете LSocket. Как только она получает входящее соединение, она создает новый процесс, который запускает цикличную функцию, а потом ждет следующего соединения.
loop(Socket) Ожидает данные для сокета Socket. Как только получает данные, немедленно отсылает их обратно через сокет. Если происходит ошибка, завершает свою работу.

В примере еть несколько вещей, заслуживающих обсуждения.

Порождение процессов
Процессы в Эрланге являются основным типом данных. Они следуют «актор» модели параллельных вычислений и делают сетевые процессы простыми.

Мы создаем новые процессы функцией spawn, которая принимает Fun, или объект-функцию в качестве параметра. Вы можете считать параметр обычной функцией. Контроль созданного процесса передается этому объекту-функции.

Объекты-функции.

Эрланг, будучи функциональным языком программирования, поддерживает тип «функция». Функции могут создавать новые функции, возвращать их, изменять.

Синтаксис для создания нового объекта-функции выглядит примерно так:
MyFunction = fun(…) ->
    % Your Erlang code here
    % Здесь Ваш Эрланг код
    end.


Chargen

Chargen это сервис, который возвращает поток символов при подключении к нему. Вы можете прочесть их все, но это не интересно.
Код:

-module(chargen).
-author(‘Jesse E.I. Farmer ’).

-export([listen/1]).

-define(START_CHAR, 33).
-define(END_CHAR, 127).
-define(LINE_LENGTH, 72).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

% Call chargen:listen(Port) to start the service.
% Вызов chargen:listen(Port) для запуска сервера.
listen(Port) ->
    {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    accept(LSocket).

% Wait for incoming connections and spawn the chargen loop when we get one.
% Ожидаем входящее соединение и запускаем процесс для обработки клиента.
accept(LSocket) ->
    {ok, Socket} = gen_tcp:accept(LSocket),
    spawn(fun() -> loop(Socket) end),
    accept(LSocket).

loop(Socket) ->
    loop(Socket, ?START_CHAR).

loop(Socket, ?END_CHAR) ->
    loop(Socket, ?START_CHAR);
loop(Socket, StartChar) ->
    Line = make_line(StartChar),
    case gen_tcp:send(Socket, Line) of
        {error, _Reason} ->
            exit(normal);
        ok ->
            loop(Socket, StartChar+1)
    end.

make_line(StartChar) ->
    make_line(StartChar, 0).

% Generate a new chargen line — [13, 10] is CRLF.
% Генерирует новую строку — [13, 10] - CRLF
make_line(_, ?LINE_LENGTH) ->
    [13, 10];
make_line(?END_CHAR, Pos) ->
    make_line(?START_CHAR, Pos);
make_line(StartChar, Pos) ->
    [StartChar | make_line(StartChar + 1, Pos + 1)].


Как и в случае с echo сервером мы можем запустить сервис chargen в оболочке Эрланга с помощью chargen:listen(8888) на 8888 порту (или на другом порту на Ваш выбор).

accept и listen такие же, как и в echo сервисе, он есть некоторые различия в другом:
loop(Socket, StartChar)
Вызов make_line(StartChar), чтобы получить CHARGEN строку начинающуюся с StartChar, запись данной строки в сокет и переход к следующей строке.
make_line(StartChar, Pos)
Рекурсивно генерирует CHARGEN строку, отслеживает текущую позицию с помощью Pos.
Есть еще несколько концептуальных различий.

«Макросы»

В Эрланге как и в Си можно определить постоянные с помощью директивы -define. Они раскрываются во время компиляции. Вы можете ссылаться на них поставив перед именем знак вопроса «?». Таким образом они отличаются от переменных.

Вызов функции по шаблону


Как и с функцией присваивания, вызов функции происходит по шаблону. При вызове функция ищет первое совпадение с шаблоном. Например, если мы вызовем loop(Socket) она найдет подходящее определение, а именно, определение с одним параметром.

Мы можем изменить аргументы, которые влияют на поведение функции loop. ?END_CHAR равен 127, поэтому если мы возовем loop(Socket, 127) мы попадем в функцию, сигнатура которой полностью совпадает и вторым параметром у которой указан символ 127.

make_line работает по такому же принципу. Если мы находимся на последней позиции в строке мы возвращаем символы возврата каретки и перевода строки и останавливаем рекурсию.

Заключение

Я постарался написать простым и понятным языком. Проделанная работа помогла понять мне многое о внутренней работе Эрланга, и, надеюсь, Вам тоже.

Статьи по Теме

Erlang: A Generalized TCP Server
Erlang: An Introduction to Records
Erlang: A Generic Server Tutorial
Learning Erlang