There is this paper:
William E. Byrd, Eric Holk, Daniel P. Friedman, 2012
miniKanren, Live and Untagged
Quine Generation via Relational Interpret
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).
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
.