kanren 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67670e33fe713a422e5149f2f01748851540d2ac
4
+ data.tar.gz: d1e95c8070cea4b57c24e87d5f5abd76aca0eb05
5
+ SHA512:
6
+ metadata.gz: 4400e2f646e4543204997c48cd9d7e88313349f279e2d643e71dd45b9ad6560b33c028bdd5b5baa9c714fbf1d025a0c8e80caf1888dafcc17258711ce837be58
7
+ data.tar.gz: 18b82172ef9564b0177aa6747554582f944e158ba9a8ed8a8de571ea170387415d1633358c7b7820c3d1bca39a05cfcd8ac268df75ed7e96dd5957e61c6193dc
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,116 @@
1
+ CC0 1.0 Universal
2
+
3
+ Statement of Purpose
4
+
5
+ The laws of most jurisdictions throughout the world automatically confer
6
+ exclusive Copyright and Related Rights (defined below) upon the creator and
7
+ subsequent owner(s) (each and all, an "owner") of an original work of
8
+ authorship and/or a database (each, a "Work").
9
+
10
+ Certain owners wish to permanently relinquish those rights to a Work for the
11
+ purpose of contributing to a commons of creative, cultural and scientific
12
+ works ("Commons") that the public can reliably and without fear of later
13
+ claims of infringement build upon, modify, incorporate in other works, reuse
14
+ and redistribute as freely as possible in any form whatsoever and for any
15
+ purposes, including without limitation commercial purposes. These owners may
16
+ contribute to the Commons to promote the ideal of a free culture and the
17
+ further production of creative, cultural and scientific works, or to gain
18
+ reputation or greater distribution for their Work in part through the use and
19
+ efforts of others.
20
+
21
+ For these and/or other purposes and motivations, and without any expectation
22
+ of additional consideration or compensation, the person associating CC0 with a
23
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25
+ and publicly distribute the Work under its terms, with knowledge of his or her
26
+ Copyright and Related Rights in the Work and the meaning and intended legal
27
+ effect of CC0 on those rights.
28
+
29
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
30
+ protected by copyright and related or neighboring rights ("Copyright and
31
+ Related Rights"). Copyright and Related Rights include, but are not limited
32
+ to, the following:
33
+
34
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
35
+ and translate a Work;
36
+
37
+ ii. moral rights retained by the original author(s) and/or performer(s);
38
+
39
+ iii. publicity and privacy rights pertaining to a person's image or likeness
40
+ depicted in a Work;
41
+
42
+ iv. rights protecting against unfair competition in regards to a Work,
43
+ subject to the limitations in paragraph 4(a), below;
44
+
45
+ v. rights protecting the extraction, dissemination, use and reuse of data in
46
+ a Work;
47
+
48
+ vi. database rights (such as those arising under Directive 96/9/EC of the
49
+ European Parliament and of the Council of 11 March 1996 on the legal
50
+ protection of databases, and under any national implementation thereof,
51
+ including any amended or successor version of such directive); and
52
+
53
+ vii. other similar, equivalent or corresponding rights throughout the world
54
+ based on applicable law or treaty, and any national implementations thereof.
55
+
56
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59
+ and Related Rights and associated claims and causes of action, whether now
60
+ known or unknown (including existing as well as future claims and causes of
61
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
62
+ duration provided by applicable law or treaty (including future time
63
+ extensions), (iii) in any current or future medium and for any number of
64
+ copies, and (iv) for any purpose whatsoever, including without limitation
65
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66
+ the Waiver for the benefit of each member of the public at large and to the
67
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
68
+ shall not be subject to revocation, rescission, cancellation, termination, or
69
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
70
+ by the public as contemplated by Affirmer's express Statement of Purpose.
71
+
72
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
73
+ judged legally invalid or ineffective under applicable law, then the Waiver
74
+ shall be preserved to the maximum extent permitted taking into account
75
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
77
+ non transferable, non sublicensable, non exclusive, irrevocable and
78
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
79
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
80
+ provided by applicable law or treaty (including future time extensions), (iii)
81
+ in any current or future medium and for any number of copies, and (iv) for any
82
+ purpose whatsoever, including without limitation commercial, advertising or
83
+ promotional purposes (the "License"). The License shall be deemed effective as
84
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
85
+ License for any reason be judged legally invalid or ineffective under
86
+ applicable law, such partial invalidity or ineffectiveness shall not
87
+ invalidate the remainder of the License, and in such case Affirmer hereby
88
+ affirms that he or she will not (i) exercise any of his or her remaining
89
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
90
+ and causes of action with respect to the Work, in either case contrary to
91
+ Affirmer's express Statement of Purpose.
92
+
93
+ 4. Limitations and Disclaimers.
94
+
95
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
96
+ surrendered, licensed or otherwise affected by this document.
97
+
98
+ b. Affirmer offers the Work as-is and makes no representations or warranties
99
+ of any kind concerning the Work, express, implied, statutory or otherwise,
100
+ including without limitation warranties of title, merchantability, fitness
101
+ for a particular purpose, non infringement, or the absence of latent or
102
+ other defects, accuracy, or the present or absence of errors, whether or not
103
+ discoverable, all to the greatest extent permissible under applicable law.
104
+
105
+ c. Affirmer disclaims responsibility for clearing rights of other persons
106
+ that may apply to the Work or any use thereof, including without limitation
107
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
108
+ disclaims responsibility for obtaining any necessary consents, permissions
109
+ or other rights required for any use of the Work.
110
+
111
+ d. Affirmer understands and acknowledges that Creative Commons is not a
112
+ party to this document and has no duty or obligation with respect to this
113
+ CC0 or use of the Work.
114
+
115
+ For more information, please see
116
+ <http://creativecommons.org/publicdomain/zero/1.0/>
@@ -0,0 +1,60 @@
1
+ # Kanren
2
+
3
+ This library provides an example Ruby implementation of [μKanren](http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf), along with some simple data structures and relations. It is intended to accompany the article [“Hello, declarative world”](http://codon.com/hello-declarative-world).
4
+
5
+ That article explains the details, but here’s a brief demonstration:
6
+
7
+ ```irb
8
+ $ irb -Ilib
9
+ >> require 'kanren/micro'
10
+ => true
11
+ >> include Kanren::Micro
12
+ => Object
13
+
14
+ >> goal = Goal.with_variables { |x, y|
15
+ Goal.either(Goal.equal(x, 1), Goal.equal(y, 2))
16
+ }
17
+ => #<Kanren::Micro::Goal …>
18
+ >> states = goal.pursue_in(State.new)
19
+ => #<Enumerator: …>
20
+ >> states.next.values
21
+ => {x=>1}
22
+ >> states.next.values
23
+ => {y=>2}
24
+ >> states.next.values
25
+ StopIteration: iteration reached an end
26
+
27
+ >> include Kanren
28
+ => Object
29
+
30
+ >> goal = Goal.with_variables { |x|
31
+ Relations.multiply(Peano.from_integer(3), Peano.from_integer(4), x)
32
+ }
33
+ => #<Kanren::Micro::Goal …>
34
+ >> states = goal.pursue_in(State.new)
35
+ => #<Enumerator: …>
36
+ >> Peano.to_integer(states.next.result)
37
+ => 12
38
+
39
+ >> goal = Goal.with_variables { |x, y|
40
+ Relations.add(x, y, Peano.from_integer(8))
41
+ }
42
+ => #<Kanren::Micro::Goal …>
43
+ >> states = goal.pursue_in(State.new)
44
+ => #<Enumerator: …>
45
+ >> states.each do |state|
46
+ p state.results(2).map { |peano| Peano.to_integer(peano) }
47
+ end
48
+ [0, 8]
49
+ [1, 7]
50
+ [2, 6]
51
+ [3, 5]
52
+ [4, 4]
53
+ [5, 3]
54
+ [6, 2]
55
+ [7, 1]
56
+ [8, 0]
57
+ => nil
58
+ ```
59
+
60
+ If you have any questions, please get in touch via [Twitter](http://twitter.com/tomstuart) or [email](mailto:tom@codon.com). If you find any bugs or other problems with the code, please [open an issue](https://github.com/tomstuart/kanren/issues/new).
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'kanren/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'kanren'
7
+ spec.version = Kanren::VERSION
8
+ spec.author = 'Tom Stuart'
9
+ spec.email = 'tom@codon.com'
10
+
11
+ spec.summary = 'An example Ruby implementation of μKanren.'
12
+ spec.homepage = 'https://github.com/tomstuart/kanren'
13
+ spec.license = 'CC0-1.0'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'rspec', '~> 3.3', '>= 3.3.0'
20
+ end
@@ -0,0 +1,6 @@
1
+ require 'kanren/list'
2
+ require 'kanren/micro'
3
+ require 'kanren/pair'
4
+ require 'kanren/peano'
5
+ require 'kanren/utils'
6
+ require 'kanren/version'
@@ -0,0 +1,35 @@
1
+ require 'kanren/pair'
2
+
3
+ module Kanren
4
+ module List
5
+ EMPTY = Object.new
6
+
7
+ module_function
8
+
9
+ def from_array(array)
10
+ if array.empty?
11
+ EMPTY
12
+ else
13
+ first, *rest = array
14
+ Pair.new(first, from_array(rest))
15
+ end
16
+ end
17
+
18
+ def to_array(list)
19
+ if list == EMPTY
20
+ []
21
+ else
22
+ first, rest = list.left, list.right
23
+ [first, *to_array(rest)]
24
+ end
25
+ end
26
+
27
+ def to_list(array)
28
+ from_array array
29
+ end
30
+
31
+ def from_list(list)
32
+ to_array list
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,4 @@
1
+ require 'kanren/micro/goal'
2
+ require 'kanren/micro/relations'
3
+ require 'kanren/micro/state'
4
+ require 'kanren/micro/variable'
@@ -0,0 +1,62 @@
1
+ require 'kanren/utils'
2
+
3
+ module Kanren
4
+ module Micro
5
+ class Goal
6
+ def initialize(&block)
7
+ @block = block
8
+ end
9
+
10
+ def pursue_in(state)
11
+ @block.call state
12
+ end
13
+
14
+ def pursue_in_each(states)
15
+ Enumerator.new do |yielder|
16
+ results = pursue_in(states.next)
17
+ results = Utils.interleave(results, pursue_in_each(states))
18
+
19
+ results.each do |state|
20
+ yielder.yield state
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.equal(a, b)
26
+ new do |state|
27
+ state = state.unify(a, b)
28
+
29
+ Enumerator.new do |yielder|
30
+ yielder.yield state if state
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.with_variables(&block)
36
+ names = block.parameters.map { |type, name| name }
37
+
38
+ new do |state|
39
+ state, variables = state.create_variables(names)
40
+ goal = block.call(*variables)
41
+ goal.pursue_in state
42
+ end
43
+ end
44
+
45
+ def self.either(first_goal, second_goal)
46
+ new do |state|
47
+ first_stream = first_goal.pursue_in(state)
48
+ second_stream = second_goal.pursue_in(state)
49
+
50
+ Utils.interleave first_stream, second_stream
51
+ end
52
+ end
53
+
54
+ def self.both(first_goal, second_goal)
55
+ new do |state|
56
+ states = first_goal.pursue_in(state)
57
+ second_goal.pursue_in_each states
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,66 @@
1
+ require 'kanren/list'
2
+ require 'kanren/micro/goal'
3
+ require 'kanren/pair'
4
+ require 'kanren/peano'
5
+
6
+ module Kanren
7
+ module Micro
8
+ module Relations
9
+ module_function
10
+
11
+ def append(a, b, c)
12
+ Goal.either(
13
+ Goal.both(
14
+ Goal.equal(a, List::EMPTY),
15
+ Goal.equal(b, c)
16
+ ),
17
+ Goal.with_variables { |first, rest_of_a, rest_of_c|
18
+ Goal.both(
19
+ Goal.both(
20
+ Goal.equal(a, Pair.new(first, rest_of_a)),
21
+ Goal.equal(c, Pair.new(first, rest_of_c))
22
+ ),
23
+ append(rest_of_a, b, rest_of_c)
24
+ )
25
+ }
26
+ )
27
+ end
28
+
29
+ def add(x, y, z)
30
+ Goal.either(
31
+ Goal.both(
32
+ Goal.equal(x, Peano::ZERO),
33
+ Goal.equal(y, z)
34
+ ),
35
+ Goal.with_variables { |smaller_x, smaller_z|
36
+ Goal.both(
37
+ Goal.both(
38
+ Goal.equal(x, Pair.new(Peano::SUCC, smaller_x)),
39
+ Goal.equal(z, Pair.new(Peano::SUCC, smaller_z))
40
+ ),
41
+ add(smaller_x, y, smaller_z)
42
+ )
43
+ }
44
+ )
45
+ end
46
+
47
+ def multiply(x, y, z)
48
+ Goal.either(
49
+ Goal.both(
50
+ Goal.equal(x, Peano::ZERO),
51
+ Goal.equal(z, Peano::ZERO)
52
+ ),
53
+ Goal.with_variables { |smaller_x, smaller_z|
54
+ Goal.both(
55
+ Goal.both(
56
+ Goal.equal(x, Pair.new(Peano::SUCC, smaller_x)),
57
+ add(smaller_z, y, z)
58
+ ),
59
+ multiply(smaller_x, y, smaller_z)
60
+ )
61
+ }
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,56 @@
1
+ require 'kanren/micro/variable'
2
+ require 'kanren/pair'
3
+
4
+ module Kanren
5
+ module Micro
6
+ class State
7
+ def initialize(variables = [], values = {})
8
+ @variables, @values = variables, values
9
+ end
10
+
11
+ attr_reader :variables, :values
12
+
13
+ def create_variables(names)
14
+ new_variables = names.map { |name| Variable.new(name) }
15
+ [State.new(variables + new_variables, values), new_variables]
16
+ end
17
+
18
+ def assign_values(new_values)
19
+ State.new(variables, values.merge(new_values))
20
+ end
21
+
22
+ def value_of(key)
23
+ if values.has_key?(key)
24
+ value_of values.fetch(key)
25
+ elsif key.is_a?(Pair)
26
+ Pair.new(value_of(key.left), value_of(key.right))
27
+ else
28
+ key
29
+ end
30
+ end
31
+
32
+ def unify(a, b)
33
+ a, b = value_of(a), value_of(b)
34
+
35
+ if a == b
36
+ self
37
+ elsif a.is_a?(Variable)
38
+ assign_values a => b
39
+ elsif b.is_a?(Variable)
40
+ assign_values b => a
41
+ elsif a.is_a?(Pair) && b.is_a?(Pair)
42
+ state = unify(a.left, b.left)
43
+ state.unify(a.right, b.right) if state
44
+ end
45
+ end
46
+
47
+ def results(n)
48
+ variables.first(n).map { |variable| value_of(variable) }
49
+ end
50
+
51
+ def result
52
+ results(1).first
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ module Kanren
2
+ module Micro
3
+ class Variable
4
+ def initialize(name)
5
+ @name = name
6
+ end
7
+
8
+ def inspect
9
+ @name.to_s
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module Kanren
2
+ Pair = Struct.new(:left, :right) do
3
+ def inspect
4
+ "(#{left.inspect}, #{right.inspect})"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ require 'kanren/pair'
2
+
3
+ module Kanren
4
+ module Peano
5
+ ZERO, SUCC = 2.times.map { Object.new }
6
+
7
+ module_function
8
+
9
+ def from_integer(integer)
10
+ if integer.zero?
11
+ ZERO
12
+ else
13
+ Pair.new(SUCC, from_integer(integer.pred))
14
+ end
15
+ end
16
+
17
+ def to_integer(peano)
18
+ if peano == ZERO
19
+ 0
20
+ else
21
+ to_integer(peano.right).succ
22
+ end
23
+ end
24
+
25
+ def to_peano(integer)
26
+ from_integer integer
27
+ end
28
+
29
+ def from_peano(peano)
30
+ to_integer peano
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ module Kanren
2
+ module Utils
3
+ module_function
4
+
5
+ def interleave(*enumerators)
6
+ Enumerator.new do |yielder|
7
+ until enumerators.empty?
8
+ loop do
9
+ enumerator = enumerators.shift
10
+ yielder.yield enumerator.next
11
+ enumerators.push enumerator
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Kanren
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,69 @@
1
+ require 'kanren/list'
2
+ require 'kanren/micro/goal'
3
+ require 'kanren/micro/relations'
4
+ require 'kanren/micro/state'
5
+
6
+ module Kanren
7
+ module Micro
8
+ RSpec.describe 'relations on lists' do
9
+ describe 'equality' do
10
+ it 'allows two lists to be unified' do
11
+ goal = Goal.with_variables { |x, y, z| Goal.equal(List.from_array([x, 2, z]), List.from_array([1, y, 3])) }
12
+ states = goal.pursue_in(State.new)
13
+
14
+ state = states.next
15
+ x, y, z = state.variables
16
+ expect(state.values).to eq x => 1, y => 2, z => 3
17
+
18
+ expect { states.next }.to raise_error StopIteration
19
+ end
20
+
21
+ describe 'append' do
22
+ it 'appends two lists' do
23
+ goal =
24
+ Goal.with_variables { |x|
25
+ Relations.append(List.from_array(%w(h e)), List.from_array(%w(l l o)), x)
26
+ }
27
+ states = goal.pursue_in(State.new)
28
+
29
+ results = states.map { |state| List.to_array(state.result) }
30
+ expect(results).to eq [
31
+ %w(h e l l o)
32
+ ]
33
+ end
34
+
35
+ it 'finds the prefix which makes one list equal to another' do
36
+ goal =
37
+ Goal.with_variables { |x|
38
+ Relations.append(x, List.from_array(%w(l o)), List.from_array(%w(h e l l o)))
39
+ }
40
+ states = goal.pursue_in(State.new)
41
+
42
+ results = states.map { |state| List.to_array(state.result) }
43
+ expect(results).to eq [
44
+ %w(h e l)
45
+ ]
46
+ end
47
+
48
+ it 'finds all pairs of lists which are equal to another when appended' do
49
+ goal =
50
+ Goal.with_variables { |x, y|
51
+ Relations.append(x, y, List.from_array(%w(h e l l o)))
52
+ }
53
+ states = goal.pursue_in(State.new)
54
+
55
+ results = states.map { |state| state.results(2).map(&List.method(:to_array)) }
56
+ expect(results).to eq [
57
+ [%w(), %w(h e l l o)],
58
+ [%w(h), %w(e l l o)],
59
+ [%w(h e), %w(l l o)],
60
+ [%w(h e l), %w(l o)],
61
+ [%w(h e l l), %w(o)],
62
+ [%w(h e l l o), %w()]
63
+ ]
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,85 @@
1
+ require 'kanren/micro/goal'
2
+ require 'kanren/micro/relations'
3
+ require 'kanren/micro/state'
4
+ require 'kanren/peano'
5
+
6
+ module Kanren
7
+ module Micro
8
+ module Data
9
+ RSpec.describe 'relations on numbers' do
10
+ describe 'addition' do
11
+ it 'adds two numbers' do
12
+ goal = Goal.with_variables { |x|
13
+ Relations.add(Peano.from_integer(5), Peano.from_integer(3), x)
14
+ }
15
+ states = goal.pursue_in(State.new)
16
+
17
+ results = states.map { |state| Peano.to_integer(state.result) }
18
+ expect(results).to eq [8]
19
+ end
20
+
21
+ it 'subtracts two numbers' do
22
+ goal = Goal.with_variables { |x|
23
+ Relations.add(x, Peano.from_integer(3), Peano.from_integer(8))
24
+ }
25
+ states = goal.pursue_in(State.new)
26
+
27
+ results = states.map { |state| Peano.to_integer(state.result) }
28
+ expect(results).to eq [5]
29
+ end
30
+
31
+ it 'finds all pairs of numbers which are equal to another when added' do
32
+ goal = Goal.with_variables { |x, y|
33
+ Relations.add(x, y, Peano.from_integer(8))
34
+ }
35
+ states = goal.pursue_in(State.new)
36
+
37
+ results = states.map { |state| state.results(2).map(&Peano.method(:to_integer)) }
38
+ expect(results).to eq [
39
+ [0, 8],
40
+ [1, 7],
41
+ [2, 6],
42
+ [3, 5],
43
+ [4, 4],
44
+ [5, 3],
45
+ [6, 2],
46
+ [7, 1],
47
+ [8, 0]
48
+ ]
49
+ end
50
+ end
51
+
52
+ describe 'multiplication' do
53
+ it 'mutiplies two numbers' do
54
+ goal = Goal.with_variables { |x|
55
+ Relations.multiply(Peano.from_integer(3), Peano.from_integer(8), x)
56
+ }
57
+ states = goal.pursue_in(State.new)
58
+
59
+ results = states.take(1).map { |state| Peano.to_integer(state.result) }
60
+ expect(results).to eq [24]
61
+ end
62
+
63
+ it 'finds all pairs of numbers which are equal to another when multiplied' do
64
+ goal = Goal.with_variables { |x, y|
65
+ Relations.multiply(x, y, Peano.from_integer(24))
66
+ }
67
+ states = goal.pursue_in(State.new)
68
+
69
+ results = states.take(8).map { |state| state.results(2).map(&Peano.method(:to_integer)) }
70
+ expect(results).to eq [
71
+ [1, 24],
72
+ [2, 12],
73
+ [3, 8],
74
+ [4, 6],
75
+ [6, 4],
76
+ [8, 3],
77
+ [12, 2],
78
+ [24, 1]
79
+ ]
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ RSpec.configure do |config|
2
+ config.disable_monkey_patching!
3
+ end
@@ -0,0 +1,113 @@
1
+ require 'kanren/micro/goal'
2
+ require 'kanren/micro/state'
3
+
4
+ module Kanren
5
+ module Micro
6
+ RSpec.describe Goal do
7
+ describe '.new' do
8
+ it 'doesn’t immediately yield to the block' do
9
+ expect { |block| Goal.new(&block) }.not_to yield_control
10
+ end
11
+ end
12
+
13
+ describe '#pursue_in' do
14
+ let(:state) { double }
15
+
16
+ it 'yields the state to the block' do
17
+ expect { |block| Goal.new(&block).pursue_in(state) }.to yield_with_args(state)
18
+ end
19
+ end
20
+
21
+ describe '#pursue_in_each' do
22
+ let(:a) { double }
23
+ let(:b) { double }
24
+ let(:c) { double }
25
+ let(:a_results) { 3.times.map { double }.to_enum }
26
+ let(:b_results) { 4.times.map { double }.to_enum }
27
+ let(:c_results) { 5.times.map { double }.to_enum }
28
+ let(:goal) { Goal.new }
29
+
30
+ it 'pursues the goal in each state and interleaves the results' do
31
+ allow(goal).to receive(:pursue_in).with(a).and_return a_results
32
+ allow(goal).to receive(:pursue_in).with(b).and_return b_results
33
+ allow(goal).to receive(:pursue_in).with(c).and_return c_results
34
+ results = goal.pursue_in_each([a, b, c].to_enum)
35
+ expect(results).to contain_exactly *a_results, *b_results, *c_results
36
+ end
37
+ end
38
+
39
+ describe '.equal' do
40
+ it 'unifies its arguments' do
41
+ state, (x, y, z) = State.new.create_variables [:x, :y, :z]
42
+ goal = Goal.equal(x, 5)
43
+ states = goal.pursue_in(state)
44
+ state = states.next
45
+ expect(state.values).to eq x => 5
46
+ expect { states.next }.to raise_error StopIteration
47
+ end
48
+ end
49
+
50
+ describe '.with_variables' do
51
+ it 'introduces local variables' do
52
+ goal = Goal.with_variables { |x| Goal.equal x, 5 }
53
+ states = goal.pursue_in(State.new)
54
+ state = states.next
55
+ x = state.variables.first
56
+ expect(state.values).to eq x => 5
57
+ expect { states.next }.to raise_error StopIteration
58
+ end
59
+ end
60
+
61
+ describe '.either' do
62
+ it 'satisfies either subgoal' do
63
+ goal = Goal.with_variables { |x| Goal.either Goal.equal(x, 5), Goal.equal(x, 6) }
64
+ states = goal.pursue_in(State.new)
65
+
66
+ state = states.next
67
+ x = state.variables.first
68
+ expect(state.values).to eq x => 5
69
+
70
+ state = states.next
71
+ x = state.variables.first
72
+ expect(state.values).to eq x => 6
73
+
74
+ expect { states.next }.to raise_error StopIteration
75
+ end
76
+ end
77
+
78
+ describe '.both' do
79
+ it 'satisfies both simple subgoals' do
80
+ goal = Goal.with_variables { |x, y| Goal.both(Goal.equal(x, 5), Goal.equal(y, 7)) }
81
+ states = goal.pursue_in(State.new)
82
+
83
+ state = states.next
84
+ x, y = state.variables
85
+ expect(state.values).to eq x => 5, y => 7
86
+
87
+ expect { states.next }.to raise_error StopIteration
88
+ end
89
+
90
+ it 'satisfies compound subgoals' do
91
+ goal = Goal.both Goal.with_variables { |a| Goal.equal(a, 7) }, Goal.with_variables { |b| Goal.either(Goal.equal(b, 5), Goal.equal(b, 6)) }
92
+ states = goal.pursue_in(State.new)
93
+
94
+ state = states.next
95
+ a, b = state.variables
96
+ expect(state.values).to eq a => 7, b => 5
97
+
98
+ state = states.next
99
+ a, b = state.variables
100
+ expect(state.values).to eq a => 7, b => 6
101
+
102
+ expect { states.next }.to raise_error StopIteration
103
+ end
104
+
105
+ it 'doesn’t satisfy contradictory goals' do
106
+ goal = Goal.with_variables { |x| Goal.both(Goal.equal(1, x), Goal.equal(x, 2)) }
107
+ states = goal.pursue_in(State.new)
108
+ expect { states.next }.to raise_error StopIteration
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,40 @@
1
+ require 'kanren/list'
2
+ require 'kanren/pair'
3
+
4
+ module Kanren
5
+ RSpec.describe List do
6
+ let(:array) { %i(a b c) }
7
+ let(:list) { Pair.new(:a, Pair.new(:b, Pair.new(:c, List::EMPTY))) }
8
+
9
+ describe '#from_array' do
10
+ it 'turns an array into a list' do
11
+ expect(List.from_array(array)).to eq list
12
+ end
13
+ end
14
+
15
+ describe '#to_array' do
16
+ it 'turns a list into an array' do
17
+ expect(List.to_array(list)).to eq array
18
+ end
19
+ end
20
+
21
+ describe 'backwards compatibility' do
22
+ let(:argument) { double }
23
+ let(:result) { double }
24
+
25
+ describe '#to_list' do
26
+ it 'is an alias for #from_array' do
27
+ expect(List).to receive(:from_array).with(argument).and_return(result)
28
+ expect(List.to_list(argument)).to eq result
29
+ end
30
+ end
31
+
32
+ describe '#from_list' do
33
+ it 'is an alias for #to_array' do
34
+ expect(List).to receive(:to_array).with(argument).and_return(result)
35
+ expect(List.from_list(argument)).to eq result
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ require 'kanren/pair'
2
+
3
+ module Kanren
4
+ RSpec.describe Pair do
5
+ let(:left) { double }
6
+ let(:right) { double }
7
+
8
+ subject(:pair) { Pair.new(left, right) }
9
+
10
+ describe '#inspect' do
11
+ before(:example) do
12
+ allow(left).to receive(:inspect).and_return 'l'
13
+ allow(right).to receive(:inspect).and_return 'r'
14
+ end
15
+
16
+ it 'shows the left and right elements between parentheses' do
17
+ expect(pair.inspect).to eq '(l, r)'
18
+ end
19
+ end
20
+
21
+ describe '#left' do
22
+ it 'returns the left element' do
23
+ expect(pair.left).to eq left
24
+ end
25
+ end
26
+
27
+ describe '#right' do
28
+ it 'returns the right element' do
29
+ expect(pair.right).to eq right
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ require 'kanren/pair'
2
+ require 'kanren/peano'
3
+
4
+ module Kanren
5
+ RSpec.describe Peano do
6
+ let(:integer) { 3 }
7
+ let(:peano) { Pair.new(Peano::SUCC, Pair.new(Peano::SUCC, Pair.new(Peano::SUCC, Peano::ZERO))) }
8
+
9
+ describe '#from_integer' do
10
+ it 'turns an integer into a Peano number' do
11
+ expect(Peano.from_integer(integer)).to eq peano
12
+ end
13
+ end
14
+
15
+ describe '#to_integer' do
16
+ it 'turns a Peano number into an integer' do
17
+ expect(Peano.to_integer(peano)).to eq integer
18
+ end
19
+ end
20
+
21
+ describe 'backwards compatibility' do
22
+ let(:argument) { double }
23
+ let(:result) { double }
24
+
25
+ describe '#to_peano' do
26
+ it 'is an alias for #from_integer' do
27
+ expect(Peano).to receive(:from_integer).with(argument).and_return(result)
28
+ expect(Peano.to_peano(argument)).to eq result
29
+ end
30
+ end
31
+
32
+ describe '#from_peano' do
33
+ it 'is an alias for #to_integer' do
34
+ expect(Peano).to receive(:to_integer).with(argument).and_return(result)
35
+ expect(Peano.from_peano(argument)).to eq result
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,122 @@
1
+ require 'kanren/micro/state'
2
+ require 'kanren/pair'
3
+
4
+ module Kanren
5
+ module Micro
6
+ RSpec.describe State do
7
+ before(:example) do
8
+ @state = State.new
9
+ end
10
+
11
+ context 'when empty' do
12
+ describe '#variables' do
13
+ it 'returns an empty variable array' do
14
+ expect(@state.variables).to be_empty
15
+ end
16
+ end
17
+
18
+ describe '#values' do
19
+ it 'returns an empty value hash' do
20
+ expect(@state.values).to be_empty
21
+ end
22
+ end
23
+ end
24
+
25
+ context 'with declared variables' do
26
+ before(:example) do
27
+ @state, (@x, @y, @z) = @state.create_variables [:x, :y, :z]
28
+ end
29
+
30
+ describe '#variables' do
31
+ it 'returns an array of the declared variables' do
32
+ expect(@state.variables).to eq [@x, @y, @z]
33
+ end
34
+ end
35
+
36
+ context 'with assigned values' do
37
+ before(:example) do
38
+ @state = @state.assign_values @x => @y, @y => @z, @z => 5
39
+ end
40
+
41
+ describe '#values' do
42
+ it 'returns a hash of the assigned values' do
43
+ expect(@state.values).to eq @x => @y, @y => @z, @z => 5
44
+ end
45
+ end
46
+
47
+ describe '#value_of' do
48
+ it 'looks up the final value of an assigned variable' do
49
+ expect(@state.value_of(@x)).to eq 5
50
+ end
51
+
52
+ it 'doesn’t affect a non-variable value' do
53
+ expect(@state.value_of(7)).to eq 7
54
+ end
55
+
56
+ it 'doesn’t affect an unassigned variable' do
57
+ @state, (a, b, c) = @state.create_variables [:a, :b, :c]
58
+ expect(@state.value_of(a)).to eq a
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#unify' do
65
+ before(:example) do
66
+ @state, (@x, @y) = @state.create_variables [:x, :y]
67
+ end
68
+
69
+ it 'changes nothing if the values are already equal' do
70
+ @state = @state.unify(@x, @x)
71
+ expect(@state.values).to be_empty
72
+ end
73
+
74
+ it 'adds an assignment to the state if the first value is an unassigned variable' do
75
+ @state = @state.unify(@x, @y)
76
+ expect(@state.values).to eq @x => @y
77
+ end
78
+
79
+ it 'adds an assignment to the state if the second value is an unassigned variable' do
80
+ @state = @state.unify(@x, @y).unify(@x, 5)
81
+ expect(@state.values).to eq @x => @y, @y => 5
82
+ end
83
+
84
+ it 'fails if the values cannot be made equal' do
85
+ @state = @state.unify(@x, @y).unify(@x, 5).unify(@y, 6)
86
+ expect(@state).to be_nil
87
+ end
88
+ end
89
+
90
+ context 'with pairs' do
91
+ describe 'unification' do
92
+ before(:example) do
93
+ @state, (@x, @y) = @state.create_variables [:x, :y]
94
+ end
95
+
96
+ it 'successfully unifies values within pairs' do
97
+ @state = @state.unify(Pair.new(3, @x), Pair.new(@y, Pair.new(5, @y)))
98
+ expect(@state.values).to eq @x => Pair.new(5, 3), @y => 3
99
+ end
100
+ end
101
+ end
102
+
103
+ describe 'extracting results' do
104
+ let(:x_value) { double }
105
+ let(:y_value) { double }
106
+
107
+ before(:example) do
108
+ @state, (x, y) = @state.create_variables [:x, :y]
109
+ @state = @state.assign_values x => x_value, y => y_value
110
+ end
111
+
112
+ it 'exposes the values of variables' do
113
+ expect(@state.results(2)).to eq [x_value, y_value]
114
+ end
115
+
116
+ it 'exposes the value of the outermost variable' do
117
+ expect(@state.result).to eq x_value
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,25 @@
1
+ require 'kanren/utils'
2
+
3
+ module Kanren
4
+ RSpec.describe Utils do
5
+ describe '#interleave' do
6
+ context 'when the streams are finite' do
7
+ let(:letters) { 'a'.upto('z') }
8
+ let(:numbers) { 1.upto(10) }
9
+
10
+ it 'interleaves the streams' do
11
+ expect(Utils.interleave(letters, numbers).entries).to eq ['a', 1, 'b', 2, 'c', 3, 'd', 4, 'e', 5, 'f', 6, 'g', 7, 'h', 8, 'i', 9, 'j', 10, 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
12
+ end
13
+ end
14
+
15
+ context 'when the streams are infinite' do
16
+ let(:letters) { ['a', 'b', 'c'].cycle }
17
+ let(:numbers) { 1.upto(Float::INFINITY) }
18
+
19
+ it 'interleaves the streams' do
20
+ expect(Utils.interleave(letters, numbers).take(20)).to eq ['a', 1, 'b', 2, 'c', 3, 'a', 4, 'b', 5, 'c', 6, 'a', 7, 'b', 8, 'c', 9, 'a', 10]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ require 'kanren/micro/variable'
2
+
3
+ module Kanren
4
+ module Micro
5
+ RSpec.describe Variable do
6
+ let(:name) { :x }
7
+ let(:different_name) { :y }
8
+
9
+ subject(:variable) { Variable.new(name) }
10
+
11
+ describe '#inspect' do
12
+ it 'returns the variable’s name' do
13
+ expect(subject.inspect).to eq name.to_s
14
+ end
15
+ end
16
+
17
+ describe 'equality' do
18
+ it 'is equal to itself' do
19
+ expect(variable).to eq variable
20
+ end
21
+
22
+ it 'is not equal to a variable with a different name' do
23
+ expect(variable).not_to eq Variable.new(different_name)
24
+ end
25
+
26
+ it 'is not equal to a different variable with the same name' do
27
+ expect(variable).not_to eq Variable.new(name)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kanren
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tom Stuart
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.3'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.3.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.3'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.3.0
33
+ description:
34
+ email: tom@codon.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - ".gitignore"
40
+ - ".rspec"
41
+ - Gemfile
42
+ - LICENSE
43
+ - README.md
44
+ - kanren.gemspec
45
+ - lib/kanren.rb
46
+ - lib/kanren/list.rb
47
+ - lib/kanren/micro.rb
48
+ - lib/kanren/micro/goal.rb
49
+ - lib/kanren/micro/relations.rb
50
+ - lib/kanren/micro/state.rb
51
+ - lib/kanren/micro/variable.rb
52
+ - lib/kanren/pair.rb
53
+ - lib/kanren/peano.rb
54
+ - lib/kanren/utils.rb
55
+ - lib/kanren/version.rb
56
+ - spec/acceptance/relations_on_lists_spec.rb
57
+ - spec/acceptance/relations_on_numbers_spec.rb
58
+ - spec/spec_helper.rb
59
+ - spec/unit/goal_spec.rb
60
+ - spec/unit/list_spec.rb
61
+ - spec/unit/pair_spec.rb
62
+ - spec/unit/peano_spec.rb
63
+ - spec/unit/state_spec.rb
64
+ - spec/unit/utils_spec.rb
65
+ - spec/unit/variable_spec.rb
66
+ homepage: https://github.com/tomstuart/kanren
67
+ licenses:
68
+ - CC0-1.0
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.4.5
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: An example Ruby implementation of μKanren.
90
+ test_files:
91
+ - spec/acceptance/relations_on_lists_spec.rb
92
+ - spec/acceptance/relations_on_numbers_spec.rb
93
+ - spec/spec_helper.rb
94
+ - spec/unit/goal_spec.rb
95
+ - spec/unit/list_spec.rb
96
+ - spec/unit/pair_spec.rb
97
+ - spec/unit/peano_spec.rb
98
+ - spec/unit/state_spec.rb
99
+ - spec/unit/utils_spec.rb
100
+ - spec/unit/variable_spec.rb