micro_kanren 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56f3ef35a8ec5353160ad27cc8f00f6515a8f02c
4
- data.tar.gz: 12def3bc0db39c98206386359ae92707209abca2
3
+ metadata.gz: 2a030efb3f7fd274fb7d90dc8d7b6f41c41afa28
4
+ data.tar.gz: 029ae9516a117f18ca88d6b864c9fadfbb7929e3
5
5
  SHA512:
6
- metadata.gz: e82a29102bc5b11474280e2320a8ca3771208859167595dd10b16e1897c705001257bb92006057729f2900f1702974fe9a1eaf3eb2c5c9213bf5d415c4e4f711
7
- data.tar.gz: c09e905aab95d68c69dd2576d39ebc985d53bf9947c36120143b2d00a18a9ad321bc58fa2ab4b30d9f5c57d765c77127c958543a4176d3dbaa360d3057caf640
6
+ metadata.gz: 09d106357a9d89c2015521c5b56561f84cf5dd070c0e95940c73a1832ba46d45c80a92db6d41c480f15fc08aed00b55bb27891aca4c3b2d96fa7956807f4be8a
7
+ data.tar.gz: b282d76b1435bff2969d2de701795c5c2da4ab6003422c362678e08bbf5acaaa090a783499468e7926fe4cea823b6d64e3921622558bd3a1d7fa5af466eb3d5e
@@ -2,5 +2,3 @@ language: ruby
2
2
  rvm:
3
3
  - 2.1.0
4
4
  - 2.0.0
5
- - ruby-head
6
- - jruby-head
data/README.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  A port of microKanren, a minimalistic logic programming language, to Ruby.
4
4
 
