¿Qué uso tiene if / 3?


El predicado if_/3 parece ser bastante popular entre los pocos contribuyentes principales en la parte 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 comparado con, por ejemplo, la construcción clásica if-then-else de Prolog if -> then ; else.

La mayoría de los enlaces que he encontrado directamente use este predicado y proporcione poca explicación de por qué se usa, que un no experto en Prolog podría entender fácilmente.

Author: Community, 2016-10-03

2 answers

En el código Prolog antiguo, 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, por supuesto, también ocurre en otros lugares.

Lo trágico es lo siguiente: {[13]]}

  • Para una lista instanciada, la coincidencia de patrones puede distinguir la primera cláusula de las dos restantes, pero no puede distinguir la segunda de la última porque ambos tienen '.'(_, _) como el funtor primario y aridad de su primer argumento.
  • Las condiciones en las que se aplican las dos últimas cláusulas son obviamente mutuamente excluyentes.
  • Así, cuando todo es conocido, queremos obtener un predicado eficiente, determinista que no deja puntos de elección, e idealmente ni siquiera crea puntos de elección.
  • Sin embargo, siempre y cuando no todo pueda ser seguro determinados, queremos beneficiarnos de backtrackingpara ver todas las soluciones, por lo que no podemos permitirnos comprometernos con ninguna de las cláusulas.

En resumen, las construcciones existentes y las características del lenguaje se quedan cortas de alguna manera para expresar un patrón que a menudo ocurre en la práctica. Por lo tanto, durante décadas, parecía necesario transigir. Y usted puede hacer una conjetura bastante buena en qué dirección los" compromisos " van generalmente en la comunidad de Prolog: Casi invariablemente, la corrección es sacrificada por la eficiencia en caso de duda. Después de todo, ¿a quién le importan los resultados correctos siempre y cuando sus programas sean rápidos, verdad? Por lo tanto, hasta la invención de if_/3, esto fue frecuentemente erróneamente escrito como:

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

El error en este supuesto, es que, cuando los elementos son no suficientemente instancia, entonces incorrectamente comprometerse a una rama aunque tanto alternativas son lógicamente posibles. Por esta razón, el uso de if-then-else casi siempre es declarativamente incorrecto, y se interpone masivamente 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 Prolog puros.


Usando if_/3, puedes escribir esto como:

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

Y mantienen todos los aspectos deseables. Esto es:

  • determinista si todo puede ser seguro decidió
  • eficiente en que ni siquiera crea puntos de elección
  • completeen el sentido de que nunca se confirma incorrectamente a una rama en particular.

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

Buenas noticias para todos: En muchos casos, condition es de la forma (=)/2, o (#=)/2, y el primero incluso barcos con library(reif) gratis.

Para más información, ver Indexación dif/2 ¡por Ulrich Neumerkel y Stefan Kral!

 16
Author: mat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-15 15:22:24

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 que, si la lista estuviera ordenada en p/2, tendríamos p(X, false).

Usaré la biblioteca reif como aquí. Por lo tanto, 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 list 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 list actual; el segundo argumento es lo que sucede cuando true; el tercer argumento es lo que sucede cuando false.

Con esto, puedo dividir una lista en as iniciales y un resto:

?- 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 puedes deshacerte de los 0 iniciales para 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 haber sido escrito mucho más simple:{[26]]}

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 tuvieras 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í, asumimos que =/3 de hecho siempre tendrá éxito sin puntos de elección y el T siempre será true o false.

Y, si no tuviéramos =/3 tampoco, escribirías: {[26]]}

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 es no es ideal. Pero ahora al menos puedes ver por ti mismo, en un solo lugar, lo que realmente está pasando.

PD: Por favor, lea el código y la interacción de nivel superior cuidadosamente.

 9
Author: halfer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-06 20:46:56