Содержание


Guile - универсальный инструмент программирования

Часть 5. Дополнительные средства Guile

Comments

Серия контента:

Этот контент является частью # из серии # статей: Guile - универсальный инструмент программирования

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Guile - универсальный инструмент программирования

Следите за выходом новых статей этой серии.

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

1. Модули, как расширение функциональных возможностей Guile

1.1. Общая характеристика

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

1.2. Модули, поддерживающие SRFI

Самую большую часть дополнительных функциональных возможностей предоставляют модули поддержки SRFI. Аббревиатура SRFI означает Scheme Request For Implementation, то есть документы SRFI определяют синтаксические и процедурные требования к реализации расширений в соответствии со стандартом Scheme R5RS. Ознакомиться с SRFI-документами во всех подробностях можно на специальной странице SRFI.

Вообще говоря, поддержка SRFI в Guile в настоящий момент организована частично в ядре интерпретатора (в основной библиотеке libguile) и частично в разнообразных дополнительных модулях. То есть, те функции, без которых никак не обойтись, становятся доступными сразу после запуска интерпретатора Guile, а прочие требуют использования команды загрузки модуля use-modules. Одна из причин такого разделения - соображения безопасности, а кроме того разработчики Guile успели реализовать в ядре интерпретатора некоторые функции, регламентируемые SRFI-документами, до того, как началась массовая разработка дополнительных SRFI-модулей. Впрочем, в будущем авторы Guile не исключают возможности выноса всей SRFI-функциональности во внешние модули.

В обобщённом виде поддержка SRFI организована модулями со специальными именами вида (srfi srfi-номер) с указанием номера соответствующего SRFI-документа.

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

  • srfi-0 - обеспечивает проверку наличия заданных функциональных возможностей в текущей среде выполнения Guile и соответствующую адаптацию среды;
  • srfi-4 - делает доступными типы данных и процедуры для работы с гомогенными числовыми векторами;
  • srfi-6 - определяет процедуры для простых строковых портов open-input-string, open-output-string и get-output-string;
  • srfi-13 - процедуры для работы со строками;
  • srfi-14 - типы данных и процедуры для работы с наборами символов (character-set).

1.3. Обзор наиболее полезных дополнительных SRFI-модулей

1.3.1. SRFI-1 - работа со списками

SRFI-1 - библиотека процедур для создания, проверки, удаления и обработки списков и пар. Здесь в группе создания наибольший интерес представляют процедуры:

  • (list-copy список) - возвращает новый список, содержащий все элементы заданного списка;
  • (iota количество [начальный_элемент шаг]) - возвращает список, содержащий заданное количество элементов, начинающийся с заданного элемента, и конструируемый с заданным шагом. По умолчанию начальный элемент 0, а шаг равен 1. Например:
    (iota 6)
    (0 1 2 3 4 5)
    (iota 5 4.5 -2)
    (4.5 2.5 0.5 -1.5 -3.5)