5
+ ## Description
6
+
7
+ This is a port of [microKanren](http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf)
8
+ to Ruby. It is an almost exact translation of
9
+ [the original implementation](https://github.com/jasonhemann/microKanren),
10
+ which was written for [Petite Chez Scheme](http://www.scheme.com/petitechezscheme.html).
11
+
5
12
  ## Installation
6
13
 
7
14
  Add this line to your application's Gemfile:
@@ -18,16 +25,23 @@ Or install it yourself as:
18
25
 
19
26
  ## Usage
20
27
 
21
- ```ruby
22
- require 'micro_kanren'
23
- include MicroKanren::Core
28
+ The following example demonstrates how MicroKanren can be used from the console:
24
29
 
25
- call_fresh(-> (q) { eq(q, 5) }).call(empty_state)
30
+ ```ruby
31
+ > require 'micro_kanren'
32
+ > include MicroKanren::Core
33
+ > include MicroKanren::MiniKanrenWrappers
26
34
 
27
- # The result is a set of nested lambda cons cells equivalent to ((([0] . 5 )) . 1)).
35
+ > res = call_fresh(-> (q) { eq(q, 5) }).call(empty_state)
36
+ > puts lprint(res)
37
+ (((([0] . 5)) . 1))
28
38
  ```
29
39
 
30
- See the language_spec for more examples.
40
+ See the
41
+ [spec file](https://github.com/jsl/ruby_ukanren/blob/master/spec/micro_kanren/core_spec.rb)
42
+ for more examples. The spec file is almost an exact port of the [microKanren tests
43
+ written in Scheme](https://github.com/jasonhemann/microKanren/blob/master/microKanren-test.scm).
44
+
31
45
  ## Credits
32
46
 
33
47
  The code in this gem is closely based on the following sources:
@@ -40,8 +54,6 @@ The code in this gem is closely based on the following sources:
40
54
  [Scott Vokes' port of microKanren to Lua](https://github.com/silentbicycle/lua-ukanren).
41
55
  It was great to have the Lua code as a second example of the implementation in
42
56
  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
57
 
46
58
  ## Dependencies
47
59
 
@@ -1,3 +1,5 @@
1
1
  require "micro_kanren/version"
2
2
  require "micro_kanren/lisp"
3
3
  require "micro_kanren/core"
4
+ require "micro_kanren/var"
5
+ require "micro_kanren/mini_kanren_wrappers"
@@ -2,23 +2,46 @@ module MicroKanren
2
2
  module Core
3
3
  include Lisp
4
4
 
5
- def var(*c) ; Array.new(c) ; end
6
- def var?(x) ; x.is_a?(Array) ; end
5
+ def var(*c) ; Var.new(c) ; end
6
+ def var?(x) ; x.is_a?(Var) ; end
7
7
 
8
- def vars_eq?(x1, x2) ; x1 == x2 ; end
8
+ # var=? in Scheme implementation.
9
+ def vars_eq?(x1, x2) ; x1[0] == x2[0] ; end
9
10
 
10
- def mzero ; nil ; end
11
+ # Walk environment S and look up value of U, if present.
12
+ def walk(u, s)
13
+ if var?(u)
14
+ pr = assp(-> (v) { u == v }, s)
15
+ pr ? walk(cdr(pr), s) : u
16
+
17
+ else
18
+ u
19
+ end
20
+ end
11
21
 
12
22
  def ext_s(x, v, s)
13
23
  cons(cons(x, v), s)
14
24
  end
15
25
 
26
+ # Constrain u to be equal to v.
27
+ # == in Scheme implementation, ≡ in uKanren papers.
28
+ def eq(u, v)
29
+ ->(s_c) {
30
+ s = unify(u, v, car(s_c))
31
+ s ? unit(cons(s, cdr(s_c))) : mzero
32
+ }
33
+ end
34
+
35
+ def unit(s_c) ; cons(s_c, mzero) ; end
36
+ def mzero ; nil ; end
37
+
16
38
  def unify(u, v, s)
17
39
  u = walk(u, s)
18
40
  v = walk(v, s)
19
41
 
20
42
  if var?(u) && var?(v) && vars_eq?(u, v)
21
43
  s
44
+
22
45
  elsif var?(u)
23
46
  ext_s(u, v, s)
24
47
 
@@ -26,34 +49,12 @@ module MicroKanren
26
49
  ext_s(v, u, s)
27
50
 
28
51
  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
52
+ s = unify(car(u), car(v), s)
53
+ s && unify(cdr(u), cdr(v), s)
54
54
 
55
55
  else
56
- u
56
+ # Object identity (equal?) seems closest to eqv? in Scheme.
57
+ u.equal?(v) && s
57
58
  end
58
59
  end
59
60
 
@@ -65,10 +66,18 @@ module MicroKanren
65
66
  }
66
67
  end
67
68
 
69
+ def disj(g1, g2)
70
+ -> (s_c) { mplus(g1.call(s_c), g2.call(s_c)) }
71
+ end
72
+
73
+ def conj(g1, g2)
74
+ -> (s_c) { bind(g1.call(s_c), g2) }
75
+ end
76
+
68
77
  def mplus(d1, d2)
69
78
  if d1.nil?
70
79
  d2
71
- elsif d1.is_a?(Proc) && !cons_cell?(d1)
80
+ elsif procedure?(d1)
72
81
  -> { mplus(d2, d1.call) }
73
82
  else
74
83
  cons(car(d1), mplus(cdr(d1), d2))
@@ -78,27 +87,12 @@ module MicroKanren
78
87
  def bind(d, g)
79
88
  if d.nil?
80
89
  mzero
81
- elsif d.is_a?(Proc) && !cons_cell?(d)
90
+ elsif procedure?(d)
82
91
  -> { bind(d.call, g) }
83
92
  else
84
93
  mplus(g.call(car(d)), bind(cdr(d), g))
85
94
  end
86
95
  end
87
96
 
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
97
  end
104
98
  end
@@ -4,15 +4,30 @@ module MicroKanren
4
4
  # Returns a Cons cell that is also marked as such for later identification.
5
5
  def cons(x, y)
6
6
  -> (m) { m.call(x, y) }.tap do |func|
7
- func.instance_eval{ def cons_cell? ; true ; end }
7
+ func.instance_eval{ def ccel? ; true ; end }
8
8
  end
9
9
  end
10
10
 
11
11
  def car(z) ; z.call(-> (p, q) { p }) ; end
12
12
  def cdr(z) ; z.call(-> (p, q) { q }) ; end
13
13
 
14
- def cons_cell?(d)
15
- d.respond_to?(:cons_cell?) && d.cons_cell?
14
+ def cons?(d)
15
+ d.respond_to?(:ccel?) && d.ccel?
16
+ end
17
+ alias :pair? :cons?
18
+
19
+ def map(func, list)
20
+ cons(func.call(car(list)), map(func, cdr(list))) if list
21
+ end
22
+
23
+ def length(list)
24
+ list.nil? ? 0 : 1 + length(cdr(list))
25
+ end
26
+
27
+ # We implement scheme cons cells as Procs. This function returns a boolean
28
+ # identically to the Scheme procedure? function to avoid false positives.
29
+ def procedure?(elt)
30
+ elt.is_a?(Proc) && !cons?(elt)
16
31
  end
17
32
 
18
33
  # Search association list by predicate function.
@@ -22,24 +37,57 @@ module MicroKanren
22
37
  # Additional reference for this function is scheme:
23
38
  # Ref for assp: http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-4.html
24
39
  def assp(func, alist)
25
- if alist && hd = car(alist)
26
- func.call(hd) ? hd : assp(func, cdr(alist))
40
+ if alist
41
+ first_pair = car(alist)
42
+ first_value = car(first_pair)
43
+
44
+ if func.call(first_value)
45
+ first_pair
46
+ else
47
+ assp(func, cdr(alist))
48
+ end
49
+ else
50
+ false
27
51
  end
28
52
  end
29
53
 
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
54
+ # Converts Lisp AST to a String. Algorithm is a recursive implementation of
55
+ # http://www.mat.uc.pt/~pedro/cientificos/funcional/lisp/gcl_22.html#SEC1238.
56
+ def lprint(node, cons_in_cdr = false)
57
+ if cons?(node)
58
+ str = cons_in_cdr ? '' : '('
59
+ str += lprint(car(node))
60
+
61
+ if cons?(cdr(node))
62
+ str += ' ' + lprint(cdr(node), true)
39
63
  else
40
- node.to_s
64
+ str += ' . ' + lprint(cdr(node)) unless cdr(node).nil?
41
65
  end
66
+
67
+ cons_in_cdr ? str : str << ')'
68
+ else
69
+ atom_string(node)
70
+ end
71
+ end
72
+
73
+ def lists_equal?(a, b)
74
+ if cons?(a) && cons?(b)
75
+ lists_equal?(car(a), car(b)) && lists_equal?(cdr(a), cdr(b))
76
+ else
77
+ a == b
42
78
  end
43
79
  end
80
+
81
+ private
82
+
83
+ def atom_string(node)
84
+ case node
85
+ when NilClass, Array, String
86
+ node.inspect
87
+ else
88
+ node.to_s
89
+ end
90
+ end
91
+
44
92
  end
45
93
  end
@@ -0,0 +1,63 @@
1
+ module MicroKanren
2
+ module MiniKanrenWrappers
3
+ include Lisp
4
+
5
+ def empty_state
6
+ cons(mzero, 0)
7
+ end
8
+
9
+ # Advances a stream until it matures. Per microKanren document 5.2, "From
10
+ # Streams to Lists."
11
+ def pull(stream)
12
+ stream.is_a?(Proc) && !cons?(stream) ? pull(stream.call) : stream
13
+ end
14
+
15
+ def take(n, stream)
16
+ if n > 0
17
+ if cur = pull(stream)
18
+ cons(car(cur), take(n - 1, cdr(cur)))
19
+ end
20
+ end
21
+ end
22
+
23
+ def take_all(stream)
24
+ if cur = pull(stream)
25
+ cons(car(cur), take_all(cdr(cur)))
26
+ end
27
+ end
28
+
29
+ def reify_1st(s_c)
30
+ v = walk_star((var 0), car(s_c))
31
+ walk_star(v, reify_s(v, nil))
32
+ end
33
+
34
+ def reify_s(v, s)
35
+ v = walk(v, s)
36
+ if var?(v)
37
+ n = reify_name(length(s))
38
+ cons(cons(v, n), s)
39
+ elsif pair?(v)
40
+ reify_s(cdr(v), reify_s(car(v), s))
41
+ else
42
+ s
43
+ end
44
+ end
45
+
46
+ def reify_name(n)
47
+ "_.#{n}".to_sym
48
+ end
49
+
50
+ def walk_star(v, s)
51
+ v = walk(v, s)
52
+ if var?(v)
53
+ v
54
+ elsif pair?(v)
55
+ cons(walk_star(car(v), s),
56
+ walk_star(cdr(v), s))
57
+ else
58
+ v
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module MicroKanren
2
+ class Var < Array ; end
3
+ end
@@ -1,3 +1,3 @@
1
1
  module MicroKanren
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,61 +1,112 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe MicroKanren::Core do
4
+
4
5
  include MicroKanren::Core
6
+ include MicroKanren::MiniKanrenWrappers
7
+ include MicroKanren::Lisp
8
+
9
+ include MicroKanren::TestPrograms
10
+ include MicroKanren::TestSupport
11
+
12
+ # These tests follow the reference implementation in Scheme located at
13
+ # https://github.com/jasonhemann/microKanren/blob/master/microKanren-test.scm
14
+
15
+ it "second-set t1" do
16
+ res = car(call_fresh(-> (q) { eq(q, 5) }).call(empty_state))
17
+ lprint(res).must_equal '((([0] . 5)) . 1)'
18
+ end
19
+
20
+ it "second-set t2" do
21
+ res = call_fresh(-> (q) { eq(q, 5) }).call(empty_state)
22
+ cdr(res).must_be_nil
23
+ end
24
+
25
+ it "second-set t3" do
26
+ res = car(a_and_b.call(empty_state))
27
+ lprint(res).must_equal '((([1] . 5) ([0] . 7)) . 2)'
28
+ end
29
+
30
+ it "second set t3, take" do
31
+ res = take(1, (a_and_b.call(empty_state)))
32
+ lprint(res).must_equal '(((([1] . 5) ([0] . 7)) . 2))'
33
+ end
34
+
35
+ it "second set t4" do
36
+ res = car(cdr(a_and_b.call(empty_state)))
37
+ lprint(res).must_equal '((([1] . 6) ([0] . 7)) . 2)'
38
+ end
39
+
40
+ it "second set t5" do
41
+ cdr(cdr(a_and_b.call(empty_state))).must_be_nil
42
+ end
43
+
44
+ it "who cares" do
45
+ res = take(1, call_fresh(-> (q) { fives.call(q) }).call(empty_state))
46
+ lprint(res).must_equal '(((([0] . 5)) . 1))'
47
+ end
48
+
49
+ it "take 2 a_and_b stream" do
50
+ res = take(2, a_and_b.call(empty_state))
51
+
52
+ expected_ast_string =
53
+ "(((([1] . 5) ([0] . 7)) . 2) ((([1] . 6) ([0] . 7)) . 2))"
54
+
55
+ lprint(res).must_equal expected_ast_string
56
+ end
5
57
 
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
58
+ it "take_all a_and_b stream" do
59
+ res = take_all(a_and_b.call(empty_state))
60
+
61
+ expected_ast_string =
62
+ "(((([1] . 5) ([0] . 7)) . 2) ((([1] . 6) ([0] . 7)) . 2))"
63
+
64
+ lprint(res).must_equal expected_ast_string
65
+ end
66
+
67
+ it "ground appendo" do
68
+ res = car(ground_appendo.call(empty_state).call)
69
+
70
+ # Expected result in scheme:
71
+ # (((#(2) b) (#(1)) (#(0) . a)) . 3)
72
+
73
+ expected_ast_string =
74
+ '((([2] b) ([1]) ([0] . a)) . 3)'
75
+
76
+ lprint(res).must_equal expected_ast_string
77
+ end
78
+
79
+ it "ground appendo2" do
80
+ res = lprint(car(ground_appendo2.call(empty_state).call))
81
+ res.must_equal '((([2] b) ([1]) ([0] . a)) . 3)'
82
+ end
83
+
84
+ it "appendo" do
85
+ res = lprint(take(2, call_appendo.call(empty_state)))
86
+ res.must_equal '(((([0] [1] [2] [3]) ([2] . [3]) ([1])) . 4) ((([0] [1] [2] [3]) ([2] . [6]) ([5]) ([3] [4] . [6]) ([1] [4] . [5])) . 7))'
87
+ end
88
+
89
+ it "appendo2" do
90
+ res = lprint(take(2, call_appendo2.call(empty_state)))
91
+ res.must_equal '(((([0] [1] [2] [3]) ([2] . [3]) ([1])) . 4) ((([0] [1] [2] [3]) ([3] [4] . [6]) ([2] . [6]) ([5]) ([1] [4] . [5])) . 7))'
92
+ end
93
+
94
+ it "reify-1st across appendo" do
95
+ res = map(method(:reify_1st).to_proc, take(2, call_appendo.call(empty_state)))
96
+
97
+ # Expected result in scheme:
98
+ # ((() _.0 _.0) ((_.0) _.1 (_.0 . _.1)))
99
+
100
+ lprint(res).must_equal '((nil _.0 _.0) ((_.0) _.1 (_.0 . _.1)))'
101
+ end
102
+
103
+ it "reify-1st across appendo2" do
104
+ res = map(method(:reify_1st).to_proc, take(2, call_appendo2.call(empty_state)))
105
+ lprint(res).must_equal '((nil _.0 _.0) ((_.0) _.1 (_.0 . _.1)))'
106
+ end
59
107
 
108
+ it "#many non-ans" do
109
+ res = take(1, many_non_ans.call(empty_state))
110
+ lprint(res).must_equal '(((([0] . 3)) . 1))'
60
111
  end
61
112
  end
@@ -22,32 +22,100 @@ describe MicroKanren::Lisp do
22
22
  end
23
23
  end
24
24
 
25
+ describe "#length" do
26
+ it "returns 0 for an empty list" do
27
+ length(nil).must_equal 0
28
+ end
29
+
30
+ it "returns the list length for a non-empty list" do
31
+ length(cons(1, cons(2, nil))).must_equal 2
32
+ end
33
+ end
34
+
35
+ describe "#map" do
36
+ it "maps a function over a list" do
37
+ func = -> (str) { str.upcase }
38
+ lprint(map(func, cons("foo", cons("bar", nil)))).must_equal '("FOO" "BAR")'
39
+ end
40
+ end
41
+
25
42
  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
43
+ it "returns the first pair for which the predicate function is true" do
44
+ al1 = cons(3, cons(:a, nil))
45
+ al2 = cons(1, cons(:b, nil))
46
+ al3 = cons(4, cons(:c, nil))
47
+
48
+ alist = cons(al1, cons(al2, cons(al3, nil)))
49
+
50
+ res = assp(->(i) { i.even? }, alist)
51
+ lists_equal?(res, cons(4, cons(:c, nil))).must_equal true
52
+ end
53
+
54
+ it "returns false if there is no matching element found" do
55
+ pair1 = cons(3, cons(:a, nil))
56
+ pair2 = cons(1, cons(:b, nil))
57
+ pair3 = cons(4, cons(:c, nil))
58
+
59
+ alist = cons(pair1, cons(pair2, cons(pair3, nil)))
60
+
61
+ res = assp(->(i) { i == 5 }, alist)
62
+ res.must_equal false
63
+ end
64
+ end
65
+
66
+ # http://download.plt-scheme.org/doc/html/reference/pairs.html#(def._((quote._~23~25kernel)._pair~3f))
67
+ describe "#pair?" do
68
+ it "is false for an integer" do
69
+ pair?(1).must_equal false
70
+ end
71
+
72
+ it "is true for a list with an int in the car and cdr" do
73
+ pair?(cons(1, 2)).must_equal true
74
+ end
75
+
76
+ it "is true for a proper list" do
77
+ pair?(cons(1, cons(2, nil))).must_equal true
78
+ end
79
+
80
+ it "is false for an empty list" do
81
+ pair?(nil).must_equal false
29
82
  end
30
83
  end
31
84
 
32
- describe "#print_ast" do
85
+ describe "#lprint" do
86
+ it "prints an expression correctly" do
87
+ c = cons(1, cons(2, cons(cons(3, cons(4, nil)), cons(5, nil))))
88
+ lprint(c).must_equal "(1 2 (3 4) 5)"
89
+ end
90
+
33
91
  it "prints a cons cell representation of a simple cell" do
34
- print_ast(cons('a', 'b')).must_equal '("a" . "b")'
92
+ lprint(cons('a', 'b')).must_equal '("a" . "b")'
35
93
  end
36
94
 
37
95
  it "represents Integers and Floats" do
38
- print_ast(cons(1, 2)).must_equal '(1 . 2)'
96
+ lprint(cons(1, 2)).must_equal '(1 . 2)'
39
97
  end
40
98
 
41
99
  it "prints a nested expression" do
42
- print_ast(cons('a', cons('b', 'c'))).must_equal '("a" . ("b" . "c"))'
100
+ lprint(cons('a', cons('b', 'c'))).must_equal '("a" "b" . "c")'
43
101
  end
44
102
 
45
103
  it "represents Arrays (in scheme, vectors) correctly in printed form" do
46
- print_ast(cons('a', [])).must_equal '("a" . [])'
104
+ lprint(cons('a', [])).must_equal '("a" . [])'
47
105
  end
48
106
 
49
107
  it "represents nil elements (in scheme, '())" do
50
- print_ast(cons('a', nil)).must_equal '("a" . nil)'
108
+ lprint(cons('a', nil)).must_equal '("a")'
109
+ end
110
+ end
111
+
112
+ describe "#lists_equal?" do
113
+ it "is true if the lists are equal" do
114
+ lists_equal?(cons(1, cons(2, nil)), cons(1, cons(2, nil))).must_equal true
115
+ end
116
+
117
+ it "is false if the lists contain different objects" do
118
+ lists_equal?(cons(1, cons(2, nil)), cons(1, nil)).must_equal false
51
119
  end
52
120
  end
53
121
  end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe MicroKanren::MiniKanrenWrappers do
4
+ include MicroKanren::MiniKanrenWrappers
5
+ include MicroKanren::Lisp
6
+
7
+ describe "#pull" do
8
+ it "advances the stream until it matures" do
9
+ stream = -> { -> { 42 } }
10
+ pull(stream).must_equal 42
11
+ end
12
+
13
+ it "returns nil in the case of the empty stream" do
14
+ pull(nil).must_be_nil
15
+ end
16
+ end
17
+ end
@@ -3,3 +3,6 @@ require 'minitest/autorun'
3
3
  require "mocha"
4
4
 
5
5
  require "#{File.dirname(__FILE__)}/../lib/micro_kanren"
6
+
7
+ require "#{File.dirname(__FILE__)}/test_programs"
8
+ require "#{File.dirname(__FILE__)}/test_support"
@@ -0,0 +1,100 @@
1
+ module MicroKanren
2
+ module TestPrograms
3
+ def a_and_b
4
+ a = -> (a) { eq(a, 7) }
5
+ b = -> (b) { disj(eq(b, 5), eq(b, 6)) }
6
+
7
+ conj(call_fresh(a), call_fresh(b))
8
+ end
9
+
10
+ def fives
11
+ -> (x) {
12
+ disj(eq(x, 5), -> (a_c) { -> { fives(x).call(a_c) } })
13
+ }
14
+ end
15
+
16
+ def appendo
17
+ -> (l, s, out) {
18
+ disj(
19
+ conj(eq(nil, l), eq(s, out)),
20
+ call_fresh(-> (a) {
21
+ call_fresh(-> (d) {
22
+ conj(
23
+ eq(cons(a, d), l),
24
+ call_fresh(-> (res) {
25
+ conj(
26
+ eq(cons(a, res), out),
27
+ -> (s_c) {
28
+ -> {appendo.call(d, s, res).call(s_c)}})}))})}))}
29
+ end
30
+
31
+ def appendo2
32
+ -> (l, s, out) {
33
+ disj(
34
+ conj(eq(nil, l), eq(s, out)),
35
+ call_fresh(-> (a) {
36
+ call_fresh(-> (d) {
37
+ conj(
38
+ eq(cons(a, d), l),
39
+ call_fresh(-> (res) {
40
+ conj(
41
+ -> (s_c) {
42
+ -> { appendo2.call(d, s, res).call(s_c) }
43
+ },
44
+ eq(cons(a, res), out))}))})}))}
45
+ end
46
+
47
+ def call_appendo
48
+ call_fresh(-> (q) {
49
+ call_fresh(-> (l) {
50
+ call_fresh(-> (s) {
51
+ call_fresh(-> (out) {
52
+ conj(
53
+ appendo.call(l, s, out),
54
+ eq(cons(l, cons(s, cons(out, nil))), q))})})})})
55
+ end
56
+
57
+ def call_appendo2
58
+ call_fresh(-> (q) {
59
+ call_fresh(-> (l) {
60
+ call_fresh(-> (s) {
61
+ call_fresh(-> (out) {
62
+ conj(
63
+ appendo2.call(l, s, out),
64
+ eq(cons(l, cons(s, cons(out, nil))), q))})})})})
65
+ end
66
+
67
+ def ground_appendo
68
+ appendo.call(cons(:a, nil), cons(:b, nil), cons(:a, cons(:b, nil)))
69
+ end
70
+
71
+ def ground_appendo2
72
+ appendo2.call(cons(:a, nil), cons(:b, nil), cons(:a, cons(:b, nil)))
73
+ end
74
+
75
+ def relo
76
+ -> (x) {
77
+ call_fresh(-> (x1) {
78
+ call_fresh(-> (x2) {
79
+ conj(
80
+ eq(x, cons(x1, x2)),
81
+ disj(
82
+ eq(x1, x2),
83
+ -> (s_c) {
84
+ -> { relo.call(x).call(s_c) }
85
+ }
86
+ )
87
+ )
88
+ })
89
+ })
90
+ }
91
+ end
92
+
93
+ def many_non_ans
94
+ call_fresh(-> (x) {
95
+ disj(
96
+ relo.call(cons(5, 6)),
97
+ eq(x, 3))})
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,9 @@
1
+ module MicroKanren
2
+ module TestSupport
3
+
4
+ # Shorthand for declaring a new logic variable.
5
+ def uvar(v)
6
+ MicroKanren::Var.new([v])
7
+ end
8
+ end
9
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: micro_kanren
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Leitgeb
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-25 00:00:00.000000000 Z
11
+ date: 2014-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -68,11 +68,16 @@ files:
68
68
  - lib/micro_kanren.rb
69
69
  - lib/micro_kanren/core.rb
70
70
  - lib/micro_kanren/lisp.rb
71
+ - lib/micro_kanren/mini_kanren_wrappers.rb
72
+ - lib/micro_kanren/var.rb
71
73
  - lib/micro_kanren/version.rb
72
74
  - micro_kanren.gemspec
73
75
  - spec/micro_kanren/core_spec.rb
74
76
  - spec/micro_kanren/lisp_spec.rb
77
+ - spec/micro_kanren/mini_kanren_wrappers_spec.rb
75
78
  - spec/spec_helper.rb
79
+ - spec/test_programs.rb
80
+ - spec/test_support.rb
76
81
  homepage: http://github.com/jsl/ruby_ukanren
77
82
  licenses:
78
83
  - MIT
@@ -100,4 +105,7 @@ summary: uKanren in Ruby
100
105
  test_files:
101
106
  - spec/micro_kanren/core_spec.rb
102
107
  - spec/micro_kanren/lisp_spec.rb
108
+ - spec/micro_kanren/mini_kanren_wrappers_spec.rb
103
109
  - spec/spec_helper.rb
110
+ - spec/test_programs.rb
111
+ - spec/test_support.rb