2

@false commented earlier:

Yes, you can implement a Turing machine without dif/2. But you cannot even implement intersection or similar predicates.

Suppose we do extend pure Prolog (Horn FOL + CWA + UNA) with call/N, dif/2, and (=)/3, to be used in if_/3, would there still be gaps in its expressiveness, i.e. things that are trivial to define in, say, Scheme, but are much harder to state in such extended (almost pure) Prolog?

In particular, does such a Prolog allow manipulating Prolog lists about as conveniently as Scheme allows manipulating Scheme lists?


Edit: Assume Scheme without mutation, macros, continuations, laziness, streams, numbers, strings, vectors or characters. Just symbols, booleans and lists (trees).

MWB
  • 11,740
  • 6
  • 46
  • 91
  • What about (is)/2? You pretty much need instantiation errors, nonvar/1 and ground/1 to guard against unintended uses. – false Dec 23 '20 at 16:04
  • 1
    @false I was thinking about Scheme without numbers, strings or characters. Just symbols. I'll edit to clarify. – MWB Dec 23 '20 at 16:12

3 Answers3

4

Just symbols and lists (trees).

You also need Scheme booleans #t and #f if you don't want to encode everything in pure lambda calculus. You're also excluding function values, which thankfully makes this answer simpler. Although you will have to allow the special case of top-level (define name (lambda ...)) forms. (Anything else, including expanded let expressions, can be defunctionalized.)

