Closed flashpixx closed 3 months ago
Only a small related note: Lists of characters are usually more preferable to represent strings, because they allow partially instantiated information and convenient reasoning about strings with DCGs. Is there any specific reason you want strings represented as atoms instead?
Yes in my case I need atoms and strings (double quoted) differently, I have got strings which containing data and should not be used as atoms, so pure data definition and I have got atoms which are not quoted, which presents the logical fact. In my use-case I use a lot of Go functions which are corresponding with external libraries to add new facts into the Prolog engine and as well some atoms. I need a more strict definition is it a value or a logical fact.
charList
is an implementation detail and I don't think it's particularly useful to be exposed.
If you want to add a fact with an atom and a string to your Prolog interpreter, you can use atom_chars/2
:
package main
import (
"fmt"
"github.com/ichiban/prolog"
)
func main() {
p := prolog.New(nil, nil)
if err := p.QuerySolution(`(Foo, Bar) = (?, ?), atom_chars(FooAtom, Foo), assertz(p(FooAtom, Bar)).`, "foo", "bar").Err(); err != nil {
panic(err)
}
if err := p.QuerySolution(`p(foo, "bar").`).Err(); err != nil {
panic(err)
}
fmt.Println("SUCCESS")
}
SUCCESS
Program exited.
In my use-case I'm add a Go function to the interpreter and have got this function signature:
func(en *engine.VM, argCidr engine.Term, k engine.Cont, env *engine.Env) *engine.Promise
the argCidr
is a double-quoted string, so how can I get it as Go-String? The debugger shows me as charList
and this could be easy casted into the string
type
I have create a minimal example to show the issue
package main
import (
"fmt"
"github.com/ichiban/prolog"
"github.com/ichiban/prolog/engine"
)
func main() {
p := new(prolog.Interpreter)
p.Register1(engine.NewAtom("foobar"), func(_ *engine.VM, url engine.Term, k engine.Cont, env *engine.Env) *engine.Promise {
u, ok := env.Resolve(url).(engine.Atom)
if !ok {
fmt.Println("string cast fails")
return engine.Error(engine.TypeError(engine.NewAtom("atom"), url, env))
}
fmt.Println(u.String())
return k(env)
})
sols, err := p.Query("foobar(?).", "String Test Case")
if err != nil {
panic(err)
}
defer func() {
if err := sols.Close(); err != nil {
panic(err)
}
}()
As far as I know, a Prolog string is just a list of single-character atoms.
You can't expect it to be charList
all the time. It's just one internal representation a string can take. For example, S
in S = [a, b, X], X = c.
is a valid Prolog string but we can't represent it as type charList string
.
Since it's a list of atoms, you can use engine.ListIterator
to iterate over it and append each atom to a strings.Builder
.
package main
import (
"fmt"
"strings"
"github.com/ichiban/prolog"
"github.com/ichiban/prolog/engine"
)
func main() {
p := new(prolog.Interpreter)
p.Register1(engine.NewAtom("foobar"), func(_ *engine.VM, url engine.Term, k engine.Cont, env *engine.Env) *engine.Promise {
var b strings.Builder
iter := engine.ListIterator{List: url, Env: env}
for iter.Next() {
switch r := env.Resolve(iter.Current()).(type) {
case engine.Variable:
return engine.Error(engine.InstantiationError(env))
case engine.Atom:
if len([]rune(r.String())) != 1 {
return engine.Error(engine.TypeError(engine.NewAtom("character"), r, env))
}
_, _ = b.WriteString(r.String())
default:
return engine.Error(engine.TypeError(engine.NewAtom("character"), r, env))
}
}
if err := iter.Err(); err != nil {
return engine.Error(err)
}
fmt.Println(b.String())
return k(env)
})
if err := p.QuerySolution("foobar(?).", "String Test Case").Err(); err != nil {
panic(err)
}
}
String Test Case
Program exited.
https://go.dev/play/p/Du8ELNXpwx_v
this is the reason to make the charList
public, because on an integer, it is quite easy to get it with u, ok := env.Resolve(url).(engine.Integer)
, so it would be nice I can do it with u, ok := env.Resolve(url).(engine.CharList)
, so it reduces the code complexity and it is more consistant. In general it would be nice to do u, ok := env.Resolve(url).(engine.String)
I feel the same about u, ok := env.Resolve(url).(engine.String)
being a better notation and I wouldn't have missed the opportunity if I could make it so. Unfortunately, this idiom only works for an atomic term which has a single internal representation. Compound terms, on the other hand, have many internal representations.
Currently, a Prolog string can be an Atom
, compound
, list
, partial
, charList
, or combination of them. This is because a Prolog string is just a list of one-char atoms.
Maybe a Prolog string isn't suitable for your use case. How about storing your Go strings externally and passing around the surrogate keys?
package main
import (
"fmt"
"github.com/ichiban/prolog"
"github.com/ichiban/prolog/engine"
)
func main() {
urlRepository := map[int]string{
123: "String Test Case",
}
p := new(prolog.Interpreter)
p.Register1(engine.NewAtom("foobar"), func(_ *engine.VM, urlID engine.Term, k engine.Cont, env *engine.Env) *engine.Promise {
var s string
switch id := env.Resolve(urlID).(type) {
case engine.Variable:
return engine.Error(engine.InstantiationError(env))
case engine.Integer:
s = urlRepository[int(id)]
default:
return engine.Error(engine.TypeError(engine.NewAtom("integer"), id, env))
}
fmt.Println(s)
return k(env)
})
if err := p.QuerySolution("foobar(?).", 123).Err(); err != nil {
panic(err)
}
}
String Test Case
Program exited.
I feel the same about u, ok := env.Resolve(url).(engine.String) being a better notation and I wouldn't have missed the opportunity if I could make it so. Unfortunately, this idiom only works for an atomic term which has a single internal representation. Compound terms, on the other hand, have many internal representations.
It would be really nice if you can add the engine.String
as enhancement, I have written a function which converts me the atom into my Go string, so this is dirty fix at the moment which works
engine.String
won't happen in the foreseeable future. Again, this is because a Prolog string is much much complex than a Go string. You need to either accept the complexity by treating it as a list or avoid the complexity by falling back to atoms or surrogate keys.
A simple utility might help you to mitigate the complexity:
package main
import (
"fmt"
"io"
"github.com/ichiban/prolog"
"github.com/ichiban/prolog/engine"
)
func main() {
p := new(prolog.Interpreter)
p.Register1(engine.NewAtom("foobar"), func(_ *engine.VM, url engine.Term, k engine.Cont, env *engine.Env) *engine.Promise {
r := NewCharListReader(url, env)
b, err := io.ReadAll(r)
if err != nil {
return engine.Error(err)
}
fmt.Println(string(b))
return k(env)
})
if err := p.QuerySolution("foobar(?).", "String Test Case").Err(); err != nil {
panic(err)
}
}
type CharListReader struct {
iter engine.ListIterator
}
func NewCharListReader(t engine.Term, env *engine.Env) *CharListReader {
return &CharListReader{
iter: engine.ListIterator{
List: t,
Env: env,
},
}
}
func (c *CharListReader) Read(p []byte) (int, error) {
iter := &c.iter
if !iter.Next() {
if err := iter.Err(); err != nil {
return 0, err
}
return 0, io.EOF
}
switch char := iter.Env.Resolve(iter.Current()).(type) {
case engine.Variable:
return 0, engine.InstantiationError(iter.Env)
case engine.Atom:
s := char.String()
if len([]rune(s)) != 1 {
return 0, engine.TypeError(engine.NewAtom("character"), char, iter.Env)
}
b := []byte(s)
copy(p, b)
return len(b), nil
default:
return 0, engine.TypeError(engine.NewAtom("character"), char, iter.Env)
}
}
Thanks, I came to a very similar solution
Hello,
is it possible to make
charList
here public toCharList
, so it is possible to deal with that type? So it is easy to deal with strings without using:- set_prolog_flag(double_quotes, atom).
Thanks a lot