Практика мультипарадигмального программирования, 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?
Стандарты пишутся не для того, чтобы было понгятно, я для того, чтобы у двух людей, которым было понятно, не было разногласия. Но есть исключения: был человек Пастел, и его RFC читать одно удовольствие.

Как определяются символы в Схеме, как ввести функцию в схеме

Есть 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)

Тема следующей лекции: компиляция из одного языка в другой


Практика мультипарадигмального программирования


01 02 03 04 05 06 07 08


Календарь

пн пн пн пн пн
Февраль
12 19
Март
12 19 26
Апрель
02 09 16


Эта статья является конспектом лекции.

Эта статья ещё не вычитана. Пожалуйста, вычитайте её и исправьте ошибки, если они есть.
Личные инструменты
Разделы