koskari.solve
Module: koskari.solve
Imports: import-from koskari.solve amb fail require solve solve-first
Use-case: Nondeterministic search. amb chooses a value from a
sequence of choices; each choice runs the rest of the search, require
prunes branches that cannot work, and solve / solve-first delimit
the search and collect what survives. There is no search engine behind
it: amb is an ordinary function over the language’s delimited
continuations, so choice points compose across function calls and nest
to any depth.
amb
Syntax: (amb CHOICES)
Nondeterministically choose a value from CHOICES (any iterable). The
rest of the enclosing search runs once per choice, and each choice’s
solutions are collected in order. Calling amb with no enclosing
solve or solve-first raises NoActiveSearch (see the Errors section).
require
Syntax: (require TEST)
Prune the current branch unless TEST is truthy. A pruned branch
contributes no solutions; the search continues with the next choice.
fail
Syntax: (fail)
Abandon the current branch unconditionally. (require test) is
(unless test (fail)).
solve
Syntax: (solve THUNK)
Run THUNK as a search and return every solution as a list, in choice
order. The thunk’s return value is one solution; branches ended by
fail or a failed require contribute nothing. An exhausted search
returns [].
solve-first
Syntax: (solve-first THUNK)
Run THUNK as a search and return its first solution, or None when
every branch fails. Once a solution is accepted, the remaining choices
at every level are skipped without running.
Enumeration is eager within a search: solve explores everything
before returning, and solve-first stops early by skipping rather than
by suspending. A lazy, one-solution-at-a-time enumerator is a
non-goal for this module: it would require yielding across choice
points, and a shift cannot cross a generator boundary (see the Errors section).
Errors
The module exports its own error vocabulary, rooted under the language’s
KoskariContinuationError category:
SearchError– base class for search errors raised by this module.NoActiveSearch–amb,fail, orrequirewas called with no enclosingsolveorsolve-firstto delimit the search. Wrap the whole computation in(solve (lambda [] ...))or(solve-first (lambda [] ...)).
A choice point inside a generator raises KoskariPromptUnreachable
untranslated: the delimiter exists but the capture cannot cross the
generator boundary, and no added delimiter will help – the generator
must be restructured out of the path between the choice point and the
search. A capture failure against a foreign (non-search) delimiter also
propagates untouched.
Examples:
import-from koskari.solve amb require solve solve-first
;; every pythagorean triple with sides under 15
(solve
(lambda []
(var a (amb (range 1 15)))
(var b (amb (range a 15)))
(var c (amb (range b 15)))
(require (== (+ (* a a) (* b b)) (* c c)))
(#tuple a b c)))
;; => #[#(3 4 5) #(5 12 13) #(6 8 10)]
;; just the first
(solve-first
(lambda []
(var x (amb (#list 1 2 3)))
(require (> x 1))
x))
;; => 2
See examples/solve.ki for a complete program, including Dinesman’s
dwelling puzzle.