Есть ли у Пролога условие и перезапустить систему, как Common Lisp?
Common Lisp позволяет обрабатывать исключения через условия и перезапускается. Грубо говоря, когда функция выдает исключение, "ловушка" может решить, как / должен ли "метатель" действовать. Предлагает ли Prolog подобную систему? Если нет, то можно ли построить поверх существующих предикатов обход и проверку стека вызовов?
4 ответа
Стандарт Prolog ISO/IEC предоставляет только очень элементарный механизм обработки исключений и ошибок, который - более или менее - сравним с тем, что предлагает Java, и далек от богатого механизма Common Lisp, но есть еще некоторые моменты, на которые стоит обратить внимание. В частности, помимо фактического механизма сигнализации и обработки, многие системы предоставляют механизм, аналогичный unwind-protect
, То есть способ гарантировать, что цель будет выполнена, даже при наличии других необработанных сигналов.
ISO бросок /1, поймать /3
Исключение повышается / выбрасывается с throw(Term)
, Первая копия Term
создан с copy_term/2
давайте назовем это Termcopy
а затем эта новая копия используется для поиска соответствующего catch(Goal, Pattern, Handler)
чей второй аргумент объединяется с Termcopy
, когда Handler
выполнено, все объединения вызваны Goal
отменены Так что для Handler
для доступа к заменам, присутствующим при throw/1
выполнен. И нет возможности продолжить в том месте, где throw/1
был выполнен.
Ошибки встроенных предикатов сигнализируются выполнением throw(error(Error_term, Imp_def))
где Error_term
соответствует одному из классов ошибок ISO и Imp_def
может предоставлять дополнительную информацию, определяемую реализацией (например, исходный файл, номер строки и т. д.).
Существует много случаев, когда локальная обработка ошибки может принести большую пользу, но многие разработчики считают ее слишком сложной для реализации.
Дополнительные усилия, направленные на то, чтобы процессор Prolog обрабатывал каждую ошибку локально, весьма значительны и намного больше, чем в Common Lisp или других языках программирования. Это связано с самой природой объединения в Прологе. Локальная обработка ошибки потребует отмены объединений, выполняемых во время выполнения встроенного: таким образом, у разработчика есть две возможности реализовать это:
- создать "точку выбора" во время вызова встроенного предиката, это повлечет за собой много дополнительных затрат, как для создания этой точки выбора, так и для "следования" последующих привязок
- Пройдите вручную каждый встроенный предикат и в каждом конкретном случае решайте, как обрабатывать ошибки - хотя это наиболее эффективно с точки зрения накладных расходов во время выполнения, это также самый дорогой и подверженный ошибкам подход.
Подобные сложности вызваны использованием регистров WAM во встроенных модулях. Опять же, у вас есть выбор между медленной системой или системой со значительными накладными расходами на реализацию.
exception_handler /3
Многие системы, однако, предоставляют более совершенные механизмы, но лишь немногие предлагают их программисту последовательно. IF/ Пролог обеспечивает exception_handler/3
который имеет те же аргументы, что и catch/3
но обрабатывает ошибку или исключение локально:
[user]?- catch ((arg(a,f(1),_); Z=ok), ошибка (type_error(_,_),_), ошибка). нет [пользователь]?- обработчик исключений ((arg(a,f(1),_); Z=ok), ошибка (type_error(_,_),_), ошибка). Z = хорошо да
setup_call_cleanup/3
Эта встроенная предлагается довольно много систем. Это очень похоже на unwind-protect
но требует некоторой дополнительной сложности из-за механизма возврата Пролога. Смотрите его текущее определение.
Все эти механизмы должны быть предоставлены разработчиком системы, они не могут быть построены поверх ISO Prolog.
Вы можете использовать гипотетические рассуждения, чтобы реализовать то, что вы хотите. Допустим, система Prolog, которая допускает гипотетические рассуждения, поддерживает следующее правило вывода:
G, A |- B
----------- (Right ->)
G |- A -> B
Есть некоторые системы Prolog, которые поддерживают это, например лямбда Prolog. Теперь вы можете использовать гипотетические рассуждения для реализации, например, restart/2 и signal_condition/3. Предположим, что гипотетические рассуждения выполняются через (-:)/2, тогда мы могли бы иметь:
restart(Goal,Handler) :-
(handler(Handler) -: Goal).
signal_condition(Condition, Restart) :-
handler(Handler), call(Handler,Condition,Restart), !.
signal_condition(Condition, _) :-
throw(Condition).
Решение будет не зря пересекать трассировку всего стека, а напрямую запросить обработчик. Но возникает вопрос, нужен ли мне специальный пролог или я могу сам делать гипотетические рассуждения. В первом приближении (-:) / 2 может быть реализовано следующим образом:
(Clause -: Goal) :- assume(Clause), Goal, retire(Clause).
assume(Clause) :- asserta(Clause).
assume(Clause) :- once(retact(Clause)).
retire(Clause) :- once(retract(Clause)).
retire(Clause) :- asserta(Clause).
Но вышеперечисленное не будет работать правильно, если Цель выдаст сокращение или исключение. Таким образом, лучшее решение, доступное, например, в Jekejeke Minlog 0.6:
(Clause -: Goal) :- compile(Clause, Ref), assume_ref(Ref), Goal, retire_ref(Ref).
assume_ref(Ref) :- sys_atomic((recorda(Ref), sys_unbind(erase(Ref)))).
retire_ref(Ref) :- sys_atomic((erase(Ref), sys_unbind(recorda(Ref)))).
Предикат sys_unbind/1 планирует цель отмены в списке привязок. Это соответствует отменить / 1 от SICStus. Список переплетов устойчив к порезам. Sys_atomic/1 гарантирует, что цель отмены всегда запланирована, даже если во время выполнения возникает внешний сигнал, такой как, например, выданный конечным пользователем прерывание. Это соответствует тому, как, например, обрабатывается первый аргумент setup_call_cleanup/3.
Преимущество использования ссылок на предложения здесь состоит в том, что это предложение компилируется только один раз, даже если происходит обратное отслеживание между целью и продолжением после (-:)/2. Но в противном случае решение, скорее всего, будет медленнее, чем поставить цель на трассировку стека с помощью его вызова. Но можно представить дальнейшие усовершенствования системы Prolog, например (-:)/2, в качестве примитивной и подходящей техники компиляции.
до свидания
Пролог ISO определяет эти предикаты:
throw/1
который бросает исключение. Аргумент - исключение, которое будет брошено (любой термин)catch/3
который выполняет цель и ловит определенные исключения, и в этом случае он выполняет обработчик исключений. Первый аргумент - это цель, которую нужно вызвать, второй аргумент - шаблон исключения (если исключение выдаетсяthrow/1
объединяет с этим шаблоном цель обработчика (), и третий аргумент - цель обработчика.
Пример использования:
test:-
catch(my_goal, my_exception(Args), (write(exception(Args)), nl)).
my_goal:-
throw(my_exception(test)).
Относительно вашей заметки: "Если нет, то можно ли построить поверх существующих предикатов ходьбу и проверку стека вызовов?" я не думаю, что есть общий способ сделать это. Возможно, посмотрите документацию системы пролога, которую вы используете, чтобы узнать, есть ли какой-нибудь способ пройти через стек.
Как ложно упомянуто в его ответе, ISO Prolog не позволяет этого. Однако некоторые эксперименты показывают, что SWI-Prolog предоставил механизм, на котором могут строиться условия и перезапуски. Далее следует очень грубое доказательство концепции.
"Ловец" вызывает restart/2
назвать цель и предоставить предикат для выбора среди доступных перезапусков в случае возникновения условия. "Метатель" вызывает signal_condition/2
, Первый аргумент - условие для поднятия. Второй аргумент будет связан с выбранным перезапуском. Если перезапуск не выбран, условие становится исключением.
restart(Goal, _) :- % signal condition finds this predicate in the call stack
call(Goal).
signal_condition(Condition, Restart) :-
prolog_current_frame(Frame),
prolog_frame_attribute(Frame, parent, Parent),
signal_handler(Parent, Condition, Restart).
signal_handler(Frame, Condition, Restart) :-
( prolog_frame_attribute(Frame, goal, restart(_, Handler)),
call(Handler, Condition, Restart)
-> true
; prolog_frame_attribute(Frame, parent, Parent)
-> signal_handler(Parent, Condition, Restart)
; throw(Condition) % reached top of call stack
).