В группе предикатов содержатся процедуры проверки свойств списков, из которых наиболее важными являются:

  • (proper-list? объект) - если объект является корректным списком, то возвращает логическое значение #t, в противном случае возвращается #f. Примеры:
    (proper-list? '(1 2 3))
    #t
    (proper-list? '(это тоже правильный список))
    #t
    (proper-list? '())
    #t
    (proper-list? 'список)
    #f

    В последнем случае объект "список" не является списком.

  • (null-list? список) - если список является пустым (), то возвращает #t, в противном случае возвращает #f. Если заданный объект не является списком, то генерируется ошибка.
  • (list= элемент= список1 список2 ... ) - возвращает #t, если все заданные списки равны, иначе возвращает #f. Равенство списков определяется, во-первых, по критерию равенства их длин, во-вторых, по критерию равенства соотвествующих элементов списков, задаваемым предикатом "элемент=". Если задан только один список или вообще ни одного списка, то всегда возвращается значение #t.

В группе селекторов имеется набор процедур-синонимов для комбинаций стандартных процедур car, cadr, caddr и т.д. Кроме того, представляют интерес следующие функции:

  • (take список i) - возвращает список, содержащий первые i элементов заданного списка.
  • (drop список i) - возвращает список, содержащий элементы заданного списка, за исключением i первых элементов.

Представлены также несколько модификаций процедур take и drop.

  • (split-at список i) - возвращает два значения: список содержащий первые i элементов заданного списка, и список, содержащий все прочие элементы исходного списка.
  • (last список) - возвращает последний элемент непустого, конечного заданного списка.

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

  • (length+ список) - возвращает длину заданного списка (количество элементов в нём).
  • (concatenate список_списков) - объединяет все отдельные списки, заданные как аргументы, в один общий список. Пример:
    (concatenate '((77 88 99) (one two three four) (и это список)))
    (77 88 99 one two three four и это список)
  • (append-reverse реверс-голова хвост) - в первом списке (реверс-голова) изменяет порядок следования элементов на противоположный, присоединяет к нему второй список (хвост) и возвращает результат, например:
    (append-reverse '(1 2 3) '(4 5 6))
    (3 2 1 4 5 6)
  • (map функция список1 список2 ...) - позволяет применить одну и ту же заданную функцию ко всем спискам, перечисленным в качестве аргументов. Количество списков-аргументов может быть различным.
    (map (lambda (arg) (display "<<") (display arg) (display ">>") (newline))
    ... '((11 22 33 44) (I you he she we they) (ещё один список)))
    <<(11 22 33 44)>>
    <<(I you he she we they)>>
    <<(ещё один список)>>
  • (find предикат список) - возвращает самый первый элемент списка, который соответствует указанному предикату. Несколько примеров:
    (find number? '(one two 3 four five 6))
    3
    (find string? '(1 2 3 "four" 5 "six"))
    "four"
    (find odd? '(2 4 6 9 10 12 14 15 16))
    9
  • (list-index предикат список1 список2 ...) - возвращает числовой индекс в первом наборе элементов (список1) того элемента, для которого было найдено первое соответствие указанному предикату. Индексация элементов начинается с 0. Если соответствие так и не было найдено, то возвращается логическое значение "ложь". Воспользуемся предикатом и списком из предыдущего примера:
    (list-index odd? '(2 4 6 9 10 12 14 15 16))
    3  ; элемент с индексом 3, то есть, 9 является первым нечётным числом в списке
    (list-index = '(1 2 3) '(3 1 2))
    #f
    (list-index = '(1 2 3 4 5) '(2 1 3 4 5))
    2  ; элемент с индексом 2 в 1м списке равен соответствующему элементу во 2м списке
  • (delete-duplicates список [предикат]) - возвращает список, содержащий элементы заданного списка, но без дубликатов (повторяющихся элементов). Если предикат не задан, то по умолчанию производится проверка на равенство чисел (=) или на равенство нечисловых элементов (equal?), как в следующих примерах:
    (delete-duplicates '(1 2 3 4 5 6 3 7 5 8 9 6))
    (1 2 3 4 5 6 7 8 9)
    (delete-duplicates '(один два три четыре два пять шесть три семь))
    (один два три четыре пять шесть семь)

1.3.2. SRFI-19 - дата и время

Модуль SRFI-19 предлагает набор переменных и функций для различных форматов представления и вычислений, связанных с датами и временем, в различных системах, включая универсальное время (UTC) и "атомное время" (TAI).

Основой для работы со временем служит объект (тип) time, в котором имеются поля системы отсчёта времени, секунд и наносекунд, представляющий произвольный момент времени, отсчитываемый от некоторой точки отсчёта. Несмотря на наличие поля наносекунд, следует отметить, что действительная точность измерения времени может оказаться меньшей.

Также в модуле определены переменные, содержащие различные типы данных о времени. Например, (current-time time-process) возвращает текущее время выполнения процесса. Основные предопределённые переменные:

  • time-utc - время в формате UTC
  • time-tai - время в формате TAI
  • time-duration - продолжительность - разность между двумя моментами времени
  • time-process - процессорное время, выделенное текущему процессу, отсчитываемое от момента создания этого процесса

Наиболее часто используемые функции модуля:

  • (time? объект) - возвращает #t, если объект имеет тип time, в противном случае #f.
  • (make-time тип секунды наносекунды) - создаёт time-объект с заданными значениями соответствующих полей.
  • (time-type объект-time) - возвращает значение поля типа (системы отсчёта времени)
  • (time-nanosecond объект-time) - возвращает значение поля наносекунд
  • (time-second объект-time) - возвращает значение поля секунд
  • (set-time-type! объект-time тип) - устанавливает значение поля типа (системы отсчёта времени)
  • (set-time-nanosecond! объект-time наносекунды) - устанавливает значение поля наносекунд
  • (set-time-second! объект-time секунды) - устанавливает значение поля секунд
  • (copy-time объект-time) - возвращает новый объект, который является копией заданного time-объекта.
  • (current-time [тип]) - возвращает текущее время заданного типа (системы отсчёта). Если тип не задан, то по умолчанию принимается time-utc.

Группа функций сравнения двух time-объектов:

(time<=? t1 t2)
(time<? t1 t2)
(time=? t1 t2)
(time>=? t1 t2)
(time>? t1 t2)

Эти функции возвращают логические константы #t или #f, в зависимости от результата сравнения. Значения t1 и t2 должны иметь одинаковый тип (систему отсчёта).

  • (time-difference t1 t2) - возвращает объект типа time-duration, представляющий продолжительность интервала между моментами времени t1 и t2.
  • (add-duration time-объект time-duration-объект)
  • (subtract-duration time-объект time-duration-объект)

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

Объект date представляет дату в формате Грегорианского календаря и время суток в определённом временном поясе.

Поля объекта date:

  • year - год
  • month - месяц
  • day - номер дня месяца
  • hour - часы
  • minute - минуты
  • second - секунды
  • nanosecond - наносекунды
  • timezone - часовой пояс

Объект date является неизменяемым, то есть его поля можно только считывать после того, как объект создан. Внесение каких-либо изменений не допускается.

Для проверки корректности типа объекта date предназначена функция (date? объект). Создать date-объект можно с помощью следующей функции:

(make-date наносекунды секунды минуты часы номер_дня месяц год отн_час_пояс)

Также имеется группа функций для получения значений полей объекта date: date-nanosecond, date-second, date-minute, date-hour, date-day, date-month, date-year и date-zone-offset. Кроме того, полезными могут оказаться и такие функции:

  • (date-year-day date-объект) - возвращает номер дня в году, считая с 1 января
  • (date-week-day date-объект) - возвращает номер дня недели (0 - воскресенье)
  • (date-week-number date-объект начало_недели) - возвращает номер недели в году, при этом первая неполная неделя года не учитывается. Параметр "начало_недели" определяет номер дня, с которого следует вести отсчёт дней недели: 0 - с воскресенья, 1 - с понедельника и т.д.

Функция (current-date [timezone-offset]) возвращает date-объект, представляющий текущую дату. Здесь необязательный параметр timezone-offset задаёт часовой пояс в относительном UTC-формате.

1.3.3. Простой пример работы с date-объектом

Чтобы обзор не превратился в "чисто теоретический", приведу небольшой пример, в котором используются функции date-объекта.

(use-modules (srfi srfi-19) (ice-9 format))
(let ((now (current-date)))
  (format #t "Сегодня ~2'0d.~2'0d.~4'0d года\n"
    (date-day now) (date-month now) (date-year now)))

Здесь выводится текущая дата в заданном формате. Логика выполнения вполне очевидна. Единственное замечание - в примере задействована функция format из ещё одного модуля расширения ice-9. Эта функция подробно описана в документации, поэтому детальный разбор её работы я оставляю читателям в качестве "домашнего задания".

2. Создание новых модулей

Прежде чем начать разговор о создании новых модулей Guile, следует уделить немного внимания расширениям (extensions), которые представляют собой не что иное, как обычные объектные библиотеки, подгружаемые в интерпретатор Guile при необходимости.

2.1. Расширение Guile с помощью функций C

В следующем примере показано, как можно написать простое расширение, которое делает доступными в Guile специализированные математические функции (в данном случае функции Бесселя).

#include <math.h>
#include <libguile.h>

SCM j0_wrapper( SCM x )
{
  return scm_make_real( j0 (scm_num2dbl( x, "j0")) );
}

void init_bessel()
{
  scm_c_define_gsubr( "j0", 1, 0, 0, j0_wrapper );
}

Этот исходный код нужно сохранить в файле bessel.c и скомпилировать его в объектную динамически загружаемую (shared) библиотеку следующим образом:

gcc -shared -o libguile-bessel.so -fPIC bessel.c

После этого наше свежеиспечённое расширение можно будет загрузить в любой выполняющийся Guile-процесс с помощью функции load-extension, и новая функция j0 сразу же станет доступной, как показано на рисунке 5.1.

Рисунок 5.1. Загрузка и использование расширения
Рисунок 5.1. Загрузка и использование расширения
Рисунок 5.1. Загрузка и использование расширения

2.2. Размещение расширений в модулях

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

$ file /usr/local/lib/libguile-bessel.so
/usr/local/lib/libguile-bessel.so: ELF 32-bit LSB shared object, 
Intel 80386, version 1 (SYSV), dynamically linked, not stripped

А в каталоге, содержащем модули Guile, нужно создать небольшой Scheme-файл (потребуются права суперпользователя root) с именем bessel.scm:

(define-module (math bessel))
(export j0)
(load-extension "libguile-bessel" "init_bessel")

После этого в любом процессе Guile можно будет получить доступ к функциям Бесселя, но теперь уже с помощью процедуры use-modules.

$ guile
guile> (use-modules (math bessel))
guile> (j0 2)
0.223890779141236
guile>

Теперь расширение стало модулем, но результат тот же самый.

2.3. Создание новых модулей на языке Scheme

Новые модули вы можете создавать непосредственно на языке Scheme, воспользовавшись для этого синтаксической формой define-module. Как правило, отдельный модуль размещается в отдельном файле. После написания модуля его файл размещается в каталоге, где Guile сможет по умолчанию его найти - обычно это каталог /usr/share/guile/<каталог_модуля>. Пусть, например, существует каталог /usr/share/guile/mymods, а в этом каталоге есть файл myfunc.scm со следующим кодом:

(define-module (mymods myfunc))
(export sum-quad)
(define (sum-quad x y) (+ (* x x) (* y y)))

Тогда в интерпретаторе Guile можно загрузить наш модуль и воспользоваться функцией, вычисляющей сумму квадратов заданных чисел, как показано на рисунке 5.2.

Рисунок 5.2. Загрузка и использование собственного модуля
Рисунок 5.2. Загрузка и использование собственного модуля
Рисунок 5.2. Загрузка и использование собственного модуля

3. Заключение

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

В первой статье, открывающей цикл, был представлен общий обзор Guile. Во второй статье рассматривалось практическое применение интерпретатора. Третья и четвёртая статьи посвящены взаимодействию Guile с языком программирования C. В пятой, заключительной статье цикла описаны дополнительные средства Guile.


Ресурсы для скачивания


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=680669
ArticleTitle=Guile - универсальный инструмент программирования: Часть 5. Дополнительные средства Guile
publish-date=06142011