问题
There is this paper:
William E. Byrd, Eric Holk, Daniel P. Friedman, 2012
miniKanren, Live and Untagged
Quine Generation via Relational Interpreters
http://webyrd.net/quines/quines.pdf
Which uses logic programming to find a Scheme Quine. The Scheme subset that is consider here does not only contain lambda abstraction and application, but also a little list processing by the following reduction, already translated to Prolog:
[quote,X] ~~> X
[] ~~> []
[cons,X,Y] ~~> [A|B], for X ~~> A and Y ~~> B
So the only symbols are quote, [] and cons, besides lembda for lambda abstraction and bound variables. And we would use Prolog lists for the Scheme lists. The goal is to find a Scheme programm Q via Prolog, so that we get Q ~~> Q, i.e. evaluates to itself.
There is one complication, which makes the endeavour non-trival, [lembda,X,Y] doesn't evaluate syntactically to itself, but is rather supposed to return an environment closure. So the evaluator would be unlike the Plotkin evaluator here.
Any Prolog solutions around? Merry X-Mas
回答1:
I'm using SWI Prolog with the occurs check turned on here (but dif/2
skips the occurs check anyway):
symbol(X) :- freeze(X, atom(X)).
symbols(X) :- symbol(X).
symbols([]).
symbols([H|T]) :-
symbols(H),
symbols(T).
% lookup(X, Env, Val).
%
% [quote-unbound(quote)] will be the empty environment
% when unbound(quote) is returned, this means that
% `quote` is unbound
lookup(X, [X-Val|_], Val).
lookup(X, [Y-_|Tail], Val) :-
dif(X, Y),
lookup(X, Tail, Val).
% to avoid name clashing with `eval`
%
% evil(Expr, Env, Val).
evil([quote, X], Env, X) :-
lookup(quote, Env, unbound(quote)),
symbols(X).
evil(Expr, Env, Val) :-
symbol(Expr),
lookup(Expr, Env, Val),
dif(Val, unbound(quote)).
evil([lambda, [X], Body], Env, closure(X, Body, Env)).
evil([list|Tail], Env, Val) :-
evil_list(Tail, Env, Val).
evil([E1, E2], Env, Val) :-
evil(E1, Env, closure(X, Body, Env1_Old)),
evil(E2, Env, Arg),
evil(Body, [X-Arg|Env1_Old], Val).
evil([cons, E1, E2], Env, Val) :-
evil(E1, Env, E1E),
evil(E2, Env, E2E),
Val = [E1E | E2E].
evil_list([], _, []).
evil_list([H|T], Env, [H2|T2]) :-
evil(H, Env, H2), evil_list(T, Env, T2).
% evaluate in the empty environment
evil(Expr, Val) :-
evil(Expr, [quote-unbound(quote)], Val).
Tests:
Find Scheme expressions that eval to (i love you)
-- this example has a history in miniKanren:
?- evil(X, [i, love, you]), print(X).
[quote,[i,love,you]]
X = [quote, [i, love, you]] ;
[list,[quote,i],[quote,love],[quote,you]]
X = [list, [quote, i], [quote, love], [quote, you]] ;
[list,[quote,i],[quote,love],[[lambda,[_3302],[quote,you]],[quote,_3198]]]
X = [list, [quote, i], [quote, love], [[lambda, [_3722], [quote|...]], [quote, _3758]]],
dif(_3722, quote),
freeze(_3758, atom(_3758)) ;
[list,[quote,i],[quote,love],[[lambda,[_3234],_3234],[quote,you]]]
X = [list, [quote, i], [quote, love], [[lambda, [_3572], _3572], [quote, you]]],
freeze(_3572, atom(_3572)) ;
In other words, the first 4 things it finds are:
(quote (i love you))
(list (quote i) (quote love) (quote you))
(list (quote i) (quote love) ((lambda (_A) (quote you)) (quote _B)))
; as long as _A != quote
(list (quote i) (quote love) ((lambda (_A) _A) (quote you)))
; as long as _A is a symbol
It looks like the Scheme semantics are correct. The language-lawyer type of constraints it places are pretty neat. Indeed, real Scheme will refuse
> (list (quote i) (quote love) ((lambda (quote) (quote you)) (quote _B)))
Exception: variable you is not bound
Type (debug) to enter the debugger.
but will accept
> (list (quote i) (quote love) ((lambda (quote) quote) (quote you)))
(i love you)
So how about quines?
?- evil(X, X).
<loops>
miniKanren uses BFS, so maybe that's why it produces results here. With DFS, this could work (assuming there are no bugs):
?- call_with_depth_limit(evil(X, X), n, R).
or
?- call_with_inference_limit(evil(X, X), m, R).
but SWI doesn't necessarily limit the recursion with call_with_depth_limit
.
回答2:
Here is a solution that uses a little blocking programming style. Its not using when/2, rather only freeze/2. There is one predicate expr/2 which checks whether something is a proper expression without any closure in it:
expr(X) :- freeze(X, expr2(X)).
expr2([X|Y]) :-
expr(X),
expr(Y).
expr2(quote).
expr2([]).
expr2(cons).
expr2(lambda).
expr2(symbol(_)).
And then there is a lookup predicate again using freeze/2,
to wait for an environment list.
lookup(S, E, R) :- freeze(E, lookup2(S, E, R)).
lookup2(S, [S-T|_], R) :-
unify_with_occurs_check(T, R).
lookup2(S, [T-_|E], R) :-
dif(S, T),
lookup(S, E, R).
And finally the evaluator, which is coded using DCG,
to limit the total number of cons and apply invokations:
eval([quote,X], _, X) --> [].
eval([], _, []) --> [].
eval([cons,X,Y], E, [A|B]) -->
step,
eval(X, E, A),
eval(Y, E, B).
eval([lambda,symbol(X),B], E, closure(X,B,E)) --> [].
eval([X,Y], E, R) -->
step,
eval(X, E, closure(Z,B,F)),
eval(Y, E, A),
eval(B, [Z-A|F], R).
eval(symbol(S), E, R) -->
{lookup(S, E, R)}.
step, [C] --> [D], {D > 0, C is D-1}.
The main predicate gradually increases the number of allowed
cons and apply invokations:
quine(Q, M, N) :-
expr(Q),
between(0, M, N),
eval(Q, [], P, [N], _),
unify_with_occurs_check(Q, P).
This query shows that 5 cons and apply invokations are enough to produce a Quine. Works in SICStus Prolog and Jekejeke Prolog. For SWI-Prolog need to use for example this unify/2 workaround:
?- dif(Q, []), quine(Q, 6, N).
Q = [[lambda, symbol(_Q), [cons, symbol(_Q), [cons, [cons,
[quote, quote], [cons, symbol(_Q), [quote, []]]], [quote,
[]]]]], [quote, [lambda, symbol(_Q), [cons, symbol(_Q), [cons,
[cons, [quote, quote], [cons, symbol(_Q), [quote, []]]],
[quote, []]]]]]],
N = 5
We can manually verify that it is indeed a non-trivial Quine:
?- Q = [[lambda, symbol(_Q), [cons, symbol(_Q), [cons, [cons,
[quote, quote], [cons, symbol(_Q), [quote, []]]], [quote,
[]]]]], [quote, [lambda, symbol(_Q), [cons, symbol(_Q), [cons,
[cons, [quote, quote], [cons, symbol(_Q), [quote, []]]],
[quote, []]]]]]], eval(Q, [], P, [5], _).
Q = [[lambda, symbol(_Q), [cons, symbol(_Q), [cons, [cons,
[quote, quote], [cons, symbol(_Q), [quote, []]]], [quote,
[]]]]], [quote, [lambda, symbol(_Q), [cons, symbol(_Q), [cons,
[cons, [quote, quote], [cons, symbol(_Q), [quote, []]]],
[quote, []]]]]]],
P = [[lambda, symbol(_Q), [cons, symbol(_Q), [cons, [cons,
[quote, quote], [cons, symbol(_Q), [quote, []]]], [quote,
[]]]]], [quote, [lambda, symbol(_Q), [cons, symbol(_Q), [cons,
[cons, [quote, quote], [cons, symbol(_Q), [quote, []]]],
[quote, []]]]]]]
来源:https://stackoverflow.com/questions/65446636/pure-prolog-scheme-quine