Интерактивные программы

Наиболее распространенный случай использования побочного эффекта – интерактивный цикл. С терминала читается некоторая команда, выполняются соответствующие действия и затем читается следующая команда, В обычных языках программирования интерактивный цикл чаще всего реализуется с помощью цикла while. Программа 12.4 описывает общую структуру подобных программ. В данной программе введенная команда повторяется путем вывода на экран.

echo ¬read (X), echo (X).

echo(X) ¬end_of_ftle(X),

echo(X) ¬write (X), nl, read(Y),!, echo(Y).

Программа 12.4. Основной интерактивный цикл.

 

Цикл чтение-ответ запускается с помощью цели echo. Основу программы составляет отношение echo(X), где Х – терм, который следует вывести на экран. Программа использует предикат end_of_fille (X), который выполнен, если X – символ конца файла. Конретный вид этого символа зависит от операционной системы. Если обнаружен конец файла, то цикл завершается. В противном случае терм выводится на экран и считывается новый терм.

Заметим, что чтение терма и проверка терма выполняются раздельно. Это необходимо для того, чтобы избежать потери терма, так как терм не может быть прочитан повторно. С такой же ситуацией мы встретились в программе 12.2 при обработке литер. Литера сначала читалась, а уж потом отдельно обрабатывалась.

Программа 12.4 является итерационной и детерминированной. Если система включает в себя оптимизацию остатка рекурсии, то программа может эффективно выполняться, всегда используя один и тот же небольшой объем памяти.

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

Первое решение, которое следует принять при создании редактора строк на Прологе, – выбор представления файла. Необходимо иметь доступ к каждой строке файла и к позиции курсора, соответствующей текущей позиции редактируемого файла. Мы используем структуру file (Before, After), где Before – список строк перед курсором; After – список строк, расположенных после курсора. Курсор может размещаться только в конце некоторой строки. Строки, расположенные перед курсором, размещены в обратном порядке, что облегчает доступ к строкам, ближайшим к курсору. В основном цикле с клавиатуры вводится команда, с помощью которой строится новая версия файла. Редактор записан в виде программы 12.5.

 

edit ¬edit(file([ ],[ ])).

edit (File) ¬

write_prompt, read(Command), edit (File, Command).

edit (File, exit) ¬!.

edit (File, Command) ¬

apply (Command, File, Fiiel),!, edit(FiIel).

edit (File, Command) ¬

