From 16969e7cd181579195a08df52fd0848ce345d221 Mon Sep 17 00:00:00 2001 From: Cassie Jones Date: Mon, 17 Feb 2020 02:09:56 +0100 Subject: [PATCH] Add a README --- README.md | 99 ++++++++++++++++++++++++++++++++++++++ src/bin/readme_examples.rs | 48 ++++++++++++++++++ src/prelude.rs | 1 + 3 files changed, 148 insertions(+) create mode 100644 README.md create mode 100644 src/bin/readme_examples.rs create mode 100644 src/prelude.rs diff --git a/README.md b/README.md new file mode 100644 index 0000000..536437f --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# MuKan + +```toml +mukan = { git = "https://git.witchoflight.com/mukan" } +``` + +(Micro Kanaya :3) + +This is a Rust implementation of MicroKanren, based on the very readable +original paper at: +. + +There's a nice Clojure implementation that I also found very informative, for +getting another perspective on the unification and an idea of where the natural +extension points are: . + +## Usage: + +You build up goals with goal constructors, and then execute them with the +`search` method of `Goal`, running that on an initial state. One of the +simplest possible programs you could write is: + +```rust +use mukan::{Goal, call_fresh, State, eq}; + +fn main() { + let goal = call_fresh(|x| eq(413, x)); + for soln in goal.search(State::new()) { + println!("{}", soln); + } +} +``` + +This will create a goal that wants to solve `413 = X`, and then show you all of +the solutions starting with an empty state. You can get slightly fancier, with +problems that have multiple solutions: + +```rust +let goal = call_fresh(|x| or(eq(612, x), eq(1025, x))); +``` + +This will produce two solutions, assigning x to 612 and 1025 in them, +respectively. There are macros that make several of the features easier to +use. Here, we can use `fresh!` to create multiple variables, and `all!` to and +together an arbitrary number of goals. You can also work with pairs of +variables, letting you produce some more interesting cases. + +```rust +let goal = fresh!((a, b, c) => all! { + eq((a, b), c), + eq((b, 8), c), +}); +``` + +Here this should solve to `a = 8`, `b = 8`, and `c = (8, 8)`. Of course, the +solution that gets printed is messier, so you might want to ask for a solution +in terms of the raw values. You can get the entire state evaluated like this +with the `reify` function, but if you just want to get a few particular +variables, then you can use `reify_vars` with variables that you own. + +One way to get those variables, is make your variables in a particular state: +```rust +let (vars, state) = State::with_vars(2); +let (a, b) = (vars[0], vars[1]); +let goal = fresh!((c) => all! { + eq((a, b), c), + eq((b, 8), c), +}); +for soln in goal.search(state) { + println!("{:?}", reify_vars(&vars, soln)); +} +``` + +You can write recursive goals as rust functions, as well! + +```rust +fn peano(n: impl Into) -> impl Goal { + let n = n.into(); + or( + eq(n.clone(), 0), + fresh!((x) => and( + eq(n.clone(), (1, x)), + delay(move || peano(x)), + )), + ) +} +``` + +We need the `delay()` in order to prevent the goal from being constructed +forever without being searched at all. If you try to solve `peano` with a +variable, then you'd get an infinite stream of assignments that satisfy it. + +```rust +let (vars, state) = State::with_vars(1); +let goal = peano(vars[0]); +for soln in goal.search(state).take(10) { + println!("{:?}", reify_vars(&vars, soln)); +} +``` diff --git a/src/bin/readme_examples.rs b/src/bin/readme_examples.rs new file mode 100644 index 0000000..7e0642f --- /dev/null +++ b/src/bin/readme_examples.rs @@ -0,0 +1,48 @@ +use mukan::{all, and, call_fresh, delay, eq, fresh, or, reify_vars, Goal, State, Term}; + +fn main() { + let goal = call_fresh(|x| eq(413, x)); + for soln in goal.search(State::new()) { + println!("{}", soln); + } + + let goal = call_fresh(|x| or(eq(612, x), eq(1025, x))); + for soln in goal.search(State::new()) { + println!("{}", soln); + } + + let goal = fresh!((a, b, c) => all! { + eq((a, b), c), + eq((b, 8), c), + }); + for soln in goal.search(State::new()) { + println!("{}", soln); + } + + let (vars, state) = State::with_vars(2); + let (a, b) = (vars[0], vars[1]); + let goal = fresh!((c) => all! { + eq((a, b), c), + eq((b, 8), c), + }); + for soln in goal.search(state) { + println!("{:?}", reify_vars(&vars, soln)); + } + + fn peano(n: impl Into) -> impl Goal { + let n = n.into(); + or( + eq(n.clone(), 0), + fresh!((x) => and( + eq(n.clone(), (1, x)), + delay(move || peano(x)), + )), + ) + } + + let (vars, state) = State::with_vars(1); + let goal = peano(vars[0]); + for soln in goal.search(state).take(10) { + println!("{:?}", reify_vars(&vars, soln)); + } +} diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..b9c85aa --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1 @@ +pub use crate::Goal; -- 2.47.0