Практика мультипарадигмального программирования, 04 лекция (от 19 марта)
Материал из eSyr's wiki.
Предыдущая лекция | Следующая лекция
Содержание |
Встраиваемый пример ЛИСПа
Существует один пример встраемого Common LISP: ecl версия 0.6, документирован паршиво.
Есть несколько диалектов, которые ни Common, ни Схема, ни ведома зверушка. Например, есть librep, который Схема, но маскируется под ЛИСп, например, есть () и T — истина, но на самом деле это Схема. Есть тьма-тьмущая своих диалектов, икто не объясняет зачем.
Что показать в качестве примера встраемого ЛИСПа? Легче встраивать Схему, она часто встраивается. Но в emacs встроен eLISP, в AutoCAD встроен AutoLISP. Но AutoCAD закрытый, emacs — отдельное проишествие. В остальных случаях чаще схема. У любого приложения, ориентированного на Схему, предполагается интеграция с Си. Посему будет рассмотрена интеграция с MZ Scheme.
Scheme
Отличия Scheme
- В Схеме списки, а не S-выражения.
- Список верхнего уровня S-выражением не является
- ...
- В Схеме не различается понятия связанного с символом значения и функции
Пример:
Common Lisp > car Error: symbol has no value >(setq car 25) 25 >car 25
при этом это не мешает иметь функцию car
Scheme >car #<system_function car>
При этом если мы зададим значение, мы функцию потеряем. Что это позволяет:
(lambda (x y) (cons x y))
В Лиспе это вызовет ошибку, надо
#'(lambda (x y) (cons x y))
но это немного другое. В Схеме это будет вычислено.
Когда мы говорим (cons a b), то в Схеме все три параметра вычисляются, а в Лиспе первым элементом должна быть функция. Как следствие, в Схеме не нужен funcall.
Именование
Lisp | Scheme |
---|---|
eq | eq? |
eql | eql? |
equal | equal? |
stringp | string? |
Как определяются символы в Схеме, как ввести функцию в схеме
Есть define, он ничем не является, встречаться должен на первом уровне, он не является S-выражением.
- Сопоставление символу значениея
(define twenty-five 25)
- Сопоставление функции
- Схемеры обычно используют такой вариант:
(define (myfun arg1 arg2 ...) ...)
- Предыдущий вариант является сокращённой формой записи для
(define myfun (lambda (arg1 arg2 ...) ...))
Реализация Scheme
Существует достаточно много реализаций Схемы, соответствующих стандарту. В итоге лектор выбрал две:
- guile — декларируется, что Схема там побочный эффект, а вообще это могучая система программирования, где можно будет делать вообще на всём. Но в документации там швах. А те потроха, в которых приходится копаться, ... . Причём есть два API, одно deprecated, разработчики сказали, что они его больше не будут поддерживать, но по документации оно понятно. Есть новое API, но в качестве примера его использовать нельзя. Этот интерпретатор можно и встраивать, и расширять.
- mzscheme — не намного лучше, но удалось разобраться практически сразу. Написана явно на Си, с расчётом и расширения, и встраивания в Си. И те же проблемы, что и в Си, например, garbage collected. А нам нужно, чтобы S-выражения были в локальных переменных ... . Проблема в том, что чтобы запустить garbage collector, нужно знать пределы стека. Посему, там есть два способа запуска: функция, которая не возвращаем значения, и мы запускаем её навсегда. Пишем вторую функцию main, void на вход, void на выход. И если хотим возвращать значения, то делаем:
main → scheme → real_main()
Есть второй вариант, но он не портабельный.
Когда лектор добрался до примера, как его встроить, то это был экран текста, довольно запутанный.
Не получается так хорошо, как с Tcl, в две строчки. Дело, вероятно, в том, что Tcl широко используется, а Scheme — нет, и тем более не в качестве встроенного языка.
Как расширить интерпретатор
Обычно встраивание требуется для того, чтобы пользователь что-то написал, а чтобы он писал что-то применительно к программе, нужно, чтобы были проблемно-ориентированные функции. Оказалось, что в mzscheme это продумано достаточно хорошо.
- Есть тип Scheme_Object *, без звёздочки не употребляется никогда. Можно считать, что это S-выражение и есть
- Чтобы это появилось, есть два варианта:
- #include <scheme.h>
- #include <escheme.h>
- Различие в способе сборки мусора
- Чтобы это появилось, есть два варианта:
- Когда возникает написать схемовскую функцию, то она должна возвращать Scheme_Object, и получать int argc, Scheme_Object ** argv
Scheme_Object * func(int argc, Scheme_Object ** argv)
Пример: Функция triple: делает из (a) (a a a). Или (a . (a . (a .()))).
Scheme_Object * func(int argc, Scheme_Object ** argv) { /* * проверяем количество параметров, * должно быть 1, иначе ошибка */ if (argc != 1) /* * правильным будет в случае не одного * параметра сообщать ошибки интерпретатору схемы */ { scheme_null; //искать пустой список пришлось в исходниках } return scheme_make_pair(argv[0], scheme_make_pair(argv[0], scheme_make_pair(argv[0], scheme_null))) }
Список простых функций
Значения
- scheme_null ()
- scheme_true #t
- scheme_false #f
- scheme_void — в Схеме вроде нет, нужно для того, чтобы не возвращать значения
Как создавать S-выражения
- Scheme_Object * scheme_make_pair(Scheme_Object * a, Scheme_Object * b) — создание точечной пары (обычно такие вещи называются pair)
- Пусть есть строка и хочется сделать атом, который строка. Как это сделать: Scheme_Object * scheme_make_string(const char * str); Есть ещё Scheme_Object * scheme_make_string_no_copy(const char * str). В первом случае происходит копирование, во втором — просто устанавливается ссылка на существующий объект
- Scheme_Object * scheme_make_integer_value(int i) — есть несколько модификаций (from_long_long, from unsigned, &hellip)
- Scheme_Object * scheme_make_char(int c) — int для поддержки unicode
- Scheme_Object * scheme_make_double(double d)
- Scheme_Object * scheme_make_symbol(...) — пытается найти имя (char *)
Как извдечь данные из Scheme_Object *
- double scheme_real_to_double(Scheme_Object *) — единственная простая ничем не примечательная функция
- int scheme_get_int_val(Scheme_Object *, long * v) — возврат значения производится через v
Ожидаемо, если бы были функции для извлечения строки и т. д. Но их не нашлось. Нашлось следующее:
- SCHEME_STR_VAL(...) — макрос, нужно передать объект, возвращает char *
- SCHEME_FLOAT_VAL(...)
- SCHEME_DOUBLE_VAL(...)
- SCHEME_INT_VAL(...)
Предварительно надо проверить тип:
- SCHEME_DOUBLEP
- SCHEME_DOUBLEP
- SCHEME_INTP
- SCHEME_NULLP
- SCHEME_STRINGP
- SCHEME_PAIRP
Макросы возвращают 0 в случае "нет" и 1 в случае "да"
Работа со списками
- SCHEME_CAR
- SCHEME_CDR
- SCHEME_CAAR
- SCHEME_CDDR
- SCHEME_CADR
Других нет.
Другой пример: nple
nple(int quan, char c) — делает список из quan символов c
Scheme_Object * nple(int argc, Scheme_Object ** argv) { Scheme_Object * res = scheme_null; int i, n; if (argc != 2) { return res; } if (!SCHEME_INTP(argv[0])) { scheme_wrong_type("nple", "integer number", 0, argc, argv); //очень похоже на то, что это longjump и return не требуется return scheme_null; } scheme_get_int_val(argv[0], &n); for (i = 0; i < n; i++) { res = scheme_make_pair(argv[1], res); } return res; }
Как расширить интерпретатор
Надо сделать сошку.
Сошка будет состоять из одного модуля, в ней будет три функции:
- scheme_initialize
- scheme_reload
- scheme_module_name
Одна из них (scheme_initialize) даётся для инициализации интерпретатора схема и своих собственных данных.
scheme_reload инициализирует только интепретатор (Scheme_Env). Для начала необходима инициализация примитивов — функций, у которых есть C-обёртки. Мы сделаем обёртки и погрузим их в интерпретатор.
Scheme_Object * scheme_reload(Scheme_Env * env) { Scheme_Object * proc; proc = scheme_make_prim(triple); //лучше функции объявлять как статики scheme_add_global("triple", proc, env); //не обязан быть именно примитив //со второй функцие поступим иначе — есть вариант создания примитива, при котором делается проверка аргументов proc = scheme_make_prim(nple, "nple", 2, 2); //последние два параметра — минимальное и максимальное количество параметров scheme_add_global("nple", proc, env); //не обязан быть именно примитив return scheme_void; //обычно делают так, но можно попытаться что-то создать и вернуть, проявится оно при вызове scheme_load_extension() }
Scheme_Object * scheme_initialize(Scheme_Env * env) { return Scheme_Reload(env); }
Различие: initialize вызывается только в первый раз, в остальные — reload.
Scheme_Object * scheme_module_name() { return scheme_false; }
Как это скомпилировать
Можно использовать gcc,
Можно использовать их программу, которая есть враппер для разных C-компиляторов. Сделано, вероятно, для унификации. Называется mzc.
$mzc --cc sample.c ; # компиляция модуля, получим sample.o $mzc --ld sample.so sample.o
Запуск
$ mzscheme > (load-extension "sample.so") > (triple 'a) (a a a) > (nple 10 2) (2 2 2 2 2 2 2 2 2 2)
Тема следующей лекции: компиляция из одного языка в другой
Практика мультипарадигмального программирования
Календарь
пн | пн | пн | пн | пн | |
Февраль
| 12 | 19 | |||
Март
| 12 | 19 | 26 | ||
Апрель
| 02 | 09 | 16 |