writeln (Command, ‘is not applicable'l),

!,

edit(FiIe).

apply(up, file([X | Xs], Ys),file(Xs, [X | Ys])). apply(up(N),file(Xs, Ys),file(XsI, Ysl)¬

N >0,up(N, Xs, Ys, Xsl, Ysl).

apply(down, file(Xs,[Y | Ys]),fi1e([Y [ Xs],Ys)),

apply(insert(Line),file(Xs, Ys),file(Xs,[Line | Ys]}).

apply (delete, file(Xs,[Y [ Ys],file(Xs, Ys)),

apply(print, file([X | Xs],Ys),file([X | Xs],Ys))¬

write(X),nl.

apply(print(*),file(Xs, Ys),file(Xs,Ys))¬

reverse(Xs, Xs1), write_file(Xs1), write_file(Ys).

up(N,[ ],Ys,[ ],Ys).

up(0,Xs,Ys,Xs,Ys).

up(N,[X | Xs],Ys, Xsl, Ysl)¬

N>0,N1 is N- l,up(Nl, Xs,[X | Ys],Xs1,Ysl).

Write_filе([Х | Xs])¬

write(X), nl, write_file(Xs). write, file([ ]).

write prompt ¬write(‘»'), nl.

Программа 12.5. Редактор строк.

 

Процесс редактирования начинается обращением к процедуре edit, которая использует цель file([ ], [ ]) для задания пустого файла в качестве начального значения редактируемого файла. Интерактивный цикл начинается с помощью процедуры edit(File). Решение цели edit_prompt приводит к выводу подсказки на экран, далее читается и выполняется команда. При выполнении используется основной предикат – edit(File, Command). Этот предикат применяет команду Command к файлу File. Применение команды осуществляется в процессе решения цели apply (Command, File, File1), где File1 – новая версия файла, полученная после применения команды. Редактирование продолжается путем обращения к процедуре edit/1 с аргументом File1. Третье предложение процедуры edit/2 используется в тех случаях, когда команда неприменима, т. е. в тех случаях, когда цель apply не выполнена. В этих случаях на экран выводится соответствующее сообщение и редактирование продолжается. Процесс радактирования завершается по команде exit, анализируемой в отдельном предложении процедуры edit/2.

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

арр1у(up,filе([Х | Xs],Ys), file(Xs,[X, Ys])).

указано, что перемещение курсора вверх состоит в перемещении строки, находящейся непосредственно перед курсором, на место строки, находящейся непосредственно за курсором. Команда не выполнится, если курсор находится в начале файла. Команда перемещения курсора вниз аналогична команде перемещения курсора вверх и также описана в программе 12.5.

Перемещение курсора вверх на N строк, а не на одну строку производится с помощью вспомогательного предиката ир15, изменяющего позицию курсора в файле. Правильность выполнения данного предиката легко усматривается из его определения. Заметим, что в процедуре apply перед обращением к процедуре up проверяется, допустимы ли аргументы процедуры up, т. е. является ли число строк положительным.В самом предикате up учитывается то, что число строк, на которое требуется переместить курсор, может быть больше числа строк в файле. В этом случае выполнение команды приводит к установке курсора в начало файла. Задача перемещения курсора на N строк вниз рассматривается в упражнениях.

Другие команды программы 12.5 вставляют и удаляют строки. Команда вставки insert (Line) содержит один аргумент, а именно вставляемую строку. Команда удаления выполняется непосредственно. Она неприменима, если курсор находится в конце файла. В редакторе имеются также команды печати строки, находящейся под курсором, – команда print и команда печати всего файла – print (*).

Команды редактора – взаимоисключающие. Для любой команды редактора применимо лишь одно правило процедуры apply. Это указывается с помощью отсечения во втором предложении процедуры edit/2. После выполнения цели apply путь вычисления однозначно определен. Такой способ указания детерминизма несколько отличается от описанного в разд. 11.1, где отсечения требуется применять непосредственно к самим фактам процедуры apply. Различие между этими двумя подходами носит скорее косметический характер.

Можно усовершенствовать редактор, связав с каждой командой собственное сообщение об ошибке. Предположим, например, что при попытке переместиться вверх из начала файла желательно получать более содержательное сообщение, чем «команда неприменима». Это можно сделать за счет усложнения предложения процедуры apply, относящегося к перемещению в файле вверх.

Перейдем от редактора к оболочкам.Оболочка получает команды с терминала и выполняет их. В качестве примера рассмотрим оболочку, решающую цели на Прологе. Она описана в программе 12.6.

 

shell ¬

shell_prompt, read(Goal), shell(Goal). shell(exit)¬ !

shell (Goal)¬

ground (Goal),!, shell_solve_ground(Goal), shell.

shell(Goal) ¬

shell_solve(Goal), shell.

shell_solve(Goal) ¬

Goal, write(Goal), nl, fail. shell_solve(Goal) ¬

write ('Решений (больше) нет'), nl.

sheiLsolve_ground(Goal) ¬

Goal,!, write ('Да'), nl.

shell_solve_ground (Goal)¬

write('Heт’), nl.

shelLprompt ¬ wnte(‘Cледующая команда?').

Программа 12.6. Интерактивная оболочка.

 

Процесс работы начинается с помощью решения цели shell. Начало работы подобно началу редактирования. С помощью процедуры shell_prompt выводится подсказка, потом читается цель и предпринимается попытка решить эту цель с помощью обращения к процедуре shell (Goal). Решение основных целей, дающее ответ да/нет, отличается от решения неосновных целей, дающего в качестве ответа цель, в которую подставлены соответствующие значения. Эти два случая рассматриваются соответственно в процедурах shell_solve_ground и shell_solve. Оболочка прекращает работу при вводе цели exit.

И shell_solve_ground, и shell_solve используют доступность метапеременных для обращения к решаемой цели. Успешность или безуспешность решения цели определяют вид выдаваемого сообщения. Эти два предиката являются простейшими примерами метаинтерпретаторов - предмета обсуждения гл. 19.

Процедура shell_solve содержит интересную конструкцию «решение-вывод-fail», полезную при выявлении всех решений цели путем принудительного возврата. Для того чтобы избежать безуспешных вычислений, добавлено еще одно предложение. Оно выполняется, когда исчерпаны все решения цели. Любопытно отметить, что невозможно непосредственно получить все решения целей, не прибегая к некоторой форме побочного эффекта. Причина этого явления разъясняется дальше в лекции, посвященной программированию второго порядка.

На основе данной оболочки может быть создано средство хранения протокола сеанса работы с Прологом. Такая система описана в программе 12.7. Эта новая оболочка начинает работать с вызова цели log, которая обращается к основному интерактивному предикату shell (Flag), аргумент Flag вначале равен константе log. Переключатель Flag принимает одно из двух значений log или nolog; это значение определяет, следует ли результат текущей работы заносить в протокол.

Данная программа является усовершенствованием программы 12.6. Основное отличие заключается в добавлении в главные предикаты дополнительного аргумента, значение которого определяет, включен ли режим хранения. Добавлены две дополнительные команды – log и поlog для включения и выключения режима хранения.

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

 

log ¬shell(log).

shell(Flag) ¬

shell_prompt, shell_read (Goal, Flag),shell(Goal,Flag)

shell (exit, Flag)¬

!, close_logging_file.

shell (nolog. Flag)¬

!, shell (nolog).

shell(log. Flag)¬

!, shell(log).

shell (Goal, Flag) ¬

ground(Goal),!, shell_solve_ground (Goal, Flag),shell(Flag).

shell(Goal,Flag)¬

shell_solve(Goal, Flag), shell (Flag).

shell_solve(Goal, Flag)¬-

Goal, shell_write(Goal,Flag), nl, fail.

shell_solve(Goal) ¬

shell_write ('Решений (больше) нет', Flag), nl.

shell_solve_ground (Goal, Flag) ¬

Goal,!, shell, write ('Да', Flag), nl. shell_solve_ground (Goal, Flag) ¬

shell_write(‘Heт', Flag), nl.

shelLprompt ¬ write('Cлeдyющaя команда ?').

shelLread (X, log) ¬read (X),

file_write(['Следующая команда ?',Х], 'prolog,log'). shelLread (X, nolog) ¬ read(X).

shelLwrite (X,nolog) ¬ write(X).

shelLwrite (X, log) ¬ write(X),file_write([X],prolog.log').

ffle„write(X,File)¬

telling(Old), tell(File). writeln(X),nl,tell(O1d).

close logging_file¬ tell('prolog.log'), told.

Программа 12.7. Реализация средства хранения протокола сеанса.

 

протокола. Поэтому обращение к предикату read имевшееся в программе 12.6, заменено на обращение к процедуре shell_read, а обращение к write заменено на обращение к shell_write.

Определение предиката shell_write описывает наши действия:

shell_write (X, nolog) ¬ write (X).

shellwrite (X, log) ¬write(X), file_write([X],’prolog.log').

Если текущее значение переключателя – nolog, то результат просто выводится на экран. Если значение – log, то дополнительная копия результата помещается в файл prolog.log. Предикат file_write(X,File) записывает строку Х в файл File.

В программе 12.7 – только два системозависимых предиката file_write и close_ logging_file. Они зависят от дополнительных системных предикатов, обрабатывающих файлы. Их определение использует примитивы Edinburgh-Пролога tell. told и telling, описанные в приложении Б. Еще одно соглашение, принятое в тексте программы – результирующий протокол записывается в файл prolog.log.