sentencia learn how equal ejemplos if-statement prolog logical-purity

if-statement - learn - prolog sentencia if



¿Qué uso tiene if_/3? (2)

En el código de Prolog a la antigua, el siguiente patrón surge con bastante frecuencia:

predicate([], ...). predicate([L|Ls], ...) :- condition(L), then(Ls, ...). predicate([L|Ls], ...) :- /+ condition(L), else(Ls, ...).

Estoy usando listas aquí como un ejemplo donde esto ocurre (ver por ejemplo include/3 , exclude/3 etc.), aunque el patrón de curso también ocurre en otro lugar.

Lo trágico es lo siguiente:

  • Para una lista instanciada , la coincidencia de patrones puede distinguir la primera cláusula de las otras dos, pero no puede distinguir la segunda de la última porque ambas tienen ''.''(_, _) Como el functor primario y arity de su primer argumento .
  • Las condiciones en las que se aplican las dos últimas cláusulas son obviamente mutuamente excluyentes .
  • Por lo tanto, cuando todo se conoce, queremos obtener un predicado determinista , eficiente que no deje puntos de elección , e idealmente ni siquiera crea puntos de elección.
  • Sin embargo , siempre que no todo se pueda determinar de manera segura, queremos beneficiarnos del retroceso para ver todas las soluciones , por lo que no podemos permitirnos el compromiso con ninguna de las cláusulas.

En resumen, los constructos y las características del lenguaje existentes no se corresponden de ninguna manera con la expresión de un patrón que a menudo ocurre en la práctica. Por lo tanto, durante décadas, pareció necesario comprometerse . Y se puede adivinar muy bien en qué dirección suelen ir los "compromisos" en la comunidad de Prolog: casi invariablemente, la corrección se sacrifica por la eficiencia en caso de duda. Después de todo, ¿a quién le importan los resultados correctos siempre que sus programas sean rápidos, verdad? Por lo tanto, hasta la invención de if_/3 , con frecuencia se escribía erróneamente como:

predicate([], ...). predicate([L|Ls], ...) :- ( condition(L) -> then(Ls, ...). ; else(Ls, ...). )

El error en esto es, por supuesto, que cuando los elementos no están suficientemente instanciados, entonces esto puede comprometerse incorrectamente en una rama, aunque ambas alternativas son lógicamente posibles. Por esta razón, usar if-then-else casi siempre es declarativamente incorrecto, y se encuentra de manera masiva en el camino de los enfoques de depuración declarativa debido a su violación de las propiedades más elementales que esperamos de los programas puros de Prolog.

Usando if_/3 , puedes escribir esto como:

predicate([], ...). predicate([L|Ls], ...) :- if_(condition(L), then(Ls, ...), else(Ls, ...)).

y retiene todos los aspectos deseables . Esto es:

  • determinista si todo se puede decidir con seguridad
  • eficiente en el sentido de que ni siquiera crea puntos de elección
  • Completa porque nunca te comprometes de forma incorrecta con una rama en particular.

El precio de esto es bastante asequible: como mencionó Boris en los comentarios, debe implementar una reificación . Ahora tengo algo de experiencia con esto y me pareció bastante fácil con algo de práctica.

