ichiban / prolog

The only reasonable scripting engine for Go.
MIT License
564 stars 27 forks source link

findall with list #316

Closed flashpixx closed 3 months ago

flashpixx commented 3 months ago

Hello,

I try to get all facts with findall with this call:

host(ip("192.168.45.1"),name("foo.bar")).
host(ip("192.168.45.2"),name("xxx.yyy")).

allhosts(L) :- findall([X,Y], host(ip(X),name(Y)), L).

If I call allhosts(L). it returns an empty result. I evaluate the script with swipl and it returns

L = [["192.168.45.1", "foo.bar"], ["192.168.45.2", "xxx.yyy"]].

Is it possible to run findall with a list argument?

Thanks a lot

ichiban commented 3 months ago

Hi, @flashpixx!

Could you check the version, please? I tried to replicate the problem but the current version seems to work.

Perhaps, you're seeing a bug in previous versions.

package main

import (
    "fmt"

    "github.com/ichiban/prolog"
)

func main() {
    p := prolog.New(nil, nil)

    if err := p.Exec(`
host(ip("192.168.45.1"),name("foo.bar")).
host(ip("192.168.45.2"),name("xxx.yyy")).

allhosts(L) :- findall([X,Y], host(ip(X),name(Y)), L).
    `); err != nil {
        panic(err)
    }

    sol := p.QuerySolution(`allhosts(L).`)
    if err := sol.Err(); err != nil {
        panic(err)
    }

    var s struct {
        L [][]string
    }
    if err := sol.Scan(&s); err != nil {
        panic(err)
    }

    fmt.Println(s.L)
}
[[192.168.45.1 foo.bar] [192.168.45.2 xxx.yyy]]

Program exited.

https://go.dev/play/p/gsNozILZvh-

flashpixx commented 3 months ago

Your example is working, but I'm using a map, because my queries are created by the user, so I didn't have fixed variable names and with the map it generated an issue: https://go.dev/play/p/Pc1komgBDwT It create the error s.L undefined (type map[string]interface{} has no field or method L)

package main

import (
    "fmt"

    "github.com/ichiban/prolog"
)

func main() {
    p := prolog.New(nil, nil)

    if err := p.Exec(`
host(ip("192.168.45.1"),name("foo.bar")).
host(ip("192.168.45.2"),name("xxx.yyy")).

allhosts(L) :- findall([X,Y], host(ip(X),name(Y)), L).
    `); err != nil {
        panic(err)
    }

    sol := p.QuerySolution(`allhosts(L).`)
    if err := sol.Err(); err != nil {
        panic(err)
    }

    s := make(map[string]interface{})
    if err := sol.Scan(&s); err != nil {
        panic(err)
    }

    fmt.Println(s.L)
}

On normale (atomic) values it works with a map, but not in this case

ichiban commented 3 months ago

It looks like you have a Go syntax error. To get L from a map, you need to look it up with a string "L".

package main

import (
    "fmt"

    "github.com/ichiban/prolog"
)

func main() {
    p := prolog.New(nil, nil)

    if err := p.Exec(`
host(ip("192.168.45.1"),name("foo.bar")).
host(ip("192.168.45.2"),name("xxx.yyy")).

allhosts(L) :- findall([X,Y], host(ip(X),name(Y)), L).
    `); err != nil {
        panic(err)
    }

    sol := p.QuerySolution(`allhosts(L).`)
    if err := sol.Err(); err != nil {
        panic(err)
    }

    s := make(map[string]interface{})
    if err := sol.Scan(&s); err != nil {
        panic(err)
    }

    fmt.Println(s["L"])
}
[[[1 9 2 . 1 6 8 . 4 5 . 1] [f o o . b a r]] [[1 9 2 . 1 6 8 . 4 5 . 2] [x x x . y y y]]]

Program exited.

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

flashpixx commented 3 months ago

I'm using next with

result := make([]map[string]interface{}, 0)
for r.Next() {
    m := make(map[string]interface{})
    result = append(result, m)
    if err := r.Scan(&m); err != nil {
       return nil, err
    }
}

Based on the input query it could be return more than one result, but expecially in that case next seems to return nil / false so the loop does not iterate

screen
flashpixx commented 3 months ago

I found the error it seems that my instatiation of the interepeter creates different behaviour: I do it with p := new(prolog.Interpreter) and you do it with p := prolog.New(nil, nil), I have changes the main init call and it works

And a comment in relatio to #315 in exactly this case it is quite hard to deal with the character list, because here I get []interface{} and in a map[string]interface{}, so to create a Go string it is a lot of additional code necessary