пятница, 16 июля 2010 г.

SchemeScript: разрабатываем на языке Scheme в Eclipse



Введение


Scheme - функциональный язык программирования, один из двух наиболее известных в наше время диалектов языка Lisp. Гай Стил (Guy L. Steele) и Джеральд Сассмен (Gerald Jay Sussman) из Массачусетского технологического института (MIT) — создали его в середине 1970-х годов. Именно Scheme долгое время применялся в MIT для обучения программированию (сейчас заменен на Python) и именно на Scheme написаны примеры в знаменитой книге "Структура и интерпретация компьютерных программ" - библии всякого уважающего себя программиста.

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



Плагин называется SchemeScript и является частью проекта SchemeWay. Скачать последнюю версию данного плагина можно с сайта SourceForge (после скачивания достаточно скопировать jar-файл в каталог eclipse/plugins). Проект активно развивается, последняя версия носит название 1.3.0 alpha10 и выпущена 09.05.2010 г. Исходный код доступен на GitHub. Дополнительную информацию о плагине, некоторых трюках, которые допускает SchemeScript, и его возможностях можно узнать из блога Dominique Boucher - автора данного расширения.

Стоит отметить, что даже последняя версия SchemeScript не работает в Eclipse Helios, поэтому пока для разработки на языке Scheme следует использовать Eclipse Galileo.

UPD 17.07.10: Последняя версия на GitHub работает в Eclipse Helios, но ее нужно собирать из исходников. Через некоторое время Доминик обещал пересобрать и дистрибутив.

Eclipse-перспектива Scheme


После установки плагина станет доступна Eclipse-перспектива Scheme. По-умолчанию она выглядит следующим образом:



Перспектива включает в себя вид (в терминах Eclipse) навигатора по проектам - Navigator, вид с отображением разобранного содержимого редактируемого файла - Outline, в котором будет отображаться список определений констант и функций, а так же два свернутых вида: вид с иконкой в виде буквы лямбда - Definitions и вид Console.

В главное меню Eclipse добавлена группа команд Scheme, предназначенная прежде всего для управления интерпретатором. На панель быстрого запуска выведены три кнопки (снабжены иконками в виде буквы лямбда), слева-направо: запустить интерпретатор, перезапустить интерпретатор и остановить интерпретатор.

Прежде чем приступать к работе с редактором, необходимо создать Eclipse-проект, в котором будут храниться файлы исходного кода. Специфичного для Scheme типа проекта плагин SchemeScript не предоставляет, поэтому нужно создать обычный проект из каталога General:



Созданный проект отобразится в навигаторе:



Теперь в проекте можно выполнить New -> File и создать файл, предназначенный для хранения исходного кода на Scheme. Данный файл должен иметь расширение .ss или .scm. Файлы с данными расширениями будут редактироваться с помощью специального редактора из поставки SchemeScript.


Редактирование исходного кода


SchemeScript предоставляет редактор исходного кода на языке Scheme с довольно богатыми возможностями. Рассмотрим их подробнее.

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



Редактор содержит встроенный анализатор корректности S-выражений. Если, например, пропустить закрывающую скобку, то будет сгенерировано уведомление об ошибке:



Существует так же такая полезная вещь, как автоподстановка или автозавершение выражений. Т.е., если при наборе нажать Ctrl+Space, то всплывет окошко с доступными вариантами завершения вводимой конструкции:



При создании определений (например, с помощью ключевого слова define) они добавляются в вид Outline, что позволяет при необходимости быстро обратиться к нужному определению:



Плагин SchemeScript добавляет две группы пунктов в контекстное меню редактора кода: Source - содержит команды редактирования исходного кода - и Eval - содержит команды его интерпретации:



Рассмотрим группу пунктов меню Source:



Find definition - осуществляет поиск определения символа в исходном коде. Т.е., например, нам нужно посмотреть как определен символ sqr. Для этого нужно поставить курсор после данного символа



и нажать F12. Курсор будет перемещен на определение функции sqr, причем данное определение будет выделено:



Complete symbol - с данной командой мы уже познакомились, это - команда автозавершения.

Describe symbol - выводит всплывающую подсказку с описанием символа. Например, рассмотренный нами в прошлом примере символ sqr является функцией:



