Перейти на главную страничку сайта (список статей, файлы для скачивания)
Оглавление: Основные свойства и возможности Hello, World! Особенности синтаксиса Контексты и модульное программирование Сборка исполняемых файлов
LISP — это целое семейство языков, наиболее известными и признанными из которых являются Common LISP и Scheme. Название LISP расшифровывается как "LISt Processor" (обработчик списков). Создатель семейства языков LISP — американец John McCarthy, автор термина "искусственный интеллект". История LISP ведёт отсчёт примерно с 1958 года.
Первые области применения LISP были связаны с символьной обработкой данных и проблемами искусственного интеллекта. Сегодня LISP является универсальным языком программирования и используется в самых разных проектах. Одно из направлений использования языков LISP — это применение в качестве встроенного языка в ряде очень популярных прикладных программ (AutoCAD, Emacs, GIMP).
Полезные русскоязычные ресурсы по LISP:
NewLISP — это свободный (лицензия GPL) скриптовый язык общего назначения для задач, обычно решающихся с помощью интерпретируемых языков, таких, как программирование для Интернет, системное администрирование, обработка текста, соединение различных программ и модулей в единое приложение и т.д. NewLISP реализует быстрый и компактный интерпретатор языка LISP.
Автор newLISP — Lutz Mueller. Интерпретатор newLISP написан на чистом Си, с применением только стандартной библиотеки. Такая реализация сделала newLISP кросс-платформенным языком, доступным для Linux, Windows, MacOS X, OS/2. Дистрибутив для Windows имеет размер порядка 1 Мб и представляет из себя удобный инсталлятор.
Основные возможности newLISP:
NewLISP достаточно сильно отличается от Common LISP или Scheme, и разработчики NewLISP не советуют изучать книги по этим языкам в целях освоения newLISP — newLISP гораздо более современен и многие проблемы решает совсем по-другому. Подробную документацию и введение в newLISP (англ.) можно найти здесь, здесь и здесь. Также есть подробный сборник образцов кодирования, примеры и форум (англ.). Дополнительные модули, не вошедшие в поставку, можно найти здесь.
После установки newLISP из дистрибутива под Windows вы получаете интерактивный интерпретатор newlisp.exe, подробную документацию, установленную локально, и графическую IDE (среду разработки) newLISP-GS. Можно прописать путь к интерпретатору в переменную среды PATH. Ша-банг для скриптов newLISP под Linux может выглядеть так:
#!/usr/bin/env newlisp
Интерпретатор newlisp.exe является интерактивным, и после его запуска вы получите консоль с приглашением для ввода команд на языке LISP. Для ввода многострочных скриптов используйте обрамляющие теги [cmd] и [/cmd]. Для завершения сессии работы интерпретатора введите команду (exit).
Кроме того, есть интерактивная IDE (среда разработки) newLISP-GS, для вызова которой при установке под Windows создаётся специальный ярлык. В newLISP-GS вы получаете графический редактор с подсветкой синтаксиса и встроенной консолью вывода. Можно также запустить newLISP-GS и командой наподобие следующей:
newlisp "C:\Program Files\newlisp\newlisp-edit.lsp"
Скрипт "Hello, world!" будет выглядеть следующим образом:
(print "Hello, world!") (exit) ; нужно для завершения работы интерпретатора
Сохраните текст скрипта в файле с расширением .lsp и запустите его командой наподобие следующей:
newlisp test.lsp
Чуть более развёрнутый пример — удаление всех пустых файлов (нулевого размера) в текущем каталоге:
(dolist (zfile (directory ".")) (if (= (file-info zfile 0) 0) ; если нулевой размер (if-not (directory? zfile) (begin ; если это не каталог (delete-file zfile) (println "deleted: " zfile) ))) ) (exit)
Эта часть статьи рассчитана на тех, кто уже немного знаком с каким-либо языком программирования, и здесь мы выборочно приведём лишь некоторые особенности, которые позволяют познакомиться с уникальным лицом языка. Приводимые ниже фрагменты кода пробуйте в интерактивной среде newLISP-GS из поставки. Вообще, здесь и далее в статье мы приведём только некоторые характерные приёмы программирования, которые позволят почувствовать "вкус" языка, но не превратят статью в руководство или справочник.
Как следует из самого названия языка, список в LISP — это основной тип данных. Элементами списка могут быть константы, имена переменных и функций (в терминологии LISP — символы) и другие списки:
(1 2 "abc" var (34 "z"))
Немного о вызове функций. То, что математик записал бы как f1(x, f2(y, z)), на LISP будет выглядеть как (f1 x (f2 y z)). Математическая запись 1 + 2 + 3 на LISP будет выглядеть как (+ 1 2 3). Таким образом, "+" — это обычная функция, которая получает аргументы. Например, setq — функция присваивания, которая также возвращает значение последнего аргумента. Пример присвоит переменной b значение 5 (квадратный корень из 25) и вернёт это значение:
(setq b (sqrt 25))
Апостроф — специальный символ, предотвращающий вычисление выражения, в результате чего получается обычный список. Апостроф — это просто короткая запись для функции quote. Следующие записи эквивалентны (переменной b присваивается список, а не квадратный корень из 25):
(setq b (quote (sqrt 25))) (setq b '(sqrt 25))
Получение элемента списка по индексу, синтаксис "implicit indexing":
(setq b '(("first" 1) ("second" 2) ("third" 3) ("fourth" 4))) (b 1) ; выведет ("second" 2), первый элемент имеет индекс 0 (1 b) ; срез, с первого элемента до конца: (("second" 2) ("third" 3) ("fourth" 4)) (1 2 b) ; срез, два элемента, начиная с первого: (("second" 2) ("third" 3))
Ассоциативный список — это список списков, в которых первый элемент используется как ключ для поиска:
((ключ1 значение1) (ключ2 значение2) ...)
Для поиска в ассоциативных списках используются функции assoc и lookup. Можно иметь несколько элементов с одинаковым ключом, а порядок следования записей всегда фиксирован. Пример поиска в ассоциативных списках:
(setq b '(("first" 1) ("second" 2) ("third" 3) ("fourth" 4))) (assoc "second" b) ; выведет весь подсписок ("second" 2) (lookup "second" b) ; выведет последний элемент подсписка - 2 (lookup "second" b 0) ; выведет указанный элемент подсписка - "second" (lookup "second" b 1) ; выведет указанный элемент подсписка - 2
Цикл со счётчиком на пять итераций:
(dotimes (i 5) (println i " привет") ; печать строки )
Одно из основополагающих свойств LISP — использование кода как данных. Выражение синтаксически является списком, может обрабатываться как список — храниться в переменных, подвергаться преобразованиям, передаваться как параметр функции.
Объявление и вызов функции:
(define (summa arg1 arg2) (+ arg1 arg2)) (summa 25 2) ; выведет 27
В некотором смысле объявленная выше пользовательская функция "summa" — это всего лишь переменная, которой присвоен в качестве значения специальный список, представляющий из себя безымянную функцию (lambda). Пример, равносильный предыдущему, который демонстрирует сказанное:
(setq summa '(lambda (arg1 arg2) (+ arg1 arg2))) (summa 25 2) ; выведет 27
Функцию можно вызвать и "по имени", передав ей список аргументов, с помощью функции apply:
(define (summa arg1 arg2) (+ arg1 arg2)) (setq oops '(summa +)) ; список oops, состоящий из двух функций - "summa" и "+" ; вызов функции summa тремя способами: (summa 1 2) (apply summa '(1 2)) (apply summa (list 1 2)) ; вызов первой функции из списка функций "oops": (apply (oops 0) '(1 2 3)) ; выведет 3, последний аргумент отбрасывается ; вызов второй функции из списка функций "oops": (apply (oops 1) (list 1 2 3)) ; выведет 6
В отличие от обычных функций, аргументы которых автоматически вычисляются перед вызовом, при вызове макро-функций используется так называемый ленивый (lazy) порядок вычисления, при котором аргументы не вычисляются:
(define (test1 arg) arg) ; просто возвращаем переданный аргумент (test1 (+ 1 2)) ; выведет 3 (define-macro (test2 arg) arg) (test2 (+ 1 2)) ; выведет (+ 1 2)
Вышеприведённая функция test2 является по сути аналогом встроенной функции quote. В макро-функции можно вычислить переданное выражение принудительно, с помощью функции eval:
(define-macro (test3 arg) (eval arg)) ; вычисляем переданное выражение (test3 (+ 1 2)) ; выведет 3
В newLISP в качестве значения "ложь" используется символ nil. Все остальные значения в newLISP трактуются как "истинные", что обозначается символом true (в т.ч. 0, пустая строка и пустой список). Функция условного ветвления выглядит так:
(setq x 0 y 1) (if (> x y) (println "больше") (= x y) (println "равно") (println "другое") )
Или так:
(setq x 0 y 1) (cond ((> x y) (println "больше")) ((= x y) (println "равно")) ((println "другое")) )
Если по условию необходимо выполнить больше одного выражения, их нужно "обернуть" функцией begin:
(setq x 2 y 1) (if (> x y) (begin (print "больше") (println "!")) (= x y) (println "равно") (println "другое") )
В варианте с cond функция begin не нужна, т.к. есть дополнительные скобки.
Функция выбора case:
(define (translate n) (case n (1 "one") (2 "two") (3 "three") (true "Can't translate this"))) (println (translate 1))
В некотором смысле в LISP существует всего два типа данных — атомы (числа и символы) и списки, с помощью которых можно построить сколь угодно сложные структуры данных. Благодаря его специфическому синтаксису LISP часто называют мета-языком: одним из рекомендуемых методов программирования на LISP является создание собственного языка с синтаксисом, удобным для решения поставленной задачи, а затем использование этого языка для получения конечного результата. Например, вот так можно определить собственный оператор условного ветвления:
(define-macro (new-if condition res-true res-false) (let (t (eval condition)) ; временный символ t (будет равен nil или true) (and t (eval res-true)) (or t (eval res-false)) ) ) ;=========================================== (new-if (= 1 1) (println "yes") (println "no")) ; yes (new-if (= 1 2) (println "yes") (println "no")) ; no
В идентификаторах можно свободно использовать кириллицу:
(define (квадрат arg) (* arg arg)) ;=========================================== (define (гипотенуза leg-1 leg-2) (sqrt (+ (квадрат leg-1) (квадрат leg-2)) ) ) ;=========================================== (setq катет-1 12 катет-2 5) (setq результат (гипотенуза катет-1 катет-2)) (println результат)
Пример выше вычислит гипотенузу прямоугольного треугольника с катетами 12 и 5.
Понятие контекста (context) в newLISP обеспечивает именованный "контейнер" для символов. У символов в различных контекстах может быть одинаковое название без конфликта имён. Если вы явно не создавали и/или не переключали контексты, вы работаете в контексте MAIN.
Функция context может использоваться для создания контекста, переключения между контекстами, получения значения существующего символа в контексте, выяснения имени текущего контекста, создания нового символа в контексте.
Полное имя символа состоит из названия контекста и собственно имени символа, разделённых двоеточием. Вывести все символы указанного контекста можно следующим образом (с использованием специальной функции symbols):
(dolist (s (symbols MAIN)) (println s)) ; можно использовать (eval s) для получения значений
Можно автоматически создать контекст при определении функции:
(define (D:greeting) ; будет создан контекст "D" (println "greetings from context " (context))) (D:greeting)
Обычное использование контекста — создание словаря (упорядоченной последовательности пар "ключ/значение"):
; пустой словарь (формально - пустая функция, название которой ; совпадает с названием её контекста, т.е. функция "по умолчанию"): (define Dict:Dict) ; синтаксис работы со словарём: ; (Dict key value) - установить значение ключу ; (Dict key) – получить значение ключа ; (Dict key nil) – удалить ключ (Dict "one" "first") (Dict "two" "second") (println (Dict "one")) ; выведет first ; другой способ инициализации словаря: (Dict '(("one" 1) ("two" 2))) (println (Dict "two")) ; выведет 2 (exit)
Созданный контекст (словарь) можно сохранить в файл:
(save "C:/Temp/test.txt" 'Dict)
Сохранённый контекст (словарь) можно загрузить из файла, чтобы им воспользоваться:
(load "C:/Temp/test.txt") (context 'Dict) (println (Dict "two")) (exit)
Контексты используются как контейнеры для программных модулей, потому что они обеспечивают лексически отделённые пространства имен. Обычно модули, включённые в поставку newLISP, определяют контекст, который содержит ряд функций, решающих задачи в определенной области. Загрузка модуля и переключение в его контекст производятся так же, как показано в примере выше, с помощью функции load, а затем context. После этого можно вызывать функции модуля точно так же, как "обычные" функции. Можно не переключаться в загруженный контекст, а вызывать его функции с полными именами (контекст:функция).
В FOOP (Functional Object-Oriented Programming) методы и свойства класса, т.е. функции и символы, которые относятся к каждому объекту этого класса, хранятся в контексте. Каждый объект хранится как список, где первый элемент — символ, идентифицирующий класс объекта, а оставшиеся элементы — значения, которые описывают свойства объекта. Таким образом, все объекты класса используют те же самые свойства, но у этих свойств могут быть различные значения.
Например, момент времени представляется как объект Time. Объект содержит два значения: число секунд, прошедших с начала 1970 года, и смещение часового пояса, в минутах к западу от Гринвича. Таким образом, список, который представляет типичный объект Time, выглядит следующим образом:
(Time 1219568914 0) ; примерно 10 утра 24 августа 2008, где-нибудь в Англии
Код, необходимый для встраивания этой объектной модели, начинается с создания нового контекста. Функция "по умолчанию" работает как конструктор:
(define (Time:Time (t (date-value)) (zone 0)) (list Time t zone)) ; список с именем класса и двумя свойствами ; значения по умолчанию - текущее время и нулевое смещение ; создание объекта класса: (set 'christmas-day (Time (date-value 2008 12 25)))
Затем можно определить метод класса:
(define (Time:show t) (date (t 1) (t 2)))
И вызвать этот метод:
(println (Time:show christmas-day))
Для сборки исполняемых файлов предназначен модуль link.lsp, расположенный в каталоге util установки newLISP.
Приведём пример сборки исполняемого файла под Windows. Создайте файл test.lsp следующего содержания:
; выводим первый переданный аргумент, преобразуя его к верхнему регистру: (println (upper-case (main-args 1))) (exit)
Скопируйте файлы link.lsp и newlisp.exe в тот же каталог, где расположен только что созданный test.lsp. Запустите командный интерпретатор и перейдите в этот каталог. Далее выполните команду:
newlisp link.lsp
В результате вы получите сессию интерпретатора newLISP в контексте link.lsp. Наберите LISP-команду:
(link "newlisp.exe" "uppercase.exe" "test.lsp")
В результате вы получите исполняемую программу uppercase.exe размером чуть больше 230 Кб, которая выводит свой первый аргумент, преобразуя его к верхнему регистру. Запустите её примерно так:
uppercase "abcdefgh"
Вы должны получить вывод "ABCDEFGH".
Людоговский Александр, 18.02.2009г.
Перейти на главную страничку сайта (список статей, файлы для скачивания)
© 2007 http://www.script-coding.com При любом использовании материалов сайта обязательна ссылка на него как на источник информации, а также сохранение целостности и авторства материалов.