micro_kanren 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/Rakefile +10 -0
- data/lib/micro_kanren/core.rb +104 -0
- data/lib/micro_kanren/lisp.rb +45 -0
- data/lib/micro_kanren/version.rb +3 -0
- data/lib/micro_kanren.rb +3 -0
- data/micro_kanren.gemspec +24 -0
- data/spec/micro_kanren/core_spec.rb +61 -0
- data/spec/micro_kanren/lisp_spec.rb +53 -0
- data/spec/spec_helper.rb +5 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 56f3ef35a8ec5353160ad27cc8f00f6515a8f02c
|
4
|
+
data.tar.gz: 12def3bc0db39c98206386359ae92707209abca2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e82a29102bc5b11474280e2320a8ca3771208859167595dd10b16e1897c705001257bb92006057729f2900f1702974fe9a1eaf3eb2c5c9213bf5d415c4e4f711
|
7
|
+
data.tar.gz: c09e905aab95d68c69dd2576d39ebc985d53bf9947c36120143b2d00a18a9ad321bc58fa2ab4b30d9f5c57d765c77127c958543a4176d3dbaa360d3057caf640
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Justin Leitgeb
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# microKanren in Ruby
|
2
|
+
|
3
|
+
A port of microKanren, a minimalistic logic programming language, to Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'micro_kanren'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install micro_kanren
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'micro_kanren'
|
23
|
+
include MicroKanren::Core
|
24
|
+
|
25
|
+
call_fresh(-> (q) { eq(q, 5) }).call(empty_state)
|
26
|
+
|
27
|
+
# The result is a set of nested lambda cons cells equivalent to ((([0] . 5 )) . 1)).
|
28
|
+
```
|
29
|
+
|
30
|
+
See the language_spec for more examples.
|
31
|
+
## Credits
|
32
|
+
|
33
|
+
The code in this gem is closely based on the following sources:
|
34
|
+
|
35
|
+
* The [microKanren paper](http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf)
|
36
|
+
by Jason Hemann and Daniel P. Friedman. I read this paper a couple of times, and
|
37
|
+
will probably have to read it a few more before I have a better understanding
|
38
|
+
about what this code is doing and how to write more effective logic programs.
|
39
|
+
* This code is also in parts copied from
|
40
|
+
[Scott Vokes' port of microKanren to Lua](https://github.com/silentbicycle/lua-ukanren).
|
41
|
+
It was great to have the Lua code as a second example of the implementation in
|
42
|
+
the paper, and it made my job especially easy since Lua is so similar to Ruby.
|
43
|
+
* Finally, I used the [microKanren examples in Scheme](https://github.com/jasonhemann/microKanren)
|
44
|
+
to see if this port worked as expected.
|
45
|
+
|
46
|
+
## Dependencies
|
47
|
+
|
48
|
+
This project requires Ruby 2.0 or higher. You can see which Rubies work with
|
49
|
+
this project in the [Travis CI Build Status](https://travis-ci.org/jsl/ruby_ukanren).
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
1. Fork it ( http://github.com/jsl/ruby_ukanren/fork )
|
54
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
55
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
56
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
57
|
+
5. Create new Pull Request
|
58
|
+
|
59
|
+
## Build Status
|
60
|
+
|
61
|
+
[](https://travis-ci.org/jsl/ruby_ukanren)
|
data/Rakefile
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module MicroKanren
|
2
|
+
module Core
|
3
|
+
include Lisp
|
4
|
+
|
5
|
+
def var(*c) ; Array.new(c) ; end
|
6
|
+
def var?(x) ; x.is_a?(Array) ; end
|
7
|
+
|
8
|
+
def vars_eq?(x1, x2) ; x1 == x2 ; end
|
9
|
+
|
10
|
+
def mzero ; nil ; end
|
11
|
+
|
12
|
+
def ext_s(x, v, s)
|
13
|
+
cons(cons(x, v), s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def unify(u, v, s)
|
17
|
+
u = walk(u, s)
|
18
|
+
v = walk(v, s)
|
19
|
+
|
20
|
+
if var?(u) && var?(v) && vars_eq?(u, v)
|
21
|
+
s
|
22
|
+
elsif var?(u)
|
23
|
+
ext_s(u, v, s)
|
24
|
+
|
25
|
+
elsif var?(v)
|
26
|
+
ext_s(v, u, s)
|
27
|
+
|
28
|
+
elsif pair?(u) && pair?(v)
|
29
|
+
if s = unify(car(u), car(v), s)
|
30
|
+
unify(cdr(u), cdr(v), s)
|
31
|
+
end
|
32
|
+
elsif u == v
|
33
|
+
s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def unit(s_c)
|
38
|
+
cons(s_c, mzero)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Constrain u to be equal to v.
|
42
|
+
def eq(u, v)
|
43
|
+
->(s_c) {
|
44
|
+
s = unify(u, v, car(s_c))
|
45
|
+
s ? unit(cons(s, cdr(s_c))) : mzero
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Walk environment S and look up value of U, if present.
|
50
|
+
def walk(u, s)
|
51
|
+
if var?(u)
|
52
|
+
pr = assp(-> (v) { u == v }, s)
|
53
|
+
pr ? walk(cdr(pr), s) : u
|
54
|
+
|
55
|
+
else
|
56
|
+
u
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Call function f with a fresh variable.
|
61
|
+
def call_fresh(f)
|
62
|
+
-> (s_c) {
|
63
|
+
c = cdr(s_c)
|
64
|
+
f.call(var(c)).call(cons(car(s_c), c + 1))
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
def mplus(d1, d2)
|
69
|
+
if d1.nil?
|
70
|
+
d2
|
71
|
+
elsif d1.is_a?(Proc) && !cons_cell?(d1)
|
72
|
+
-> { mplus(d2, d1.call) }
|
73
|
+
else
|
74
|
+
cons(car(d1), mplus(cdr(d1), d2))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def bind(d, g)
|
79
|
+
if d.nil?
|
80
|
+
mzero
|
81
|
+
elsif d.is_a?(Proc) && !cons_cell?(d)
|
82
|
+
-> { bind(d.call, g) }
|
83
|
+
else
|
84
|
+
mplus(g.call(car(d)), bind(cdr(d), g))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def disj(g1, g2)
|
89
|
+
-> (s_c) {
|
90
|
+
mplus(g1.call(s_c), g2.call(s_c))
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def conj(g1, g2)
|
95
|
+
-> (s_c) {
|
96
|
+
bind(g1.call(s_c), g2)
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def empty_state
|
101
|
+
cons(mzero, 0)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module MicroKanren
|
2
|
+
module Lisp
|
3
|
+
|
4
|
+
# Returns a Cons cell that is also marked as such for later identification.
|
5
|
+
def cons(x, y)
|
6
|
+
-> (m) { m.call(x, y) }.tap do |func|
|
7
|
+
func.instance_eval{ def cons_cell? ; true ; end }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def car(z) ; z.call(-> (p, q) { p }) ; end
|
12
|
+
def cdr(z) ; z.call(-> (p, q) { q }) ; end
|
13
|
+
|
14
|
+
def cons_cell?(d)
|
15
|
+
d.respond_to?(:cons_cell?) && d.cons_cell?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Search association list by predicate function.
|
19
|
+
# Based on lua implementation by silentbicycle:
|
20
|
+
# https://github.com/silentbicycle/lua-ukanren/blob/master/ukanren.lua#L53:L61
|
21
|
+
#
|
22
|
+
# Additional reference for this function is scheme:
|
23
|
+
# Ref for assp: http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-4.html
|
24
|
+
def assp(func, alist)
|
25
|
+
if alist && hd = car(alist)
|
26
|
+
func.call(hd) ? hd : assp(func, cdr(alist))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# A nicer printed representation of nested cons cells represented by Ruby
|
31
|
+
# Procs.
|
32
|
+
def print_ast(node)
|
33
|
+
if cons_cell?(node)
|
34
|
+
['(', print_ast(car(node)), ' . ', print_ast(cdr(node)), ')'].join
|
35
|
+
else
|
36
|
+
case node
|
37
|
+
when NilClass, Array, String
|
38
|
+
node.inspect
|
39
|
+
else
|
40
|
+
node.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/micro_kanren.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'micro_kanren/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "micro_kanren"
|
8
|
+
spec.version = MicroKanren::VERSION
|
9
|
+
spec.authors = ["Justin Leitgeb"]
|
10
|
+
spec.email = ["justin@stackbuilders.com"]
|
11
|
+
spec.summary = %q{uKanren in Ruby}
|
12
|
+
spec.description = %q{A port of uKanren to Ruby.}
|
13
|
+
spec.homepage = "http://github.com/jsl/ruby_ukanren"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency 'mocha'
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MicroKanren::Core do
|
4
|
+
include MicroKanren::Core
|
5
|
+
|
6
|
+
describe "#call_fresh" do
|
7
|
+
it "second-set t1" do
|
8
|
+
res = call_fresh(-> (q) { eq(q, 5) }).call(empty_state)
|
9
|
+
|
10
|
+
# The result should be ((([0] . 5 )) . 1)) following the reference
|
11
|
+
# implementation:
|
12
|
+
# https://github.com/jasonhemann/microKanren/blob/master/microKanren-test.scm#L6
|
13
|
+
|
14
|
+
# We don't have a pretty printer for our lambda-conses, so here goes:
|
15
|
+
car(car(car(car(res)))).must_equal [0]
|
16
|
+
cdr(car(res)).must_equal 1
|
17
|
+
cdr(car(car(car(res)))).must_equal 5
|
18
|
+
end
|
19
|
+
|
20
|
+
it "second-set t2" do
|
21
|
+
res = call_fresh(-> (q) { eq(q, 5) }).call(empty_state)
|
22
|
+
|
23
|
+
# Following reference implementation:
|
24
|
+
# https://github.com/jasonhemann/microKanren/blob/master/microKanren-test.scm#L11
|
25
|
+
cdr(res).must_be_nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def a_and_b
|
29
|
+
a = -> (a) { eq(a, 7) }
|
30
|
+
b = -> (b) { disj(eq(b, 5), eq(b, 6)) }
|
31
|
+
|
32
|
+
conj(call_fresh(a), call_fresh(b))
|
33
|
+
end
|
34
|
+
|
35
|
+
it "second-set t3" do
|
36
|
+
res = a_and_b.call(empty_state)
|
37
|
+
|
38
|
+
# The result should be ((([1] . 5) ([0] . 7)) . 2)) following the reference
|
39
|
+
# implementation:
|
40
|
+
# https://github.com/jasonhemann/microKanren/blob/master/microKanren-test.scm#L13
|
41
|
+
car(car(car(car(res)))).must_equal [1]
|
42
|
+
cdr(car(car(car(res)))).must_equal 5
|
43
|
+
car(car(cdr(car(car(res))))).must_equal [0]
|
44
|
+
cdr(car(cdr(car(car(res))))).must_equal 7
|
45
|
+
cdr(car(res)).must_equal 2
|
46
|
+
end
|
47
|
+
|
48
|
+
def fives
|
49
|
+
-> (x) {
|
50
|
+
disj(eq(x, 5), -> (a_c) { -> { fives(x).call(a_c) } })
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
it "who cares" do # Apparently not the authors of the reference implementation...
|
55
|
+
skip("Create proper assertion for this test")
|
56
|
+
l = -> (q) { fives.call(q) }
|
57
|
+
res = call_fresh(l).call(empty_state)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MicroKanren::Lisp do
|
4
|
+
include MicroKanren::Lisp
|
5
|
+
|
6
|
+
describe "#car" do
|
7
|
+
it "returns the first element in the pair" do
|
8
|
+
car(cons(1, 2)).must_equal 1
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#cdr" do
|
13
|
+
it "returns the second item in the pair" do
|
14
|
+
cdr(cons(1, 2)).must_equal 2
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#cons" do
|
19
|
+
it "returns a pair" do
|
20
|
+
car(cons(1, 2)).must_equal 1
|
21
|
+
cdr(cons(1, 2)).must_equal 2
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#assp" do
|
26
|
+
it "returns the first element for which the predicate function is true" do
|
27
|
+
alist = cons(1, cons(2, cons(3, 4)))
|
28
|
+
assp(->(i) { i == 3 }, alist).must_equal 3
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#print_ast" do
|
33
|
+
it "prints a cons cell representation of a simple cell" do
|
34
|
+
print_ast(cons('a', 'b')).must_equal '("a" . "b")'
|
35
|
+
end
|
36
|
+
|
37
|
+
it "represents Integers and Floats" do
|
38
|
+
print_ast(cons(1, 2)).must_equal '(1 . 2)'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "prints a nested expression" do
|
42
|
+
print_ast(cons('a', cons('b', 'c'))).must_equal '("a" . ("b" . "c"))'
|
43
|
+
end
|
44
|
+
|
45
|
+
it "represents Arrays (in scheme, vectors) correctly in printed form" do
|
46
|
+
print_ast(cons('a', [])).must_equal '("a" . [])'
|
47
|
+
end
|
48
|
+
|
49
|
+
it "represents nil elements (in scheme, '())" do
|
50
|
+
print_ast(cons('a', nil)).must_equal '("a" . nil)'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: micro_kanren
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Leitgeb
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mocha
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A port of uKanren to Ruby.
|
56
|
+
email:
|
57
|
+
- justin@stackbuilders.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/micro_kanren.rb
|
69
|
+
- lib/micro_kanren/core.rb
|
70
|
+
- lib/micro_kanren/lisp.rb
|
71
|
+
- lib/micro_kanren/version.rb
|
72
|
+
- micro_kanren.gemspec
|
73
|
+
- spec/micro_kanren/core_spec.rb
|
74
|
+
- spec/micro_kanren/lisp_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
homepage: http://github.com/jsl/ruby_ukanren
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.2.1
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: uKanren in Ruby
|
100
|
+
test_files:
|
101
|
+
- spec/micro_kanren/core_spec.rb
|
102
|
+
- spec/micro_kanren/lisp_spec.rb
|
103
|
+
- spec/spec_helper.rb
|