Insert header comment - добавляет глобальный комментарий в начало файла с исходным кодом. В настройках (см. ниже) можно определить некоторые поля такого комментария - автор, копирайт и т.д.



Insert chapter comment - добавляет комментарий к некоторому блоку кода - набору функций и определений.



Insert section comment - добавляет комментарий к конкретной функции или определению.



Toggle comment - закомментировать/раскомментировать выделенный блок кода. Если блок кода не является комментарием, то к нему добавляются лидирующие символы ";":



И, соответственно, наоборот - если какое-то выражение закомментировано, то символы комментариев убираются:



Compress spaces - убирает лишние предшествующие и последующие пробелы и символы перевода строки. Что-то вроде функции trim:



Format - осуществляет форматирование кода в соответствии с заданными настройками. Расставляет отступы, убирает лишние пробелы и переносы строки.

Код до форматирования



Код после форматирования



Swap S-expressions - меняет порядок следования S-выражений одного уровня вложенности. Например, если в предыдущем примере выполнить данную команду, то list и lambda поменяются местами:



В пункте Scheme главного меню Eclipse содержится команда Find Scheme Symbol (доступна так же по Ctrl + F12). При вызове данной команды появляется диалоговое окно, позволяющее быстро найти определение нужной функции. Например, у нас в файле исходного кода определена функция sqr, соответственно, если в поле Enter the symbol prefix: диалогового окна ввести sq, то в поле Matching symbols появится определенная в коде функция sqr:



Если из поля Matching symbols выбрать эту функцию, то курсор в редакторе автоматически перейдет к месту ее определения. Предположим, что у нас несколько файлов с исходным кодом и в каждом определена функция sqr. Тогда помимо самой функции нужно еще выбрать файл. Для этого служит вид Definitions, в который после выбора функции помещается информация о вариантах ее определения из разных файлов:


Интерпретация


Когда код написан, его хочется проверить, например, запустив на интерпретаторе Scheme. Плагин SchemeScript предоставляет возможности и для этого. Прежде всего, это пункт Scheme главного меню Eclipse, содержащий команды выбора нужного интерпретатора и управления им: запуск, перезапуск и останов (необходим в случае зависания приложения, например - вечного цикла. Т.к. Scheme может оптимизировать хвостовую рекурсию, вечный цикл - действительно вечный).



На рисунке видны варианты выбора интерпретатора. Прежде всего это - External Interpreter - подключает внешний интерпретатор, установленный на той же машине, что и Eclipse. Интерпретаторов Scheme сейчас много, под Windows можно использовать, например, Chez Scheme.

Embedded Kawa - входящий в поставку SchemeScript интепретатор Kawa. Представляет собой разрабатываемый под эгидой GNU на Java фреймворк для реализации высокоуровневых и динамических языков, код с которых компилируется в Java-байткод. Позволяет легко использовать в коде на Scheme Java-классы и поэтому хорош в качестве скриптового языка. Помимо интерпретатора содержит и компилятор, что позволяет использовать его в качестве полноценного языка под JVM, как используются Scala, Groovy и Clojure. Некоторые энтузиасты используют Kawa для разработки приложений, работающих на платформе Android.

Remote Interpreter - позволяет выбрать в качестве интерпретатора специальный сервер, запущенный на другой машине. Например, в институте при обучении программированию можно поднять специальный сервер, умеющий интерпретировать Scheme-код. Взаимодействие SchemeScript и данного интерпретатора будет осуществляться по сети.

SISC Interpreter - встроенный REPL от Second Interpreter of Scheme Code - написанного на Java интерпретатора Scheme.

После выбора работающего интерпретатора, например Kawa, его нужно запустить соответствующей командой меню или кнопкой с панели быстрого запуска:



Непосредственно для отправки участков кода на интерпретацию используется группа команд Eval из контекстного меню редактора кода:



Данная группа содержит следующие команды:

Eval top expression - выполнить выражение верхнего уровня относительно того, в котором находится курсор. Если курсор находится перед последней закрывающей скобкой - см. рисунок



и выполнена данная команда, то в интерпретатор будет загружено определение функции sqr.

Eval previous expression - выполнить предыдущее относительно курсора выражение. Если в примере



