github.com/trealla-prolog/trealla-go

Trealla Prolog embedded in Go using WASM


Keywords
go, logic-programming, prolog, trealla-prolog, wasm
License
MIT
Install
go get github.com/trealla-prolog/trealla-go

Documentation

trealla-go GoDoc

import "github.com/trealla-prolog/go/trealla"

Prolog interface for Go using Trealla Prolog and wazero. It's pretty fast. Not as fast as native Trealla, but pretty dang fast (about 2x slower than native).

Development Status: inching closer to stability

Caveats

  • Beta status, API will probably change
    • API is relatively stable now.

Install

go get github.com/trealla-prolog/go

Note: the module is under github.com/trealla-prolog/go, not [...]/go/trealla. go.dev is confused about this and will pull a very old version if you try to go get the trealla package.

Usage

This library uses WebAssembly to run Trealla, executing Prolog queries in an isolated environment.

import "github.com/trealla-prolog/go/trealla"

func main() {
	// load the interpreter and (optionally) grant access to the current directory
	pl := trealla.New(trealla.WithPreopen("."))
	// run a query; cancel context to abort it
	ctx := context.Background()
	query := pl.Query(ctx, "member(X, [1, foo(bar), c]).")

	// calling Close is not necessary if you iterate through the whole result set
	// but it doesn't hurt either
	defer query.Close()

	// iterate through answers
	for query.Next(ctx) {
		answer := query.Current()
		x := answer.Solution["X"]
		fmt.Println(x) // 1, trealla.Compound{Functor: "foo", Args: [trealla.Atom("bar")]}, "c"
	}

	// make sure to check the query for errors
	if err := query.Err(); err != nil {
		panic(err)
	}
}

Single query

Use QueryOnce when you only want a single answer.

pl := trealla.New()
answer, err := pl.QueryOnce(ctx, "succ(41, N).")
if err != nil {
	panic(err)
}

fmt.Println(answer.Stdout)
// Output: hello world

Binding variables

You can bind variables in the query using the WithBind and WithBinding options. This is a safe and convenient way to pass data into the query. It is OK to pass these multiple times.

pl := trealla.New()
answer, err := pl.QueryOnce(ctx, "write(X)", trealla.WithBind("X", trealla.Atom("hello world")))
if err != nil {
	panic(err)
}

fmt.Println(answer.Stdout)
// Output: hello world

Scanning solutions

You can scan an answer's substitutions directly into a struct or map, similar to ichiban/prolog.

Use the prolog:"VariableName" struct tag to manually specify a variable name. Otherwise, the field's name is used.

answer, err := pl.QueryOnce(ctx, `X = 123, Y = abc, Z = ["hello", "world"].`)
if err != nil {
	panic(err)
}

var result struct {
	X  int
	Y  string
	Hi []string `prolog:"Z"`
}
// make sure to pass a pointer to the struct!
if err := answer.Solution.Scan(&result); err != nil {
	panic(err)
}

fmt.Printf("%+v", result)
// Output: {X:123 Y:abc Hi:[hello world]}

Struct compounds

Prolog compounds can destructure into Go structs. A special field of type trealla.Functor will be set to the functor. The compound's arguments are matched with the exported struct fields in order. These structs can also be used to bind variables in queries.

?- findall(kv(Flag, Value), current_prolog_flag(Flag, Value), Flags).
   Flags = [kv(double_quotes,chars),kv(char_conversion,off),kv(occurs_check,false),kv(character_escapes,true),...]
// You can embed trealla.Functor to represent Prolog compounds using Go structs.

// kv(Flag, Value)
type pair struct {
	trealla.Functor `prolog:"-/2"` // tag is optional, but can be used to specify the functor/arity
	Flag            trealla.Atom   // 1st arg
	Value           trealla.Term   // 2nd arg
}
var result struct {
	Flags []pair // Flags variable
}

ctx := context.Background()
pl, err := trealla.New()
if err != nil {
	panic(err)
}
answer, err := pl.QueryOnce(ctx, `
	findall(Flag-Value, (member(Flag, [double_quotes, encoding, max_arity]), current_prolog_flag(Flag, Value)), Flags).
`)
if err != nil {
	panic(err)
}
if err := answer.Solution.Scan(&result); err != nil {
	panic(err)
}
fmt.Printf("%v\n", result.Flags)
// Output: [{- double_quotes chars} {- encoding 'UTF-8'} {- max_arity 255}]

Documentation

See package trealla's documentation for more details and examples.

Builtins

These additional predicates are built in:

  • crypto_data_hash/3
  • http_consult/1
    • Argument can be URL string, or my_module_name:"https://url.example"

WASM binary

This library embeds the Trealla WebAssembly binary in itself, so you can use it without any external dependencies. The binaries are currently sourced from guregu/trealla.

Building the WASM binary

If you'd like to build libtpl.wasm yourself:

# The submodule in src/trealla points to the current version
git submodule update --init --recursive
# Use the Makefile in the root of this project
make clean wasm
# Run the tests and benchmark to make sure it works
go test -v ./trealla -bench=.

Thanks

License

MIT. See ATTRIBUTION as well.

See also