So, my claim is: No, there is no gap in expressiveness between this vague Scheme subset and pure Prolog as you defined it. My argument (it's not a proof) is constructive, by translating Scheme code for list intersection from this answer to Prolog.

Specifically, this:

(define intersect
  (lambda (set1 set2)
    (cond
      ((null? set1)(quote ()))
      ((member? (car set1) set2)
       (cons (car set1)
             (intersect (cdr set1) set2)))
      (else (intersect (cdr set1) set2)))))

becomes:

intersect(Set1, Set2, Result) :-
    cond([
        ['null?'(Set1), result([])],
        [cond_1(Set1, Set2), body_1(Set1, Set2)],
        [else, body_2(Set1, Set2)]], Result).

cond_1(Set1, Set2, Result) :-
    car(Set1, Car),
    'member?'(Car, Set2, Result).

body_1(Set1, Set2, Result) :-
    car(Set1, Car),
    cdr(Set1, Cdr),
    intersect(Cdr, Set2, PartialIntersection),
    cons(Car, PartialIntersection, Result).

body_2(Set1, Set2, Result) :-
    cdr(Set1, Cdr),
    intersect(Cdr, Set2, Result).

and this:

(define member?
  (lambda (a lat)
    (cond
      ((null? lat) #f)
      (else (or (equal? (car lat) a) 
                (member? a (cdr lat)))))))

becomes:

'member?'(A, Lat, Result) :-
    cond([
        ['null?'(Lat), result('#f')],
        [else, or([or_case_1(Lat, A),
                   or_case_2(Lat, A)])]], Result).

or_case_1(Lat, A, Result) :-
    car(Lat, Car),
    'equal?'(Car, A, Result).

or_case_2(Lat, A, Result) :-
    cdr(Lat, Cdr),
    'member?'(A, Cdr, Result).

Note that nested expressions need to be un-nested, and in all but the most trivial cases this is simplest by defining auxiliary Prolog predicates. This doesn't increase code size non-linearly.

These definitions use the following translations of standard Scheme constructs:

'equal?'(X, Y, '#t') :-
    =(X, Y, true).
'equal?'(X, Y, '#f') :-
    =(X, Y, false).

'null?'(Value, Result) :-
    'equal?'(Value, [], Result).

car([Car | _Cdr], Car).

cdr([_Car | Cdr], Cdr).

cons(Car, Cdr, [Car | Cdr]).

or([], '#f').
or([Goal | Goals], Result) :-
    if(Goal,
       Result = '#t',
       or(Goals, Result)).

cond([], _Result) :-
    throw(error(cond_without_else, _)).
cond([[Condition, Body] | OtherCases], Result) :-
    if(Condition,
       call(Body, Result),
       cond(OtherCases, Result)).

Some support stuff for getting a simple value out of a cond case body, and for the else case:

result(Result, Result).

else('#t').

And this is all the internally-impure, externally-pure Prolog support you need:

if(Goal, True, False) :-
    call(Goal, Truth),
    (   Truth == '#t' -> call(True)
    ;   Truth == '#f' -> call(False)
    ;   throw(error(type_or_instantiation_error(Truth), _)) ).

I called this if/3 instead of if_/3 because it's not exactly the "standard" if_/3: It expects the condition to evaluate to a Scheme truth value rather than true or false. Feel free to massage it into "standard" form. Edit: There are several "good enough" ways to define a (=)/3 that will work in the context of this answer, but to avoid further bikeshedding, just use the definition from https://stackoverflow.com/a/27358600/4391743.

Tests:

?- 'member?'(a, [x, y, a, c], Result).
Result = '#t' ;
false.

?- intersect([a, b, c, d], [c, d, e, f], Result).
Result = [c, d] ;
false.
Isabelle Newbie
  • 9,258
  • 1
  • 20
  • 32
  • 1
    That `(=)/3` isn't pure. – false Dec 27 '20 at 17:53
  • It's pure enough under the post's assumption that all Scheme values are ground. I meant to add that in the answer, I'll update it. In any case, replacing my `(=)/3` by your full definition from https://stackoverflow.com/a/27358600/4391743 makes the program behave the same. – Isabelle Newbie Dec 27 '20 at 17:57
  • No need for dif/2. An instantiation error instead would make it really safe enough. – false Dec 27 '20 at 22:34
  • 1
    *"It's pure enough under the post's assumption that all Scheme values are ground."* That's not the assumption. If you are translating a Scheme procedure into a Prolog predicate, it must work correctly (like Scheme) with ground "inputs", but it may be applied to non-ground ones (Why use (almost pure) Prolog otherwise?) The actual assumption is stated in the first paragraph. – MWB Dec 28 '20 at 06:08
  • *"You also need Scheme booleans"* Added, thanks. Although I think booleans aren't important. Other Lisps use `'T` and `()` to represent `#t` and `#f`. – MWB Dec 28 '20 at 07:36
  • *"it may be applied to non-ground ones"* It may, but you didn't specify the intended semantics in this case. Exploring Scheme-with-logical-variables would make this question too broad. – Isabelle Newbie Dec 28 '20 at 07:52
  • I've removed my own `(=)/3` in favor of a link to a full implementation. – Isabelle Newbie Dec 28 '20 at 09:09
  • As long as a program remains pure and monotonic, it can be used for producing explanations for, say, [unexpected failure](https://stackoverflow.com/a/30791637/772868). That is no bikeshedding! For, if you sink into the inpure, all what is left is solely procedural interpretations and their incarnation in tracers. – false Dec 29 '20 at 13:59
  • +100 for the effort, but I disagree with your interpretation of the Q. – MWB Jan 01 '21 at 10:21
  • You could have stated earlier that you disagree with my interpretation; you're still not saying what you're disagreeing with. The bounty note specifically mentioned that you would accept a translation of your Scheme fragment to Prolog. I didn't implement a translator, but I did give translations of all relevant Scheme constructs. – Isabelle Newbie Jan 01 '21 at 18:38
2

Just symbol/1 and dif/2 are sufficient extensions of logically pure Prolog.

Proof:

This answer contains an evaluator for Scheme expressions, evil/2. It understands lambda and quote, and can be easily extended to handle built-in list procedures like list, car, cdr, etc. Aside from pure (Horn) Prolog, it uses only symbol/1 and dif/2. While it is an interpreter and will run slowly, its existence shows that the same list operations done by Scheme can be done in such almost pure Prolog. (I think symbol/1 wouldn't be needed either, if Scheme symbols were translated into symb(prolog_atom) instead of directly into prolog_symbol)


Edit

This extends evil/2 to handle if, #t and #f (represented by true and false):

evil(true, _, true).
evil(false, _, false).

evil([if, E1, E2, E3], Env, Val) :-
    evil(E1, Env, false),
    symbols(E2),
    evil(E3, Env, Val).

evil([if, E1, E2, E3], Env, Val) :-
    evil(E1, Env, V),
    dif(V, false),
    symbols(E3),
    evil(E2, Env, Val).

This extends evil/2 to handle equalp. It's even more powerful than Scheme's eq* in that it will equate some closures too:

evil([equalp, E1, E2], Env, true) :-
    evil(E1, Env, V),
    evil(E2, Env, V).

evil([equalp, E1, E2], Env, false) :-
    evil(E1, Env, V1),
    evil(E2, Env, V2),
    dif(V1, V2).
MWB
  • 11,740
  • 6
  • 46
  • 91
  • 1
    That's an evaluator for Scheme expressions that don't include any form of `cond` or equality testing. Given that *equality testing is the underlying problem* for implementing list intersection, this "proof" doesn't show what you think it does. – Isabelle Newbie Dec 31 '20 at 10:53
  • @IsabelleNewbie The problem with `intersection` was the lack of *disequality*. However, this Q specifically allows `dif/2`. I think `evil/2` can be trivially extended to handle conditional statements (using `(=)/2` and `dif/2`). – MWB Dec 31 '20 at 11:11
  • @IsabelleNewbie added `if`, `#t` and `#f`. (Although I remember some discussions about how lambda-calculus doesn't need them) -- might be buggy w.r.t. what happens when you do `(lambda (#t)..)`, but should be fixable, if so. – MWB Dec 31 '20 at 11:31
  • *"Although I remember some discussions about how lambda-calculus doesn't need them"* It doesn't need them in the sense that you can come up with a way of encoding them as lambdas, the same way you can encode the natural numbers, lists, and everything else as lambdas. But if you're that reductionist, your original question becomes meaningless: We know that you can encode Scheme in a Turing machine, so it's no surprise that you can encode it in Prolog. – Isabelle Newbie Dec 31 '20 at 12:05
  • Anyway, this still lacks `equal?`, for which you will need some way of deciding equality, like `(=)/3` for example. – Isabelle Newbie Dec 31 '20 at 12:08
  • @IsabelleNewbie *"this still lacks equal?, for which you will need some way of deciding equality, like (=)/3 for example."* -- added `equalp`. – MWB Jan 01 '21 at 10:17
  • @IsabelleNewbie *"But if you're that reductionist, your original question becomes meaningless: We know that you can encode Scheme in a Turing machine, so it's no surprise that you can encode it in Prolog"* -- I'd challenge you to replicate `evil/3` without `dif/2`, using only Horn clauses, then (Must produce correct and *only* correct results) – MWB Jan 01 '21 at 10:21
  • 1
    @bobcat, why do you describe `dif/2` as an "extension" of logically pure prolog. Are we identifying "logically pure prolog" with "pure Horn clause prolog"? – Jason Hemann Mar 05 '21 at 20:13
1

If you take a pure Scheme. Possibly pure Prolog is enough. Pure Scheme would be lambda expressions with a certain strict call-by-value evaluation strategy. So we could implement pure Scheme as follows in Prolog, drawing on deBruijn indexes:

eval(P*Q, H) :- !,
   eval(P, R),
   eval(Q, S),
   reduce(R, S, H).
eval(X, X).

reduce(b(R), S, J) :- !,
   subst(R, 0, S, H),
   eval(H, J).
reduce(R, S, R*S).

If you change the respresentation a little bit, I guess you can get rid of the cuts. And maybe do the necessary arithmetic via Peano axioms. Eh voila you got pure Scheme in pure Prolog.

Here is an example query, SUCC and ZERO are from here:

?- ZERO = b(b(0)), SUCC = b(b(b(1*(2*1*0)))), 
   eval(SUCC*(SUCC*ZERO)*f*a, X).
ZERO = b(b(0)),
SUCC = b(b(b(1*(2*1*0)))),
X = f*(f*a)
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • In Scheme you can if-then-elseish test for equality trivially. Not so in Prolog without dif/2. – false Dec 23 '20 at 16:02
  • Good luck with dif/2, in SWI-Prolog you cannot combine it with unify_with_occurs_check/2, see here: https://stackoverflow.com/q/65532899/502187 –  Jan 01 '21 at 19:25