выполнить данную команду, то в интерпретатор будет загружено выражение (* x x), которое он не сможет выполнить, т.к. параметр x не определен:



Load file in Interpreter - загрузить файл в интерпретатор, т.е. выполнить в интерпретаторе все содержимое файла.

Рассмотрим пример: существует код функции, вычисляющей n-е число Фибоначчи. Мы хотим вычислить четвертое число Фибоначчи:



Если выполнить команду Eval previous expression после (fib 4), то будет сгенерирована ошибка: интерпретатор не знает определение функции fib:



Если же выполнить команду Load file in Interpreter, а затем - Eval previous expression, то вычисление пройдет корректно:


Поддержка Kawa REPL


Read-eval-print loop (REPL) - простая интерактивная среда программирования, в данной среде пользователь может вводить выражения, которые тут же будут вычислены, а результаты вычисления - отображены пользователю.

В состав Kawa входит такая среда. Например, можно прямо в консоли с запущенным интепретатором определить функцию sum и вычислить ее значение относительно каких-то аргументов (каждая строчка трактуется как одно выражение):



Дополнительно так же существует вид Kawa Stack Trace, позволяющий следить за состоянием стека вызовов при отладке. Чтобы понять как им пользоваться - смоделируем ошибку, например вызовем функцию, вычисляющую квадрат числа, от аргумента "ss":



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



Стектрейс при этом будет выглядеть так:



Если отжать в виде Kawa Stack Trace кнопку Show Scheme frames only, то будет отображен полный стектрейс, содержащий в том числе и ислючительные ситуации в коде, написанном на Java, а не только на Scheme:


Настройки


Плагин SchemeWay является довольно гибконастраиваемым расширением для среды разработки Eclipse. Чтобы разобраться со всеми его возможностями обязательно стоит рассмотреть настройки. Дерево настроек начинается с группы Scheme:



На странице Scheme доступны следующие настройки:
- Displayed tab with - позволяет задать ширину (в пробелах) для отображения символа табуляции. Это работает, только если в команде не принято заменять табуляции на пробелы.
- Enable structural editing - галочка отвечает за включение/выключение структурированного редактора Scheme. Структурированный редактор работает не с символами, а с S-выражениями целиком. Т.е. по backspace, например, будет удаляться сразу все S-выражение. Так же при открытии скобки будет сразу же добавляться парная закрывающая.
- Save... и Load... позволяют сохранить и наоборот - загрузить - настройки SchemeScript в/из .epf-файл/а.



Страница Appearance отвечает за внешний вид редактора. Позволяет настроить цвет, которым будет выделяться парная скобка (группа Matched parenthesis), цвет фона редактора (группа Background) и цвета, используемые при подсветки синтаксиса (группа Foreground).



На рисунке выше приведен пример установки для строковых констант синего цвета.



Страница Comments отвечает за отображение комментариев. На ней представлены следующие настройки:
- Prefix for comments - префикс, с которого будет начинаться комментарий. По-умолчанию - две точки с запятой (;;).
- Author field content - содержимое поля Автор, позволяет задать имя/фамилию автора кода.
- Copyright field content - содержимое поля Копирайт, позволяет задать информацию об авторских правах.
- Automatically continue comment from previous line - если данная галочка установлена, то при нажатии Enter после комментария на новой строке комментарий продолжиться, т.е. новая строка начнется с ;;.



Страница Fast Eval Keys позволяет задать команды (т.е. имена Scheme-функций), которые будут выполнены при нажатии соответствующих клавиатурных комбинаций. По-умолчанию каждому Fast Eval Key 0 ... 9 соответствует комбинация клавиш Alt-K, 0...9. При активном использовании плагина у каждого разработчика формируется набор названий методов, которые он постоянно вызывает (например, метод test, для запуска какого-то тестирующего кода). Соответственно, вызов данный методов можно назначить на клавиатурные комбинации.



