# ichiban/prolog puts an ISO Prolog interpreter inside your Go program

> The library embeds a standards oriented Prolog engine in Go, with a database/sql shaped API, placeholder bindings for Go values and a sandbox mode with no built-in predicates. It is a fit for logic work inside a Go service, and a poor fit if you want the object oriented scripting model that other Go interpreter libraries provide.

**ichiban/prolog** — The only reasonable scripting engine for Go.

- Repository: https://github.com/ichiban/prolog
- Stars: 734 · Forks: 38
- Language: Go
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ichiban-prolog

## A Prolog engine you call from Go, not a separate process

The repository describes itself as "an embeddable ISO Prolog interpreter in Go", and that word embeddable is the whole design decision. There is no external runtime to supervise, no server to talk to over a socket, no process to start per query. You construct an interpreter value in your Go program, feed it Prolog source, and ask it questions from the same code path that does the rest of the work.

The standard it targets is specific. The README lists ISO/IEC 13211-1:1995 for the general core of Prolog, A Prologue for Prolog, and DCGs. The default language therefore includes the ISO predicates plus the Prologue and DCG sets, with the directives and built-in predicate list kept in the project wiki. Alongside the interpreter sit two extension points that matter for embedding: custom predicates implemented in Go, and custom terms, meaning Go defined data types that can be passed into and out of the logic layer.

## Exec loads rules, Query returns a stream of solutions

The API is modelled on `database/sql`, and the analogy holds all the way through. `p.Exec` runs a program, in the sense of a database driver executing a statement without returning rows. `p.Query` runs a goal and hands back a solution set that you iterate. The solution set has `Next`, `Scan`, `Err` and `Close`, which are exactly the methods you would expect on a rows cursor.

The binding mechanism is the part worth stealing from the database model. Placeholders written as `?` inside the Prolog text are filled with Go values, so a query about a specific argument does not require string interpolation into Prolog syntax. The README gives both forms of the same query: one where the value is embedded as a quoted Prolog atom, and one where it is passed as a Go argument through a placeholder. That is the difference between building Prolog source as text and passing values across a boundary, and it is what keeps values from outside your program out of the rule source.

Solutions can also be scanned into a struct. The README shows a query over a variable `Who` and a local struct whose field name matches the variable, so a returned solution maps straight onto a Go value without manual term extraction.

## First steps: fetch the module, then write two lines

The module is fetched with the standard Go tool:

```console
go get -u github.com/ichiban/prolog
```

Creating an interpreter is a constructor call that takes the input and output streams:

```go
p := prolog.New(os.Stdin, os.Stdout) // Or `prolog.New(nil, nil)` if you don't need user_input/user_output.
```

Passing nil for both streams, as the comment notes, gives you an interpreter without user input and user output. Loading a program then goes through `Exec`, and a syntax error is returned as an error value:

```go
if err := p.Exec(`
	human(socrates).       % This is a fact.
	mortal(X) :- human(X). % This is a rule.
`); err != nil {
	panic(err)
}
```

The two clauses are a fact and a rule, which is the smallest interesting program: `human(socrates)` and the rule that derives `mortal(X)` from `human(X)`. To use a Go value as an argument, the placeholder form is the one to reach for:

```go
if err := p.Exec(`human(?).`, "socrates"); err != nil { // Same as p.Exec(`human("socrates").`)
	panic(err)
}
```

Finally, a query returns solutions you walk with `Next` and finish with `Err`:

```go
sols, err := p.Query(`mortal(?).`, "socrates") // Same as p.Query(`mortal("socrates").`)
if err != nil {
	panic(err)
}
defer sols.Close()

// Iterates over solutions.
for sols.Next() {
	fmt.Printf("Yes.\n") // ==> Yes.
}

// Check if an error occurred while querying.
if err := sols.Err(); err != nil {
	panic(err)
}
```

A failed query is not signalled by the absence of rows. The README is explicit that you check `sols.Err()` after the loop, because that is where an error raised during solving is reported.

## Sandboxing starts with zero built-in predicates

There are two ways to obtain an interpreter, and the difference is a security boundary rather than a style choice. `prolog.New` builds one with the standard library of predicates. For an interpreter without any built-in predicates, the README points you at the zero value:

```go
// See examples/sandboxing/main.go for details.
p := new(prolog.Interpreter)
```

The repository ships an `examples/sandboxing/` directory for exactly this pattern. The trade-off is direct. A sandboxed interpreter will not execute anything the standard library allows, which is the point if the rules come from outside your trust boundary. It also means the Prolog you write against it must be written with that restriction in mind, since the familiar built-ins are simply not there.

This is the design the README highlights first among its customisation options, alongside writing custom predicates in Go and defining custom terms in Go. The three are meant to work together: restrict what the interpreter can do, then add back precisely the operations your application needs.

## 1pl is an experimental top level, not a finished REPL

The project includes a command line top level, installed separately:

```console
go install github.com/ichiban/prolog/cmd/1pl@latest
```

