Buscar..


Introducción

Muchos sistemas modernos de Prolog están en continuo desarrollo y han agregado nuevas características para abordar las deficiencias clásicas del lenguaje. Desafortunadamente, muchos libros de texto de Prolog e incluso cursos de enseñanza aún presentan solo el prólogo obsoleto. El objetivo de este tema es ilustrar cómo el Prolog moderno ha superado algunos de los problemas y la sintaxis bastante compleja que aparece en el Prolog antiguo y que aún se puede introducir.

CLP (FD) para aritmética de enteros

Tradicionalmente, Prolog realizaba aritmética usando los operadores is y =:= . Sin embargo, varios Prólogos actuales ofrecen CLP (FD) (Programación de lógica de restricción sobre dominios finitos) como una alternativa más limpia para la aritmética de enteros. CLP (FD) se basa en almacenar las restricciones que se aplican a un valor entero y combinarlas en la memoria.

CLP (FD) es una extensión en la mayoría de los Prologs que la admiten, por lo que debe cargarse explícitamente. Una vez que se carga, la sintaxis #= puede tomar el lugar de ambos is y =:= . Por ejemplo, en SWI-Prolog:

?- X is 2+2.
X = 4.

?- use_module(library(clpfd)).
?- X #= 2+2.
X = 4.

A diferencia de is , #= es capaz de resolver ecuaciones simples y unificar en ambas direcciones:

?- 4 is 2+X.
ERROR: is/2: Arguments are not sufficiently instantiated

?- 4 #= 2+X.
X = 2.

CLP (FD) proporciona su propia sintaxis de generador.

?- between(1,100,X).
X = 1;
X = 2;
X = 3...

?- X in 1..100.
X in 1..100.

Tenga en cuenta que el generador no se ejecuta realmente: solo se almacena la restricción de rango, listo para que las restricciones posteriores se combinen con él. El generador puede ser forzado a correr (y restricciones de fuerza bruta) usando el predicado de la label :

?- X in 1..100, label([X]).
X = 1;
X = 2;
X = 3..

El uso de CLP puede permitir una reducción inteligente de los casos de fuerza bruta. Por ejemplo, usando aritmética de enteros de estilo antiguo:

?- trace.
?- between(1,10,X), Y is X+5, Y>10.
...
Exit: (8) 6 is 1+5 ? creep
Call: (8) 6 > 10 ? creep
...
X = 6, Y = 11; ...

El prólogo aún recorre los valores del 1 al 5, aunque a partir de las condiciones dadas se puede demostrar matemáticamente que estos valores no pueden ser útiles. Utilizando CLP (FD):

?- X in 1..10, Y #= X+5, Y #> 10.
X is 6..10,
X+5 #= Y,
Y is 11..15.

CLP (FD) hace las cuentas inmediatamente y calcula los rangos disponibles. Agregar una label([Y]) hará que X pase por los valores útiles 6..10. En este ejemplo de juguete, esto no aumenta el rendimiento porque con un rango tan pequeño como 1-10, el procesamiento de álgebra toma tanto tiempo como el bucle; pero cuando se procesa un mayor rango de números, esto puede reducir de manera valiosa el tiempo de cálculo.

El soporte para CLP (FD) es variable entre Prologs. El mejor desarrollo reconocido de CLP (FD) se encuentra en SICStus Prolog, que es comercial y costoso. SWI-Prolog y otros Prologs abiertos a menudo tienen alguna implementación. Visual Prolog no incluye CLP (FD) en su biblioteca estándar, aunque hay bibliotecas de extensión disponibles.

Forall en lugar de bucles impulsados ​​por fallas

Algunos libros de texto de Prolog "clásicos" todavía utilizan la sintaxis de bucle impulsada por fallas, confusa y propensa a errores, donde se usa una construcción fail para forzar el retroceso para aplicar un objetivo a cada valor de un generador. Por ejemplo, para imprimir todos los números hasta un límite dado:

fdl(X) :- between(1,X,Y), print(Y), fail.
fdl(_).

La gran mayoría de los Prólogos modernos ya no requieren esta sintaxis, en lugar de proporcionar un predicado de orden superior para abordar esto.

nicer(X) :- forall(between(1,X,Y), print(Y)).

Esto no solo es mucho más fácil de leer, sino que si se utilizara un objetivo que pudiera fallar en lugar de la impresión , su fallo se detectaría y pasaría correctamente, mientras que los fallos de los objetivos en un bucle impulsado por fallas se confunden con el fallo forzado que impulsa el bucle.

Visual Prolog tiene un azúcar sintáctico personalizado para estos bucles, combinado con predicados de función (ver más abajo):

vploop(X) :- foreach Y = std::fromTo(1,X) do
                 console::write(X)
             end foreach.

Aunque esto parece un imperativo para el bucle, todavía sigue las reglas de Prolog: en particular, cada iteración del foreach es su propio alcance.

Predicados de estilo de función

Tradicionalmente, en Prolog, las "funciones" (con una salida y entradas enlazadas) se escribían como predicados regulares:

mangle(X,Y) :- Y is (X*5)+2.

Esto puede crear la dificultad de que si un predicado de estilo de función se llama varias veces, es necesario "encadenar" variables temporales.

multimangle(X,Y) :- mangle(X,A), mangle(A,B), mangle(B,Y).