Buenas noticias para todos : en muchos casos, la condition es de la forma (=)/2 , o (#=)/2 , y la primera incluso se envía con la library(reif) de forma gratuita .

Para obtener más información, consulte arxiv.org/abs/1607.01590 de Ulrich Neumerkel y Stefan Kral.

El predicado if_/3 parece ser bastante popular entre los pocos contribuyentes principales en la parte de Prolog de Stack Overflow.

Este predicado se implementa como tal, cortesía de @false:

if_(If_1, Then_0, Else_0) :- call(If_1, T), ( T == true -> call(Then_0) ; T == false -> call(Else_0) ; nonvar(T) -> throw(error(type_error(boolean,T),_)) ; /* var(T) */ throw(error(instantiation_error,_)) ).

Sin embargo, no he podido encontrar una explicación clara, simple y concisa de lo que hace este predicado, y qué uso tiene en comparación, por ejemplo, con la construcción clásica de if-then-else de Prolog if -> then ; else if -> then ; else

La mayoría de los enlaces que he encontrado usan directamente este predicado y proporcionan pocas explicaciones sobre por qué se usa, que un no experto en Prolog podría entender fácilmente.


Tratemos de resolver un problema simple usando if_/3 ; por ejemplo, intentaré particionar una lista (ordenada en un predicado p/2 ) en dos listas: un prefijo en el que, para cada elemento X , tenemos p(X, true) y un resto (en el cual, si la lista fue ordenada en p/2 , tendríamos p(X, false) .

reif la biblioteca reif como here . Entonces, aquí está el código completo de mi programa:

:- use_module(reif). pred_prefix(Pred_1, List, L_true, L_false) :- pred_prefix_aux(List, Pred_1, L_true, L_false). pred_prefix_aux([], _, [], []). pred_prefix_aux([X|Xs], Pred_1, True, False) :- if_( call(Pred_1, X), ( True = [X|True0], pred_prefix_aux(Xs, Pred_1, True0, False) ), ( True = [], False = [X|Xs] ) ).

El predicado pasado a este meta-predicado tomará dos argumentos: el primero es el elemento de lista actual, y el segundo será true o false . Idealmente, este predicado siempre tendrá éxito y no dejará atrás los puntos de elección.

En el primer argumento de if_/2 , el predicado se evalúa con el elemento de lista actual; el segundo argumento es qué sucede cuando es true ; el tercer argumento es lo que sucede cuando es false .

Con esto, puedo dividir una lista en liderazgo y descanso:

?- pred_prefix([X, B]>>(=(a, X, B)), [a,a,b], T, F). T = [a, a], F = [b]. ?- pred_prefix([X, B]>>(=(a, X, B)), [b,c,d], T, F). T = [], F = [b, c, d]. ?- pred_prefix([X, B]>>(=(a, X, B)), [b,a], T, F). T = [], F = [b, a]. ?- pred_prefix([X, B]>>(=(a, X, B)), List, T, F). List = T, T = F, F = [] ; List = T, T = [a], F = [] ; List = T, T = [a, a], F = [] ; List = T, T = [a, a, a], F = [] .

¿Cómo puede deshacerse de los 0 principales, por ejemplo?

?- pred_prefix([X, B]>>(=(0, X, B)), [0,0,1,2,0,3], _, F). F = [1, 2, 0, 3].

Por supuesto, esto podría haberse escrito mucho más simple:

drop_leading_zeros([], []). drop_leading_zeros([X|Xs], Rest) :- if_(=(0, X), drop_leading_zeros(Xs, Rest), [X|Xs] = Rest).

Aquí acabo de eliminar todos los argumentos innecesarios.

Si tuviera que hacer esto sin if_/3 , habría tenido que escribir:

drop_leading_zeros_a([], []). drop_leading_zeros_a([X|Xs], Rest) :- =(0, X, T), ( T == true -> drop_leading_zeros_a(Xs, Rest) ; T == false -> [X|Xs] = Rest ).

Aquí, suponemos que =/3 siempre tendrá éxito sin puntos de elección y la T siempre será true o false .

Y, si tampoco tuviéramos =/3 , escribirías:

drop_leading_zeros_full([], []). drop_leading_zeros_full([X|Xs], Rest) :- ( X == 0 -> T = true ; X /= 0 -> T = false ; T = true, X = 0 ; T = false, dif(0, X) ), ( T == true -> drop_leading_zeros_full(Xs, Rest) ; T == false -> [X|Xs] = Rest ).

que no es ideal Pero ahora al menos puedes ver por ti mismo, en un solo lugar, lo que está sucediendo realmente.

PD: Lea detenidamente el código y la interacción de nivel superior.