Страница Indentation служит для настройки отступов, добавляемых при написании/форматировании кода. Для каждого ключевого слова можно указать настройку отступов, применыемых для его аргументов. Таблица настроек устроена следующим образом: в колонке Symbol задается ключевое слово, отступы для которого настраиваются. Колонка Indentation scheme задает схему расстановки отступов. Схемы бывают следующими:
- default - все под-выражения выравниваются ниже первого символа своего обрамляющего выражения.
- definition - все подвыражения выравниваются как обрамляющее выражение + 2 пробела (похоже на оформление формы define).
- if - все подвыражения выравниваются как обрамляющее выражение + 4 пробела (похоже на оформление формы if).
- none - нет отступов, все подвыражения выравниваются так же как и обрамляющее выражение.
- with - наиболее сложная форма выравнивания, используется для функций с длинными именамим и/или большим списком параметров. Первые N-параметров выравниваются с отступом в 4 пробела, остальные - в 2. Столбец Hint задает величину N. Примером использования данной формы служит выравнивание ключевого слова let:





Страница Interpreter позволяет задать настройки интерпретации и разбора кода. Пока таких настроек две:
- Save file before loading in interpreter - если галочка установлена, то перед отправкой на интерпретацию файл будет сохранен. Позволяет не помнить о сохранении изменений перед интерпретацией.
- Surround exressions with (begin ...) - обрамлять ли интепретируемые выражения в форму (begin ...). Как я понимаю, данная опция необходима для работы с некоторыми интепретаторами, которые могут за раз выполнять только одно S-выражение.