En la mayoría de los Prólogos, es posible evitar esto escribiendo un operador de infijo alternativo para usar en lugar de is que expande expresiones que incluyen la función alternativa.

% Define the new infix operator
:- op(900, xfy, <-).

% Define our function in terms of the infix operator - note the cut to avoid
% the choice falling through
R <- mangle(X) :- R is (X*5)+2, !.

% To make the new operator compatible with is..
R <- X :-
    compound(X),            % If the input is a compound/function
    X =.. [OP, X2, X3],     % Deconstruct it
    R2 <- X2,               % Recurse to evaluate the arguments
    R3 <- X3,
    Expr =.. [OP, R2, R3],  % Rebuild a compound with the evaluated arguments
    R is Expr,              % And send it to is
    !.
R <- X :- R is X, !.        % If it's not a compound, just use is directly

Ahora podemos escribir:

multimangle(X,Y) :- X <- mangle(mangle(mangle(Y))).

Sin embargo, algunos Prólogos modernos van más allá y ofrecen una sintaxis personalizada para este tipo de predicado. Por ejemplo, en Visual Prolog:

mangle(X) = Y :- Y = ((X*5)+2).
multimangle(X,Y) :- Y = mangle(mangle(mangle(X))).

Tenga en cuenta que el operador <- y el predicado de estilo funcional de arriba aún se comportan como relaciones : es legal para ellos tener puntos de elección y realizar una unificación múltiple. En el primer ejemplo, evitamos esto utilizando cortes. En Visual Prolog, es normal usar la sintaxis funcional para las relaciones y los puntos de elección se crean de la manera normal, por ejemplo, el objetivo X = (std::fromTo(1,10))*10 correctamente con enlaces X = 10 , X = 20, X = 30, X = 40, etc.

Declaraciones de flujo / modo

Al programar en Prolog, no siempre es posible, o deseable, crear predicados que se unifiquen para cada combinación posible de parámetros. Por ejemplo, el predicado between(X,Y,Z) que expresa que Z es numéricamente entre X e Y. Se implementa fácilmente en los casos en que X, Y y Z están todos unidos (o Z está entre X e Y o no lo es), o donde X e Y están vinculados y Z está libre (Z se unifica con todos los números entre X e Y, o el predicado falla si Y <X); pero en otros casos, como donde X y Z están vinculados y Y es libre, potencialmente hay un número infinito de unificaciones. Aunque esto puede ser implementado, por lo general no lo sería.

La declaración de flujo o las declaraciones de modo permiten una descripción explícita de cómo se comportan los predicados cuando se llaman con diferentes combinaciones de parámetros enlazados. En el caso de between , la declaración sería:

%! between(+X,+Y,+Z) is semidet.
%! between(+X,+Y,-Z) is nondet. 

Cada línea especifica un patrón de llamada potencial para el predicado. Cada argumento está decorado con + para indicar los casos en los que está vinculado, o - para indicar los casos donde no lo está (también hay otras decoraciones disponibles para tipos más complejos, como tuplas o listas que pueden estar parcialmente enlazadas). La palabra clave after es el comportamiento del predicado en ese caso, y puede ser uno de estos:

  • det si el predicado siempre tiene éxito sin un punto de elección. Por ejemplo, add(+X,+Y,-Z) es det porque agregar dos números dados X e Y siempre tendrá exactamente una respuesta.
  • semidet si el predicado tiene éxito o falla, sin un punto de elección. Como arriba, between(+X,+Y,+Z) es semidet porque Z es entre X e Y o no lo es.
  • multi si el predicado siempre tiene éxito, pero puede tener puntos de elección (pero también puede que no). Por ejemplo, el factor(+X,-Y) sería multi porque un número siempre tiene al menos un factor, sí, pero puede tener más.
  • nondet si el predicado puede tener éxito con los puntos de elección, o fallar. Por ejemplo, between(+X,+Y,-Z) está nondet porque puede haber varias unificaciones posibles de Z a números entre X e Y, o si Y <X entonces no hay números entre ellos y el predicado falla.

Las declaraciones de flujo / modo también se pueden combinar con el etiquetado de argumentos para aclarar qué significan los términos, o con la escritura. Por ejemplo, between(+From:Int, +To:Int, +Mid:Int) is semidet .

En los Prólogos puros, las declaraciones de flujo y modo son opcionales y solo se usan para la generación de documentación, pero pueden ser extremadamente útiles para ayudar a los programadores a identificar la causa de los errores de creación de instancias.

En Mercury, las declaraciones de flujo y modo (y tipos) son obligatorias y son validadas por el compilador. La sintaxis utilizada es la anterior.

En Visual Prolog, las declaraciones y tipos de flujo y modo también son obligatorios y la sintaxis es diferente. La declaración anterior se escribiría como:

between : (int From, int To, int Mid) determ (i,i,i) nondeterm (i,i,o).

El significado es el mismo que el anterior, pero con las diferencias que:

  • Las declaraciones de flujo / modo están separadas de las declaraciones de tipo (ya que se supone que el flujo / modo para un solo predicado no variará con la sobrecarga de tipo);
  • i y o se utilizan para + y - y se comparan con los parámetros basados ​​en el orden;
  • Los términos utilizados son diferentes. det convierte procedure , semidet se convierte en determ y nondet convierte nondeterm ( multi sigue siendo multi ).


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow