micro_kanren 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/jsl/ruby_ukanren.png)](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
|