It is then run from the Go workspace bin directory:

```console
$(go env GOPATH)/bin/1pl [<file>...]
```

The README calls `1pl` an experimental top level command for testing the default language and its compliance to the ISO standard, and the optional file arguments are part of that testing use. Two things follow from the word experimental. It is a compliance checking tool as much as an interactive shell, and the README does not present it as a supported production surface. If you plan to explore Prolog through it, treat the output as a statement about the interpreter's conformance, not as a polished developer experience.

For the extension ecosystem, the README points to native predicates from the `predicates` project and to `kagomelog`, a Japanese morphological analysing predicate.

## Logic with a database shaped API, not object oriented scripting

The README compares the library with `otto` and `go-lua` in a table, and the comparison is about approach rather than about feature lists. Both alternatives host scripting languages inside Go, but they host ECMA Script and Lua, both object oriented, both declarative style ruled out, and both exposing their original Go API rather than a `database/sql` shaped one. On sandboxing the table marks prolog and go-lua the same, with otlo left out, so it does rule out one of the two alternatives.

The real difference is the shape of a call. In an object oriented scripting engine you hand the host a script and it returns a value. Here you hand the interpreter a goal and it returns a stream of solutions, and the host reads that stream with `Next` and `Scan` the way it reads database rows. Rules are written as clauses that the engine resolves, and the host writes the bridging code for whatever does not belong in logic.

That is a genuine trade rather than a free win. Logic reads well for rule heavy domains, and the ISO conformance plus placeholder bindings make the boundary between Go and Prolog unusually clean. But nothing in this API hands you an imperative script host, so a task that is naturally a sequence of steps is more natural in the surrounding Go code.

## go.mod pins Go 1.19 and two direct dependencies

The module file is short and gives you the version picture. The module path is `github.com/ichiban/prolog`, the declared language version is `go 1.19`, and the direct requirements are `github.com/stretchr/testify v1.7.0` for testing and `golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897`. Everything else in the file is marked indirect.

The source layout explains how the interpreter is split. `interpreter.go` and `solutions.go` sit at the top level with their test files, `engine/` holds the solver, `cmd/` holds the commands including `1pl`, and `examples/` contains six runnable cases: calling Go from Prolog, DCGs, embedding Prolog into Go, the towers of Hanoi, initialization and sandboxing. `bootstrap.pl` and `ARCHITECTURE.md` sit alongside them, the second being what the contributing section points at for architecture details. `codecov.yml` and a codecov badge indicate that coverage is tracked for the pull request workflow.

On maintenance, the repository is not archived and the last push was on 2026-09-25, yet the most recent tag is v1.2.2 from 2024-10-15, with v1.2.1 and v1.2.0 in 2024 before that. Code has moved since the last release, so pinning to a tag means pinning to roughly two years of unaudited gap. The licence is MIT, with the details in the `LICENSE` file.

## Conclusion

Adopt ichiban/prolog when the rules you need to run are declarative and they should live inside an existing Go service rather than beside it, and when you are willing to write the few Go side glue pieces yourself. Do not adopt it for imperative scripting or for a quick REPL, since the only bundled top level is marked experimental. Verify first that the ISO predicate set in the wiki covers what your program calls, that a sandboxed interpreter built with new(prolog.Interpreter), which has no built-in predicates, has been given back every predicate your program calls, and that the version you vendor matches v1.2.2, the most recent tag from 2024-10-15, given that the last push was on 2026-09-25.

## FAQ

### How do I create a Prolog interpreter in Go with ichiban/prolog?

Call `prolog.New(os.Stdin, os.Stdout)` for an interpreter wired to your streams, or pass nil for both if you do not need user input or user output. For a sandboxed interpreter with no built-in predicates, use `new(prolog.Interpreter)`.

### How do I pass Go values into a Prolog query?

Use `?` placeholders inside the Prolog text and pass the Go value as an argument, so `p.Query(`mortal(?).`, "socrates")` runs the same goal as `p.Query(`mortal("socrates").`) without building Prolog source by string concatenation.

### How do I check whether a Prolog query failed with ichiban/prolog?

After iterating solutions with `sols.Next()`, check `sols.Err()`, as the README shows. The error surfaces there rather than through the absence of solutions, and the solution set should be closed with `defer sols.Close()`.

### Is the 1pl command line top level stable?

The README describes `1pl` as an experimental top level command for testing the default language and its compliance to the ISO standard. It is installed with `go install github.com/ichiban/prolog/cmd/1pl@latest` and run from your Go workspace bin directory.

## Sources

- [ichiban/prolog on GitHub](https://github.com/ichiban/prolog)
- [Issues](https://github.com/ichiban/prolog/issues)
- [License: MIT](https://github.com/ichiban/prolog/blob/main/LICENSE)
- [README](https://github.com/ichiban/prolog/blob/main/README.md)
- [Releases](https://github.com/ichiban/prolog/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ichiban-prolog
