Как мога да предам предикат като параметър за друг предикат в Prolog?

Имам тези 3 предиката:

times(X, Y):-
    Result is X * Y.
minus(X, Y):-
    Result is X - Y.
plus(X, Y):-
    Result is X + Y.

и искам да предам например times(2,2) в plus(X, Y) по този начин plus(times(2,2), minus(X, Y)).


person HowTheF    schedule 19.10.2014    source източник
comment
моля, прочетете за metacalls   -  person CapelliC    schedule 19.10.2014
comment
Въз основа на написаното от @CapelliC по-горе, в SWI-prolog можете да използвате call, за да извикате съществуващ предикат, например променлива, съдържаща този предикат, и да го предадете на списък с аргументи. Много полезно за създаване на генерични продукти на Prolog! Можете да използвате call(Operator, ListOfArguments) или call(Operator, Arg1, Arg2), за да създавате функции, които правят неща, връщат резултати, но не знаете или не ви интересува да знаете какво точно се случва, стига резултатът да е от определен тип, например +(1,Output),call(Arithmetic(Input1,Input2,Output). Изпълнете някаква функция/3, която връща число, добавете 1 към резултата.   -  person G_V    schedule 30.01.2018


Отговори (3)


Връзката между заглавието на вашия въпрос и текста на въпроса ви е неясна за мен и мисля, че @false вероятно е прав, че тук има по-фундаментално недоразумение относно Prolog. Не знам дали това наистина отговаря на вашите нужди или не, но алтернативата тук е да напишете свой собствен оценител.

eval(times(X,Y), Result) :-
    eval(X, XResult),
    eval(Y, YResult),
    Result is XResult * YResult.
eval(minus(X,Y), Result) :-
    eval(X, XResult),
    eval(Y, YResult),
    Result is XResult - YResult.
eval(plus(X,Y), Result) :-
    eval(X, XResult),
    eval(Y, YResult),
    Result is XResult + YResult.

Рекурсивните извиквания към eval/2 в тялото на всяко от тези правила са необходими за обработка на случаи като plus(times(2,2), minus(X, Y)). След това ви трябва правило за числата:

eval(Num, Num) :- number(Num).

Това работи чудесно за случаи като този:

?- eval(plus(times(2,2), minus(7,1)), Result).
Result = 10.

Не ви помага в случаи като този:

?- eval(plus(times(2,2), minus(X,Y)), Result).
ERROR: Out of local stack

Разбира се, ще работи, ако установим свързвания за X и Y, преди да стигнем до там, но ако искате да генерира възможни решения за X и Y, нямате късмет, ще трябва да използвате clpfd. Причината за тази любопитна грешка, ако проследите, е защото number(X), когато X е необвързано, е невярно, така че всъщност генерира нови клаузи, включващи времена, минус и плюс структури и ги опитва, което не е това, което искате в оценител.

Редактиране: прилагане на printterm/1.

Предикатът eval/2 ви показва как да извършите рекурсивно дървообхождане. Принципът е същият при създаването на красив принтер. Много съм мързелив, така че само ще го скицирам, вие ще трябва да попълните подробностите сами.

printterm(T) :- format_term(T, Formatted), write(Formatted), nl.

format_term(plus(X,Y), Formatted) :- 
  format_term(X, XFormatted),
  format_term(Y, YFormatted),
  format(atom(Formatted), '(~a + ~a)', [XFormatted, YFormatted]).

% other format_term clauses here for other arithmetic expressions

format_term(X, X) :- number(X).

Надявам се това да помогне!

person Daniel Lyons    schedule 19.10.2014
comment
Thx, имате idé за предикат printterm/1, който би бил за команда като тази ?- printterm( plus(minus(8,2), times(4,-3))). Разпечатайте: ((8 - 2) + (4 * -3)) - person HowTheF; 23.10.2014

Първо, трябва да разберете какво всъщност описват предикатите на Prolog: Те не са функции, а по-скоро отношения между стойности. Така че, ако искате да имате предикат за добавяне, това трябва да е предикат с три аргумента: plus(A, B, Sum). Така в Prolog резултатите не се показват безплатно, както в много други езици.

Вместо

plus(X, Y):-
   Result is X + Y.

трябва да пишеш

plus(X, Y, Result) :-
   Result is X + Y.

Това, което искате след това, е да предадете междинните стойности по-нататък. В езиците, които поддържат функции, това е много лесна задача. Но в Prolog функционалните символи не се интерпретират. Така че или кодирате всичко в отношения, или по друг начин прилагате своя собствена версия на (is)/2. За начинаещи по-скоро се придържайте към първия вариант. По този начин, вместо

..., plus(times(2,2), minus(X, Y)) ...

сега пиши

..., times(2, 2, R), plus(R, minus(X, Y), S), ...

като S е крайният резултат.

Обърнете внимание, че ако кодирате изрази директно в отношения, трябва да въведете междинни променливи като R по-горе.

Толкова ясно, че (директната) релационна нотация е по-малко елегантна за цел като тази. За много прецизни области Prolog предлага и изрази, по-специално (is)/2 и след това в много по-общата настройка на library(clpfd).

Въпреки това, като начинаещ е добра идея първо да свикнете с релационната нотация. И може би е още по-добра идея първо да изучите аритметиката на наследника. Преди да използвате library(clpfd) и преди да използвате (is)/2.

person false    schedule 19.10.2014

Алтернатива на решението на Даниел е да разглеждате термини като plus(times(2,2), minus(5,3)) като изразни обекти, поддържащи набор от операции, като "оценява" или "диференцира". Това ви позволява да пишете цели като:

| ?- plus(times(2,2), minus(5,3))::evaluate(Result).
Result = 6
yes

Предимството е, че правилата за всички операции, които се прилагат за конкретен израз, напр. times/2 са спретнато капсулирани в съответния обект. т.е. вместо да групират предикатите чрез операция, те се групират чрез (елементарен) израз. Пример в тази насока, но в контекста на извличане и опростяване на изрази, може да бъде намерен тук.

person Paulo Moura    schedule 20.10.2014
comment
Мамка му, symdiff е страхотно! ?- (x**2+2*x+1)::diff(D), D::simplify(Sim). -› D = 2*x**1*1+2*1, Sim = 2*x+2. Възможността да декларирате _ * _ като обект в Logtalk е наистина невероятна! - person Daniel Lyons; 22.10.2014
comment
Благодаря. Параметричните обекти са много гъвкави. Вижте напр. link.springer.com/chapter/10.1007%2F978-3- 642-20589-7_4 - person Paulo Moura; 22.10.2014