Е.И. Большакова, Н.В. Груздева - Основы программирования на языке Лисп (1110798), страница 17
Текст из файла (страница 17)
Поскольку в рядеслучаев требуется посимвольный ввод обрабатываемых данных, в Лиспедля этого встроена функция ввода read-char. Обращение к ней имеетвид (read-char), а её значение зависит от диалекта языка Лисп.85В MuLisp значением функции read-char является внешнее имясимвола, считанного из входного потока, например:при вводе символа 1:(read-char) => \1при вводе символа (:(read-char) => \(при вводе символа А:(read-char) => Апри вводе символа a:(read-char) => \аВ Common Lisp введённый символ преобразуется в константусимвольного типа (этот диалект поддерживает несколько типов данных,включая символы, строки, списки).
Символ-константа изображается какпоследовательность трёх знаков: #, \ и сам введённый символ, например:при вводе символа (:(read-char) => #\(при вводе символа А:(read-char) => #\Aпри вводе символа a:(read-char) => #\aCимвольные константы не являются атомами, тем не менее они могутсравниваться на равенство встроенными функциями eq и eql (длякорректной работы eql оба её аргумента должны быть одного типа).Рассмотрим теперь способы ввода и дальнейшей обработки текста,содержащего особые символы (в частности, математических формул, вкоторых есть круглые скобки). Для этого можно использовать либофункцию посимвольного ввода read-char, либо функцию read,позволяющую ввести весь текст за одно обращение к ней.В Common Lisp для такого ввода функцией read необходимозаключить вводимый текст в кавычки, сделав из него строку, например:"(a+d)/c-12".
Введённую строку символов можно преобразовать всписок символов с помощью встроенной функции coerce. Эта функцияимеет два вычисляемых аргумента, значением первого должно бытьпреобразуемое выражение, а значением второго – тип, к которому онодолжно быть преобразовано (в частности: list, string). В нашем случаефункцию coerce имеет смысл использовать для преобразования строки всписок символов-констант, и для обратного преобразования такого спискав строку:(coerce "(a+d)/c-12" 'list)=> (#\( #\a #\+ #\d #\) #\/ #\c #\- #\1 #\2)(coerce '(#\a #\* #\( #\b #\+ #\c #\) ) 'string))=> "a*(b+c)"После преобразования функцией coerce строки в список символовэтот список уже может быть проанализирован с помощью известныхвстроенных функций, включая функцию eq.86В диалекте MuLisp для единоразового ввода текста с помощьюфункции read необходимо заключить его либо в кавычки, либо в символывертикальной черты.
При этом введённый текст будет сохранен вовнутреннем представлении как символьный атом. Для дальнейшейобработки этого атома может быть использована встроенная в MuLispфункция unpack (распаковка) с одним вычисляемым аргументом.Функция unpack преобразует значение своего аргумента –символьный или числовой атом – в список односимвольных атомов,например:(unpack 'ASD123) => (A S D \1 \2 \3)(unpack '|(a*b)+С|) => ( \( \a * \b \) + С)(unpack '458) => (\4 \5 \8)Обратное преобразование выполняет встроенная в MuLisp функцияpack (упаковка) с одним вычисляемым аргументом.Функция pack преобразует заданный список атомов (не обязательносимвольных и односимвольных) в один символьный атом путёмконкатенации входящих в их запись символов, например:(pack '(AS 127 D 34)) => AS127D34(pack '(127 AS D 34)) => |127ASD34|(pack '(as -12.56 df 34) => AS-12.56DF34(pack '(a |sd| 56 \8t)) => |Asd568T|Заметим, что в исходном списке могут быть числовые атомы, приконкатенации функцией pack берутся символы их записи.Часто в процессе работы программы (в частности, при её отладке)требуется производить выдачу каких-либо сообщений и промежуточныхрезультатов вычислений.
Для этого служат встроенные функции выводалисповских выражений print, prin1, princ и terpri.Функция print с обращением (print e) вычисляет свойаргумент – выражение e и выполняет вывод полученного значения вместес переводом строки. В качестве результата PRINT возвращает значениесвоего аргумента, например:(print (cdr '(a (s) d)) => ((S) D)кроме того, как побочный результат функции этот список-результат будетвыведен на печать или в выходной поток.В диалекте MuLisp print сначала выводит значение выражения eс текущей позиции строки, после чего осуществляет перевод строки. ВCommonLisp сначала происходит перевод строки, а затем выводитсязначение аргумента e и символ пробела.87Функция prin1 от одного аргумента (prin1 e) вычисляет свойаргумент e и выводит полученный результат с текущей позиции строки.Значением функции является вычисленное значение e.
Отличие этойфункции от print - вывод выполняется без перевода строки и бездополнительных пробелов.Функция princ отличается от функции prin1 только тем, что длявывода символьных атомов использует не внешние имена атомов, а ихвнутренние имена, например,(princ '(|as| 23 \(sd\))) => (|as| 23 |(SD)|)но выведено будет выражение (as 23 (SD)).Для перевода строки при выводе служит функция terpri безаргументов, её значением является атом NIL.Ещё одна встроенная функция load служит для загрузки ивычисления файла с лисп-программой.
Вызов функции имеет вид:(load "имя_файла_с_расширением") .Указанный в вызове текстовый файл должен состоять изпоследовательности вычислимых лисповских выражений (форм). Привыполнении вызова load из этого файла последовательно считываются ивычисляются лисповские выражения. Если в процессе ввода и вычисленияочередного выражения обнаружена ошибка, то выводится диагностическоесообщение о ней, а ввод и вычисление следующих выражений из файлапрекращается. В случае успешного вычисления всех выражений иззаданного программного файла выводится сообщение об этом, арезультатом работы функции load является атом Т.Функция load обычно применяется для загрузки определенийновых функций, в частности, после загрузки в системе MuLisp файлаcommon.lsp появляется возможность использования ряда встроенныхфункций диалекта CommonLisp.4.3.Общий вид функций cond, defun, letДо сих пор мы записывали лисповские особые функции cond,defun, let в их простой, хотя и часто используемой форме,применяемой при программировании строго функциональных программ, вкоторых исключены побочные эффекты вычислений.
При использованиифункций с побочными эффектами (в частности, при использованииописанных выше встроенных функций ввода/вывода и функций работы сосписком свойств атомов) может оказаться полезной запись управляющихконструкций cond, defun, let в их более общей форме.88Общий вид условного выражения:(cond (p1 e11 e12 … e1m1)(p2 e21 e22 … e2m2)…(pn en1 en2 … en mn))mi ≥ 0 , n ≥ 1Вычисление условного выражения общего вида выполняется последующим правилам:I. последовательно вычисляются условия p1, p2, … pn ветвейвыражения до тех пор, пока не встретится выражение pi, значениекоторого отлично от NIL;II.
последовательно вычисляются выражения-формы ei1 ei2 … eimiсоответствующей ветви, и значение последнего выражения eimiвозвращается в качестве значения функции cond;III. если все условия pi имеют значение NIL, то значением условноговыражения становится NIL.Как и ранее, ветвь условного выражения может иметь вид (pi), когдаmi=0. Тогда если значение pi ≠ NIL, значением условного выраженияcond становится значение pi.В случае, когда pi ≠ NIL и mi ≥ 2, т.е. ветвь cond содержит болееодного выражения ei, эти выражения вычисляются последовательно, ирезультатом cond служит значение последнего из них eimi.
Такимобразом, в дальнейших вычислениях может быть использовано толькозначение последнего выражения, и при строго функциональномпрограммировании случай mi ≥ 2 обычно не возникает, т.к. значенияпредшествующих eimi выражений пропадают.Использование более одного выражения ei на ветви cond имеетсмысл тогда, когда вычисление предшествующих eimi выражений даётпобочные эффекты, как при вызове функций ввода и вывода, изменениисписка свойств атома, а также определении новой функции с помощьюdefun. К примеру:(cond ((< X 5)(print "Значение х меньше пяти") X)((= X 10)(print "Значение х равно 10") X)(T(print "Значение х больше пяти, но не 10")X))Значением этого условного выражения всегда будет значение переменнойX, но при этом на печать будет выведена одна из трёх строк, в зависимостиот текущего значения X.Во многих диалектах, в том числе в MuLisp и Common Lisp,допускается запись нескольких выражений в теле функции при еёопределении.
Общий вид обращения к defun:89(defun f (x1 … xk) e1 … en) , n ≥ 0Если тело функции пусто, то значение функции равно NIL. В ином случаепоследовательно вычисляются входящие в тело функции выражения ei, ив качестве значения функции берётся значение последнего выражения en.Опять же, включение в тело функции более одного выражения имеетсмысл тогда, когда их вычисление имеет побочный эффект, например:(defun Plus5()(princ "Введите целое число: ")(put 'N 'Value (+ 5 (read)))(terpri)(princ "Число, большее на 5: ")(print (get 'N 'Value)) )При вызове этой функции без аргументов (Plus5) у пользователязапрашивается число, и после его ввода оно увеличивается на 5 исохраняется в списке свойств атома N.
После вывода поясняющей строкииз списка свойств достаётся и выводится увеличенное число, и оностановится итоговым значением функции Plus5.Аналогично, в теле лямбда-выражения, являющегося, по сути,определением безымянной функции, допускается произвольное числовыражений:(lambda (m1 m2 … mk) e1 e2 … en)n≥0, k≥0 .При соответствующем лямбда-вызове((lambda (m1 m2 … mk) e1 e2 … en) p1 p2 … pk)после связывания формальных параметров m1 m2 … mk происходитпоследовательное вычисление выражений-форм е1 … en, и в качестверезультата лямбда-вызова берётся значение последней формы en . Опятьже, в случае пустого тела функции её значение равно NIL.В лисповской конструкции let, позволяющей вводить локальныепеременные и являющейся другой формой записи лямбда-вызова, такжедопускается любое количество вычисляемых выражений. Общий вид let:(let ((m1 p1)(m2 p2)…(mk pk)) e1 e2 … en)n≥0, k≥1Эта форма эквивалентна лямбда-вызову:((lambda (m1 m2 … mk) e1 e2 … en) p1 p2 … pk)и позволяет завести и связать локальные переменные m1 m2 … mk созначениями p1 p2 … pk на время вычисления форм e1 e2 … ek.Значением обращения к let является значение последней формы en (илиNIL в случае n=0).
Отметим, что сначала вычисляются значенияфактических параметров (форм p1 p2 … pn), а затем происходит90одновременное связывание полученных значений с формальнымипараметрами m1 m2 … mk.Опишем ещё одну полезную конструкцию let*, которая аналогичнаlet, но вычисление форм pi и связывание локальных переменных miпроисходит строго последовательно. Сначала вычисляется p1, иполученное значение связывается с m1, затем вычисляется p2, и егозначение связывается с m2, и т.д. На момент вычисления формы piпеременные m1 … mi-1 уже связаны и их значения могут бытьиспользованы для вычисления выражения pi. Приведём примериспользования этой конструкции:(let* ((X 12) (Y (* X 2)) (Z (+ X Y)) )(print X) (print Y) (print Z) )В результате вычисления этой формы будут выведены последовательночисла 12, 24, 36, а число 36 станет значением формы.Подчеркнём, что использование конструкций defun и let в ихобщей форме предполагает некоторые побочные эффекты при вычислениивходящих в них выражений e1 e2 … en, возможно за исключениемпоследнего en, служащего для определения результирующего значения.