ichiban / prolog

The only reasonable scripting engine for Go.
MIT License
613 stars 29 forks source link

Missing forall predicate #324

Closed lynerist closed 2 months ago

lynerist commented 2 months ago

I need to implement something like that:

valid(X) :- 
    forall(needs(X,Y),
            exists(Y)).
exists(a).
exists(b).
needs(x,a).
needs(x,b).
needs(y,b).
needs(y,c).

% ?- valid(x). % =>true % ?- valid(y). % => false % ?- valid(z). % => true

But it seems that the forall predicate is missing in the repository. I'll use the findall to replace it but it would be a great improvement to add the forall.

UWN commented 2 months ago

This is not missing. And findall/3 is not a good replacement. It's in some implementations offered as a double (\+)/1 with all its weak points. To take your example:


?- X = a, valid(X).
   X = a.
?-        valid(X), X = a.
   false, unexpected.
lynerist commented 2 months ago

If it's not missing then it's my bad, I'm sorry. But then, why if I run this code (where p is a *prolog.Interpreter) :

p.Exec(`

valid(X) :- 
    forall(needs(X,Y),
            exists(Y)).
exists(a).
exists(b).
needs(x,a).
needs(x,b).
needs(y,b).
needs(y,c).
    `)

with the query

p.Query("valid(x).")

The resulting *prolog.Solution is empty? I mean, the Next method returns false and there is no solution.

btw when I said that I'll use the findall/3 to replace it I meant to rewrite valid in this way:

valid(X) :-
    findall(Y, needs(X, Y), Needed),
    maplist(exists, Needed).
UWN commented 2 months ago

If it's not missing then it's my bad, I'm sorry.

My fault. What I meant was that this forall/2 is a rather problematic predicate. So it does not make sense to include it into the set of built-in predicates. And thus it is "not missing".

It was considered to be included into the standard. Last time was N208, but later it was not included into Cor.2 (all below the double line was rejected).

In general, when trying out Prolog, I would suggest to first use the top level loop, and not an interface via another programming language since such interfaces are often optimized for certain uses only.

Your definition of valid/1 has a similar problem with the most general query ?- valid(X). as this will succeed only if all needed things exist. And if a single does not exist, it will fail.

ichiban commented 2 months ago

Hi, @lynerist

As you found out, forall/2 isn't in this library. We only include standardized predicates.

Luckily, you can easily implement forall/2 like this:

package main

import (
    "fmt"

    "github.com/ichiban/prolog"
)

func main() {
    p := prolog.New(nil, nil)
    if err := p.Exec(`
        forall(Condition, Action) :- \+ (Condition, \+Action).

        valid(X) :- forall(needs(X,Y), exists(Y)).

        exists(a).
        exists(b).

        needs(x,a).
        needs(x,b).
        needs(y,b).
        needs(y,c).
    `); err != nil {
        panic(err)
    }

    for _, q := range []string{
        `X = x, valid(X).`, `valid(X), X = x.`,
        `X = y, valid(X).`, `valid(X), X = y.`,
        `X = z, valid(X).`, `valid(X), X = z.`,
    } {
        s := p.QuerySolution(q)
        fmt.Printf("%s ==> %t\n", q, s.Err() == nil)
    }
}
X = x, valid(X). ==> true
valid(X), X = x. ==> false
X = y, valid(X). ==> false
valid(X), X = y. ==> false
X = z, valid(X). ==> true
valid(X), X = z. ==> false

Program exited.

https://go.dev/play/p/xMb1_IS0_Oh

Of course, you should be careful with the weird behaviour of forall/2 @UWN pointed out. (Thank you for the insight!)

But for me, it feels more natural to encode the requirements as bodies of valid/1 like this:

package main

import (
    "fmt"

    "github.com/ichiban/prolog"
)

func main() {
    p := prolog.New(nil, nil)
    if err := p.Exec(`
        valid(x) :- exists(a), exists(b).
        valid(y) :- exists(b), exists(c).
        valid(z).

        exists(a).
        exists(b).
    `); err != nil {
        panic(err)
    }

    for _, q := range []string{
        `X = x, valid(X).`, `valid(X), X = x.`,
        `X = y, valid(X).`, `valid(X), X = y.`,
        `X = z, valid(X).`, `valid(X), X = z.`,
    } {
        s := p.QuerySolution(q)
        fmt.Printf("%s ==> %t\n", q, s.Err() == nil)
    }
}
X = x, valid(X). ==> true
valid(X), X = x. ==> true
X = y, valid(X). ==> false
valid(X), X = y. ==> false
X = z, valid(X). ==> true
valid(X), X = z. ==> true

Program exited.

https://go.dev/play/p/NzWcAmmMkf0

lynerist commented 2 months ago

Ok thank you very much! I'm pretty new in Prolog and I'm using swipl compiler to test programs before to implement them in Go, due to the fact that they implemented "forall" I thought it was in the standard.

I'll implement it as @ichiban suggested, thank you for the advice.

Anyway I can't include the requirements as bodies

But for me, it feels more natural to encode the requirements as bodies of valid/1 like this:

because they change with the input and in the time so I need valid/1 to be a function.