понедельник, 2 июля 2012 г.

Использование Qt для реализации GUI в Common Lisp

У меня нет никаких предрассудков насчёт Qt и на десктопе у меня KDE, так что для первых шагов в GUI я воспользовался проектом под названием CommonQt. Это CFFI-биндинги к libqt и libsmoke.

Здесь я запишу, как настроить окружение для того, чтобы писать Qt-приложения. Ниже скриншот конечного результата. ;)

четверг, 14 июня 2012 г.

Простое и сложное в Common Lisp

Сегодня развлекался с моим любимым Common Lisp и обнаружил интересную деталь.

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

(average 1 2 3 4)
5/2

...ну хорошо, хорошо:

(coerce (average 1 2 3 4) 'float)
2.5

Заметьте то, что я не хочу передавать в average список, я хочу кортеж чисел. Интересная деталь вот в чём: самая простая (в смысле, лаконичная) реализация, которая пришла мне в голову, это такая:

(defun average (&rest args)
  (/ (reduce #'+ args) (list-length args)))

Дело в том, что определение среднего арифметического — это задачка для средней школы, и мы вполне можем её дать в качестве задания по программированию на второй паре (на первой мы изучим весь синтаксис лиспа вместе с макросами defun и defvar). Но на самом деле мы не можем её дать, потому что у нас есть вот эта форма в решении: (reduce #'+ args), и, чтобы студенты смогли ей бегло пользоваться, нужно, чтобы они знали как минимум:

  • о разделении пространства имён переменных и пространства имён функций;
  • как передавать в функцию имя другой функции;
  • о назначении и работе reduce, что для не знакомого с функциональными методами, вообще говоря, стопроцентная магия (особенно если их уже обработали алголоподобными языками);
  • о том, что параметры, переданные через &rest, оборачиваются в список.

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

Конечно, мы можем соорудить нечто работающее из let и dotimes, но тогда зачем нам Common Lisp вообще? :)

четверг, 7 июня 2012 г.

Паттерн «Веб-функция»

Допустим, вам нужно написать endpoint для аякс-запроса. Или обычный обработчик обычного POST-запроса от веб-формы. Тогда основные действия, которые обязательно должны быть в процессе обработки, будут такие:

  • Фильтрация входных данных (это НЕ валидация бизнес-правилами, только лишь такие действия, как укорачивание, очистка от окружающих пробелов, исключение непечатных символов, приведение к нижнему регистру и т. д.). Или мы можем даже сыграть роль адаптера и переименовать некоторые параметры запроса в вид, ожидаемый обработчиком.
  • Обработка запроса. Здесь мы собственно делаем то, зачем существуем. Мы ожидаем уже более-менее очищенный от мусора массив входных параметров, возможно, среди которых есть ошибочные (например, числовое значение вне допустимого диапазона, или передан массив вместо скаляра).
  • Форматирование результата для отклика клиенту. Например, мы можем возвращать JSON, или генерировать HTML страницу по шаблону. Или генерировать изображения. Или отдавать файлы с диска.

Если записать в функциональном стиле на PHP, то можно получить следующий шаблон хэндлера:


<?php

echo format(process(clean($_REQUEST)));

/**
 * Здесь очистка суперглобального массива $_REQUEST и генерация массива $request
 * 
 * @param array $request Данные запроса из массива $_POST или $_GET (или $_REQUEST).
 * @return array Очищенные данные из запроса, которые ожидает функция process.
 */
function clean($request)
{
  // TODO
  return $request;
}

/**
 * Здесь выполнение действий согласно запросу $request
 * 
 * @param array $request Параметры запроса, очищенные функцией clean.
 * @return array $result Результат работы со всей информацией, необходимой для форматирования ответа клиенту.
 */
function process($request)
{
  // TODO
  return $request;
}

/**
 * Здесь форматирование результата работы для выдачи клиенту.
 * 
 * @param array $result Результат работы, как он сгенерирован процессором. Нам не дозволяется использовать какие-либо другие данные.
 * @return string Форматированный ответ, готовый к отправке клиенту через echo.
 */
function format($result)
{
  // TODO
  return json_encode($result);
}
?>

Если process сталкивается с ошибкой, он пишет об этом в результат своей работы (например, в поле 'error') и сразу возвращает результат в format.

Если format должен генерировать большой HTML документ, то ничего страшного, он может и делать echo внутри себя, вместо того, чтобы возвращать строку. Тогда вызов всей цепочки будет без echo в начале, конечно же.

Как запускать программы на Common Lisp как консольные скрипты

Допустим, у вас есть классная маленькая программка на Common Lisp под названием filter. Она занимает всего один файл исходного кода под названием filter.lisp примерно в 200 строк длиной. В этом файле вот такой заголовок:


(defpackage :localhost.me.filter
  (:use :common-lisp)
  (:export :run))
(in-package :localhost.me.filter)

И дальше собственно сама программа. Допустим также, что вы пользуетесь своей программой часто. Загвоздка в том, что для того, чтобы её запустить, вам необходимо сделать слишком много действий:


$ cd $PROGRAMDIR
$ sbcl
CL-USER> (load "filter.lisp")
CL-USER> (:localhost.me.filter:run)

Рассмотрим два решения, одно простое, но не всегда поможет, другое посложнее и поможет уже в большем числе случаев.

Решение первое

SBCL умеет запускать программы на CL в режиме скриптов командной строки, для этого используется специальный флаг --script. Более того, благодаря этому можно написать лисп-программу, добавить к ней типичный юниксовый шебанг, вызывающий SBCL с этим флагом, и она будет работать как любой другой шелл-скрипт (за исключением того, что рантайм SBCL весит 50MB бгг). Так что напишем этот скрипт:


#!/usr/bin/sbcl --script
(load "filter.lisp")
(:localhost.me.filter:run)

Теперь положим этот скрипт в ту же директорию, что и filter.lisp, в файл с именем run и можно будет запускать нашу программу так:


$ ./run

Что и требовалось.

Решение второе

Однако, у флага --script есть одна особенность: SBCL не загружает никакие пользовательские скрипты инициализации при старте, так что скрипт будет выполняться в 100% дефолтном рантайме. Это плохо, если, допустим, вы используете Quicklisp и у вас в filter.lisp есть такой вызов:


...
(ql:quickload :CL-FAD)
(use-package :CL-FAD)
...

Ну или вызов любой другой библиотеки, неважно. Если запустить такой скрипт первым способом, то SBCL будет грязно ругаться по поводу того, что он не знает, что такое ql:quickload.

Для решения этой проблемы мы смухлюем, подменив бинарник SBCL таким бинарником, у которого все нужные библиотеки уже загружены. В SBCL есть функция save-lisp-and-die, которая выгружает текущее состояние рантайма в файл с указанным именем. Дальше этот файл можно использовать как обычный бинарник SBCL, в том числе, и для вызовов с использованием --script. Поэтому если запустить SBCL и сразу выгрузить его в файл, то полученный бинарник будет содержать всё, что SBCL подключил при загрузке, основываясь на пользовательских конфигах.


(save-lisp-and-die "/абсолютный/путь/до/файла")

Теперь в скрипте run, который написан по первому варианту, заменяем путь до системного SBCL путём до выгруженного бинарника, и скрипт начнёт работать как должен.

Как писать юнит-тесты к программе на Free Pascal при помощи FPTest

В свете работы над моими старыми программами из КГУ понадобилось покрывать код юнит-тестами. Как выяснилось после гуглопоиска, для Free Pascal, которым я компилирую свою переработку, существует проект под названием FPTest, представляющий собой каркас для написания юнит-тестов на этом замечательном языке.

Документации по этому проекту довольно немного, и официальная вики, как и README, сильно Lazarus-ориентированы. Поэтому расскажу здесь, как подключить FPTest к существующему консольному проекту и собрать тесты, написанные с его помощью.

среда, 23 мая 2012 г.

PHP: архивы PHAR не работают с включённым suhosin patch

Сегодня ставил behat и ужаснулся. phar-архивы не запускаются в командной строке вообще никак. Даже не появляется сообщения об ошибке, просто молча ничего не выполняется.

После некоторого продолжительного поиска в Сети был найден ответ на ServerFault: выполнение phar-архивов блокирует suhosin patch.

Для решения проблемы нужно раскомментировать следующую строку в /etc/php5/cli/conf.d/suhosin.ini:


;suhosin.executor.include.whitelist =

И заменить её на


suhosin.executor.include.whitelist = "phar"

Одноразовое решение выглядит так:


$ php -d suhosin.executor.include.whitelist="phar" MYPHAR.phar


воскресенье, 13 мая 2012 г.

Рефакторинг заданий курса КГУ по программированию 2004-2011 гг.

У нас в набережночелнинском филиале КГУ на факультете прикладной математики и информационных технологий преподавали несколько программистских курсов. На них студентам задавали задания в духе "перемножьте каждый четвертый член массива чисел полученной от пользователя длины", и их нужно было запрограммировать. В первом семестре - на Delphi. Потом - на C++. После третьего курса можно было писать на чём хочешь.

Пожалуй, это были единственные учебные курсы из всех пяти лет обучения, которые хоть что-то дали лично мне как разработчику. Во всяком случае, я узнал об ООП в стиле C++ и о такой вещи, как указатели.

Я ужасно обожаю чистить чужой код, поэтому мне пришла в голову идея поднять старые исходники готовых решений тех задач и переоформить их согласно всему тому, чему я научился за последние 7 лет практики (да, включая универ, конечно :) ).

Изменения, которые я хочу внести, следующие:

  1. Структура каждой программы должна быть как можно проще. Всё должно компилироваться из командной строки. Всю IDE машинерию нужно убрать.
  2. Для сборки будет использоваться GNU Make, для программ на любом языке.
  3. Покрыть код юнит-тестами.  Чистый TDD использовать уже поздно, всё-таки код уже написан.
  4. Код единообразно переформатировать, очистить от "попахивающих" мест и снабдить формальными комментариями.
  5. Текст заданий изъять изо всяких PDF и DJVU файлов, в которых он сейчас находится, и перенести в текстовые README.md.
Кроме того, для разработки на первых курсах активно использовался homemade хак для того, чтобы выводить кириллический текст в консоль. Отдельный хак для программ на Паскале, отдельный для программ на Си. Общаться с пользователем по-русски не так важно, так что для простоты все сообщения от программ будут переведены на английский.

В первом семестре использовался Delphi, с помощью которого компилировались консольные приложения. Вместо Delphi будет использоваться Free Pascal в объектном режиме. Всю машинерию, связанную с Delphi, нужно убрать.

Для тестов программ на Паскале будем использовать FPTest.

Начиная со второго семестра и дальше использовался C++. Для компиляции точно буду использовать g++ вместо Visual Studio, а вот какой каркас для тестов использовать, пока не знаю (просто пока не пробовал xUnit-каркас для C++).

Исходники уже выложил на ГитХабе. Пока более-менее нормально обработано только первое задание.

Думаю, когда закончу задания первого семестра, напишу письмо в КГУ. :)