Страница External Interpreter позволяет указать используемый внешний интерпретатор. Можно задать следующие параметры:
- Interpreter name - название интерпретатора
- Command line - командную строку, включающую полный путь до интепретатора и какие-то нужные ему для работы опции.
- Working directory - рабочий каталог интерпретатора - каталог, относительно которого он будет вычислять пути к файлам.
- Error Regexp - регулярное выражение для разбора ошибок. Интерпретатор выдает сообщения об ошибках в каком-то своем формате, SchemeScript должен знать как из этой строки с ошибками извлечь информацию о месте нахождения ошибки в исходном коде. Например, для Kawa данное регулярное выражение следующее: [\t ]+at [^(]+\(((.+):([0-9]+))\).



На рисунке выше показан запущенный в консоли внешний интерпретатор chez-scheme.



Страница Remote Interpreter содержит настройки подключения к удаленному интепретатору - интерпретатору, запущенному в качестве сервера и слушающему определенный порт. Соответственно, данная страница позволяет задать имя хоста и порт, на котором этот удаленный интерпретатор работает.



Страница Lexical extensions служит для управления лексическими расширениями Scheme. Содержит два пункта:
- Treat brackets ([] or {}) as parentheses - обрабатывать ли фигурные и квадратные скобки так же как и круглые. Т.е. подсвечивать ли пару и контролировать ли парность скобок.
- Accept dash (#) as identifier part - разрешать ли использовать символ решетки как часть имени идентификатора (как я понял - работает не для всех интерпретаторов Scheme).



Последняя страница - Syntax - позволяет гибко управлять обработкой синтаксиса языка - добавлять и удалять конструкции из групп, что влияет на подсветку синтаксиса при редактировании. На данной странице определены 5 групп:
- Define - определения, содержат ключевые слова, с которых начинаются определения, например - define.
- Special names - специальные именнованые параметры, такие как #!key или #!rest.
- Special forms - специальные формы, такие как if, and, let и т.д. По сути - основная часть ключевых слов языка программирования.
- Mutator - мутаторы, т.е. функции, меняющие состояние. Подсвечиваются иначе, нежели функции, не меняющие состояние.
- Constant - константы, такие как #!eof, #!nul и др. Можно добавить так же #t и #f.

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

Вот что будет, если добавить в группу Define ключевое слово pavel:


Заключение


Мы рассмотрели богатый возможностями и гибконастраиваемый инструмент для разработки на языке программирования Scheme в среде Eclipse. Данный инструмент позволяет редактировать исходный код, взаимодействовать с широким набором интепретаторов, в том числе встроенными и удаленными. Если сравнивать возможности данного плагина с инструментарием для разработки на Scheme, например, под Emacs, то они вполне сопоставимы (за исключением отсутствия таких мелочей, как, замена ключевого слова lambda на символ греческой буквы лямбды). Любой программист, который не хочет осваивать Emacs может использовать Eclipse для знакомства с функциональным программированием или языком Scheme, а также для решения задач и упражнений из SICP. Так же возможно кого-нибудь заинтересует тема разработки на Scheme под JVM с использованием Kawa.

Кое что осталось вне рассмотрения, например вид Kawa ScratchPad и его использование при написании на Scheme скриптов для Eclipse. Надеюсь, что у меня еще будет возможность поделиться этим материалом с вами - читателями блога.

Хочется сказать спасибо автору SchemeScript - Dominique Boucher, который бесплатно разрабатывает эту утилиту. Стоит отметить, что плагин SchemeScript является проектом с открытым исходным кодом и любой программист может помочь в его развитии, добавив возможности, которые ему необходимы.

З.Ы. Возникла мысль, что ту же Scheme трудно изучать, потому что не интересно - нет интересных задач. Обычные математические какие-то вычисления неинтересны, а даже для простой работы с графикой, например, нужна платформа и библиотеки. Интереснее изучать язык, прилагая его к какой-нибудь предметной области. Так вот, SchemeScript + Kawa + Eclipse могут использоваться в качестве платформы для обучения. Посмотрите примеры из SchemeScript (в jar-файле каталог examples), там есть довольно интересные.

Как обычно вы можете оставить свои комментарии или задать вопросы Суровому челябинскому программисту.

Понравилось сообщение - подпишитесь на блог или читайте меня в twitter

13 комментариев:

Виктор Аленьков комментирует...

Спасибо за статью!

Вот бы ещё пример смешанного использования Scheme+Java, коли уж он компилируется в байт-код. :-[ и преимущества такого подхода (если они есть) - я как-то не думаю, что он язык "сам в себе" и только исполняться на яве может...

Pavel Samolisov комментирует...

Спасибо за отзыв.
Я обязательно попробую что-нибудь написать по поводу Kawa, но позже.

Beholder комментирует...

По нынешним временам уже интереснее Clojure, чем Scheme.

Alex Ott комментирует...

2Виктор: для лиспа на яве лучше смотреть на clojure ;-)

2Pavel: для SLIME есть kawa плагин, который позволяет комфортной с ней работать. Ну и Paredit, это конечно must use для работы с лисповым кодом в емаксе

Pavel Samolisov комментирует...

@Alex В сторону Clojure тоже посмотреть интересно, особенно благодаря вашей статье в ПФП. Вообще в мире много интересного, из функциональных языков под мэйнстрим-платформы это прежде всего Clojure и F#.

Та же Kawa мэйнстримом скорее всего никогда не будет, однако попробовать использовать простоту Scheme и мощь JVM можно. Как минимум Just for fun.

Pavel Samolisov комментирует...

Надо будет попробовать посмотреть, что вкусного есть в Paredit и может выслать Доминику патчик.

Dominique Boucher комментирует...

@Alex the latest version of SchemeScript has some support for Clojure syntax. And you can configure the external interpreter to point to the Clojure interpreter. I did that myself.

Also, SchemeScript emulates most of Paredit's S-expression manipulation commands.

@Pavel I will try to port SchemeScript to Eclipse Helios in the upcoming week. Stay tuned.

Dominique Boucher комментирует...

@Pavel Thanks for this excellent article on SchemeScript!

Alex Ott комментирует...

2Dominique: Ok, I'm glad to know that SchemeScript supports both Paredit and Clojure... I played with SchemeScript right after you first announcement, but I never used Eclipse much. We used Kawa and Bigloo to write projects in Scheme on JVM, but always used Emacs ;-)

2Pavel: You can also look onto SISC project (if it alive) - It provides pretty good integration with Java

Alex Ott комментирует...

2Pavel: да, забыл написать - я скоро выложу на своем сайте обновленную версию статьи про Clojure, с описанием функциональности версии 1.2

Pavel Samolisov комментирует...

@Alex вот это - хорошая новость. Обязательно почитаю.

Andrew ``Bass'' Shcheglov комментирует...

Спасибо, Павел.

Теперь можно брать "SICP" (http://ru.wikipedia.org/wiki/Структура_и_интерпретация_компьютерных_программ) и идти по примерам и упражнениям прямо из Eclipse.

Pavel Samolisov комментирует...
Этот комментарий был удален автором.

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!