mini_kraken 0.1.08 → 0.1.13
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 +4 -4
- data/CHANGELOG.md +52 -0
- data/README.md +11 -3
- data/lib/mini_kraken/core/association_walker.rb +1 -1
- data/lib/mini_kraken/core/base_arg.rb +10 -0
- data/lib/mini_kraken/core/conde.rb +143 -0
- data/lib/mini_kraken/core/conj2.rb +5 -3
- data/lib/mini_kraken/core/def_relation.rb +49 -0
- data/lib/mini_kraken/core/disj2.rb +1 -0
- data/lib/mini_kraken/core/equals.rb +17 -13
- data/lib/mini_kraken/core/formal_arg.rb +22 -0
- data/lib/mini_kraken/core/formal_ref.rb +24 -0
- data/lib/mini_kraken/core/goal.rb +9 -3
- data/lib/mini_kraken/core/goal_arg.rb +5 -3
- data/lib/mini_kraken/core/goal_template.rb +60 -0
- data/lib/mini_kraken/core/k_boolean.rb +31 -0
- data/lib/mini_kraken/core/outcome.rb +14 -0
- data/lib/mini_kraken/core/relation.rb +7 -0
- data/lib/mini_kraken/core/variable.rb +10 -4
- data/lib/mini_kraken/core/vocabulary.rb +18 -1
- data/lib/mini_kraken/glue/fresh_env.rb +45 -3
- data/lib/mini_kraken/glue/run_star_expression.rb +43 -26
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conde_spec.rb +147 -0
- data/spec/core/def_relation_spec.rb +96 -0
- data/spec/core/equals_spec.rb +3 -3
- data/spec/core/goal_template_spec.rb +74 -0
- data/spec/core/k_boolean_spec.rb +107 -0
- data/spec/core/outcome_spec.rb +48 -0
- data/spec/core/vocabulary_spec.rb +6 -0
- data/spec/glue/fresh_env_spec.rb +27 -1
- data/spec/glue/run_star_expression_spec.rb +500 -7
- data/spec/support/factory_methods.rb +15 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79ea33dd475e6dfbed71915177b1581abb5e1ca9a60db04133d11cda7dca9d42
|
4
|
+
data.tar.gz: 249bea4319183c929e4c291785c6b74e10606889c789c5e0f2b211197cc6f288
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '086bf0b30750e908dfee1b7e95eb3b0c0e54dff4f7bd700d54bbf28091aaf02080d458c14508ddcaa580248e0234d1da23553869480e3bea930d46708c115d82'
|
7
|
+
data.tar.gz: d3ada8d056819b2ae1af0d442056008600f82633ee52ca8aa672708943fdf517adb60522bfff6673f9cea86985c1bf5ce2a7a3ea73f07504945e660f31ebee49
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,55 @@
|
|
1
|
+
## [0.1.13] - 2020-07-01
|
2
|
+
- Cover all frames from Chapter One of "Reasoned Scheme" book.
|
3
|
+
- Fix defect for fused variables that remain fresh
|
4
|
+
|
5
|
+
### CHANGED
|
6
|
+
- Method `Variable#quote` now takes into account of cases when variables are fused.
|
7
|
+
- Method `Vocabulary#names_fused` now copes with cases where no variable with given name can be found.
|
8
|
+
|
9
|
+
## [0.1.12] - 2020-06-29
|
10
|
+
- Supports `conde`, that is, a relation that can take an arbitrary number of arguments.
|
11
|
+
- Cover all frames but one from Chapter One of "Reasoned Scheme" book.
|
12
|
+
|
13
|
+
### New
|
14
|
+
- Class `Conde` a relation that succeeds for each of its successful arguments.
|
15
|
+
|
16
|
+
### CHANGED
|
17
|
+
- Method `Goal#validated_actuals` add into account polyadic relations (= relations with arbitrary number of arguments)
|
18
|
+
|
19
|
+
## [0.1.11] - 2020-06-25
|
20
|
+
- Supports `defrel`, that is, the capability to define new relations by combining other relations.
|
21
|
+
- Covers frames from "The Reasoned Scheme" book up to frame [1:87]
|
22
|
+
|
23
|
+
### New
|
24
|
+
- Class `BaseArg` a generalization of goal or goal template argument.
|
25
|
+
- Class `DefRelation` A specialization of `Relation` class aimed for user-defined relation.
|
26
|
+
- Class `FormalArg` to represent goal template argument(s).
|
27
|
+
- Class `FormalRef` an allusion to a formal argument in a goal template.
|
28
|
+
- Class `GoalTemplate` a representation of a goal parametrized with formal arguments.
|
29
|
+
- Class `KBoolean` a MiniKraken representation of a boolean value.
|
30
|
+
|
31
|
+
### CHANGED
|
32
|
+
- File `README.md` minor change: added more TODO's.
|
33
|
+
|
34
|
+
## [0.1.10] - 2020-06-13
|
35
|
+
- Supports frames from "The Reasoned Scheme" book up to frame [1:81]
|
36
|
+
|
37
|
+
### New
|
38
|
+
- Factory methods `Outcome#failure`, `Outcome#success`
|
39
|
+
- Method `Vocabulary#inspect`
|
40
|
+
- File `outcome_spec.rb`
|
41
|
+
|
42
|
+
### FIXED
|
43
|
+
- `Conj2#conjunction` vocabulary wasn't cleared when outcome2 was nil.
|
44
|
+
|
45
|
+
## [0.1.09] - 2020-06-06
|
46
|
+
- Supports frames from "The Reasoned Scheme" book up to frame [1:76]
|
47
|
+
|
48
|
+
### CHANGED
|
49
|
+
- Method `FreshEnv#initialize`accepts an array of goals as second argument. This array is transformed into a conjunction of goals.
|
50
|
+
- Method `RunStarExpression#initialize` accepts multiple multiple variable names and goals.
|
51
|
+
- Method `RunStarExpression#run` can handle solutions with multiple variables.
|
52
|
+
|
1
53
|
## [0.1.08] - 2020-05-30
|
2
54
|
- Fix of nasty bug (object aliasing) that caused flaky failures in specs.
|
3
55
|
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
### What is __mini_kraken__ ?
|
7
7
|
An implemention of the [miniKanren](http://minikanren.org/) relational programming language in Ruby.
|
8
8
|
*miniKanren* is a small language for relational (logic) programming.
|
9
|
-
Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
|
9
|
+
Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
|
10
10
|
Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann: "The Reasoned Schemer", Second Edition,
|
11
11
|
ISBN: 9780262535519, (2018), MIT Press.
|
12
12
|
|
@@ -15,13 +15,21 @@ ISBN: 9780262535519, (2018), MIT Press.
|
|
15
15
|
- [X] run\*
|
16
16
|
- [X] fresh
|
17
17
|
- [X] conj2
|
18
|
-
- [X] disj2
|
18
|
+
- [X] disj2
|
19
|
+
- [X] defrel
|
19
20
|
|
20
21
|
### TODO
|
21
|
-
- [ ] defrel
|
22
22
|
- [ ] conde
|
23
23
|
- [ ] Occurs check
|
24
24
|
|
25
|
+
List-centric relations from Chapter 2
|
26
|
+
- [ ] caro
|
27
|
+
- [ ] cdro
|
28
|
+
- [ ] conso
|
29
|
+
- [ ] nullo
|
30
|
+
- [ ] pairo
|
31
|
+
- [ ] singletono
|
32
|
+
|
25
33
|
## Installation
|
26
34
|
|
27
35
|
Add this line to your application's Gemfile:
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require_relative 'conj2'
|
5
|
+
require_relative 'duck_fiber'
|
6
|
+
require_relative 'fail'
|
7
|
+
require_relative 'goal'
|
8
|
+
require_relative 'goal_relation'
|
9
|
+
require_relative 'outcome'
|
10
|
+
|
11
|
+
unless MiniKraken::Core.constants(false).include? :Conde
|
12
|
+
module MiniKraken
|
13
|
+
module Core
|
14
|
+
# A polyadic relation (i.e. it can takes an arbitrary number of argumentt)
|
15
|
+
# that behaves as the disjunction of its arguments.
|
16
|
+
# It succeeds if at least one of its goal arguments succeeds.
|
17
|
+
class Conde < GoalRelation
|
18
|
+
include Singleton
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
super('conde', nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
# A relation is polyadic when it accepts an arbitrary number of arguments.
|
25
|
+
# @return [TrueClass]
|
26
|
+
def polyadic?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param actuals [Array<Term>] A two-elements array
|
31
|
+
# @param anEnv [Vocabulary] A vocabulary object
|
32
|
+
# @return [Fiber<Outcome>] A Fiber that yields Outcomes objects
|
33
|
+
def solver_for(actuals, anEnv)
|
34
|
+
args = *validated_args(actuals)
|
35
|
+
Fiber.new { cond(args, anEnv) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Yields [Outcome, NilClass] result of the disjunction
|
39
|
+
# @param goals [Array<Goal>] Array of goals
|
40
|
+
# @param voc [Vocabulary] A vocabulary object
|
41
|
+
def cond(goals, voc)
|
42
|
+
# require 'debug'
|
43
|
+
success = false
|
44
|
+
|
45
|
+
goals.each do |g|
|
46
|
+
fiber = nil
|
47
|
+
|
48
|
+
case g
|
49
|
+
when Core::Goal
|
50
|
+
fiber = g.attain(voc)
|
51
|
+
when Core::Environment
|
52
|
+
fiber = g.attain(voc)
|
53
|
+
when Array
|
54
|
+
conjunct = conjunction(g)
|
55
|
+
fiber = conjunct.attain(voc)
|
56
|
+
when Core::ConsCell
|
57
|
+
goal_array = to_goal_array(g)
|
58
|
+
conjunct = conjunction(goal_array)
|
59
|
+
fiber = conjunct.attain(voc)
|
60
|
+
end
|
61
|
+
loop do
|
62
|
+
outcome = fiber.resume
|
63
|
+
break unless outcome
|
64
|
+
|
65
|
+
outcome.parent = voc unless outcome.parent
|
66
|
+
if outcome.successful?
|
67
|
+
success = true
|
68
|
+
Fiber.yield outcome
|
69
|
+
outcome.clear
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Fiber.yield Outcome.new(:"#u", voc) unless success
|
75
|
+
Fiber.yield nil
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def validated_args(actuals)
|
81
|
+
result = []
|
82
|
+
|
83
|
+
actuals.each do |arg|
|
84
|
+
case arg
|
85
|
+
when Core::Goal
|
86
|
+
result << arg
|
87
|
+
|
88
|
+
when Core::Environment
|
89
|
+
result << arg
|
90
|
+
|
91
|
+
when Array
|
92
|
+
result << validated_args(arg)
|
93
|
+
|
94
|
+
else
|
95
|
+
prefix = "#{name} expects goal as argument, found a "
|
96
|
+
raise StandardError, prefix + "'#{arg.class}'"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
result
|
101
|
+
end
|
102
|
+
|
103
|
+
def conjunction(goal_array)
|
104
|
+
result = nil
|
105
|
+
|
106
|
+
loop do
|
107
|
+
conjunctions = []
|
108
|
+
goal_array.each_slice(2) do |uno_duo|
|
109
|
+
if uno_duo.size == 2
|
110
|
+
conjunctions << Core::Goal.new(Core::Conj2.instance, uno_duo)
|
111
|
+
else
|
112
|
+
conjunctions << uno_duo[0]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
if conjunctions.size == 1
|
116
|
+
result = conjunctions[0]
|
117
|
+
break
|
118
|
+
end
|
119
|
+
goal_array = conjunctions
|
120
|
+
end
|
121
|
+
|
122
|
+
result
|
123
|
+
end
|
124
|
+
|
125
|
+
def to_goal_array(aCons)
|
126
|
+
array = []
|
127
|
+
curr_node = aCons
|
128
|
+
loop do
|
129
|
+
array << curr_node.car if curr_node.car.kind_of?(Core::Goal)
|
130
|
+
break unless curr_node.cdr
|
131
|
+
break unless curr_node.car.kind_of?(Core::Goal)
|
132
|
+
|
133
|
+
curr_node = curr_node.cdr
|
134
|
+
end
|
135
|
+
|
136
|
+
array
|
137
|
+
end
|
138
|
+
end # class
|
139
|
+
|
140
|
+
Conde.instance.freeze
|
141
|
+
end # module
|
142
|
+
end # module
|
143
|
+
end # unless
|
@@ -32,11 +32,11 @@ unless MiniKraken::Core.constants(false).include? :Conj2
|
|
32
32
|
# @param voc [Vocabulary] A vocabulary object
|
33
33
|
def conjunction(g1, g2, voc)
|
34
34
|
# require 'debug'
|
35
|
-
outcome1 = nil
|
36
|
-
outcome2 = nil
|
37
35
|
if g1.relation.kind_of?(Fail) || g2.relation.kind_of?(Fail)
|
38
36
|
Fiber.yield Outcome.new(:"#u", voc)
|
39
37
|
else
|
38
|
+
outcome1 = nil
|
39
|
+
outcome2 = nil
|
40
40
|
f1 = g1.attain(voc)
|
41
41
|
loop do
|
42
42
|
outcome1 = f1.resume
|
@@ -63,7 +63,9 @@ unless MiniKraken::Core.constants(false).include? :Conj2
|
|
63
63
|
else
|
64
64
|
Fiber.yield outcome1
|
65
65
|
end
|
66
|
-
|
66
|
+
if outcome1.successful? && (outcome2&.successful? || outcome2.nil?)
|
67
|
+
voc.clear
|
68
|
+
end
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'relation'
|
4
|
+
|
5
|
+
module MiniKraken
|
6
|
+
module Core
|
7
|
+
# A relation that is parametrized with generic formal arguments
|
8
|
+
# and a goal template expression.
|
9
|
+
class DefRelation < Relation
|
10
|
+
# @return [Array<FormalArg>] formal arguments of this DefRelation
|
11
|
+
attr_reader :formals
|
12
|
+
|
13
|
+
# @return [GoalTemplate] goal template
|
14
|
+
attr_reader :goal_template
|
15
|
+
|
16
|
+
# @param aName [String] name of def relation
|
17
|
+
# @param aGoalTemplate [GoalTemplate]
|
18
|
+
def initialize(aName, aGoalTemplate, theFormals, alternateName = nil)
|
19
|
+
super(aName, alternateName)
|
20
|
+
@formals = validated_formals(theFormals)
|
21
|
+
@goal_template = validated_goal_template(aGoalTemplate)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Number of arguments for the relation.
|
25
|
+
# @return [Integer]
|
26
|
+
def arity
|
27
|
+
formals.size
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param actuals [Array<Term>] A two-elements array
|
31
|
+
# @param anEnv [Vocabulary] A vocabulary object
|
32
|
+
# @return [Fiber<Outcome>] A Fiber(-like) instance that yields Outcomes
|
33
|
+
def solver_for(actuals, anEnv)
|
34
|
+
goal = goal_template.instantiate(formals, actuals)
|
35
|
+
goal.attain(anEnv)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def validated_formals(theFormals)
|
41
|
+
theFormals
|
42
|
+
end
|
43
|
+
|
44
|
+
def validated_goal_template(aGoalTemplate)
|
45
|
+
aGoalTemplate
|
46
|
+
end
|
47
|
+
end # class
|
48
|
+
end # module
|
49
|
+
end # module
|
@@ -31,9 +31,9 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
31
31
|
arg2_nil = arg2.nil?
|
32
32
|
if arg1_nil || arg2_nil
|
33
33
|
if arg1_nil && arg2_nil
|
34
|
-
result = Outcome.
|
34
|
+
result = Outcome.success(anEnv)
|
35
35
|
else
|
36
|
-
result =
|
36
|
+
result = Outcome.failure(anEnv)
|
37
37
|
end
|
38
38
|
return result
|
39
39
|
end
|
@@ -46,6 +46,7 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
+
|
49
50
|
# table: Unification
|
50
51
|
# | arg1 | arg2 | Criterion || Unification |
|
51
52
|
# | isa? Atomic | isa? Atomic | arg1.eq? arg2 is true || { "s", [] } |
|
@@ -70,12 +71,12 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
70
71
|
# | | unification(arg1, arg2.value) => "u" || { "u", [] }
|
71
72
|
def do_unification(arg1, arg2, anEnv)
|
72
73
|
# require 'debug'
|
73
|
-
return Outcome.
|
74
|
+
return Outcome.success(anEnv) if arg1.equal?(arg2)
|
74
75
|
|
75
|
-
result = Outcome.
|
76
|
+
result = Outcome.failure(anEnv) # default case
|
76
77
|
|
77
78
|
if arg1.kind_of?(AtomicTerm)
|
78
|
-
result =
|
79
|
+
result = Outcome.success(anEnv) if arg1.eql?(arg2)
|
79
80
|
elsif arg1.kind_of?(CompositeTerm)
|
80
81
|
if arg2.kind_of?(CompositeTerm) # AtomicTerm is default case => fail
|
81
82
|
result = unify_composite_terms(arg1, arg2, anEnv)
|
@@ -84,14 +85,14 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
84
85
|
arg1_freshness = arg1.freshness(anEnv)
|
85
86
|
if arg2.kind_of?(AtomicTerm)
|
86
87
|
if arg1_freshness.degree == :fresh
|
87
|
-
result = Outcome.
|
88
|
+
result = Outcome.success(anEnv)
|
88
89
|
arg1.associate(arg2, result)
|
89
90
|
else
|
90
|
-
result = Outcome.
|
91
|
+
result = Outcome.success(anEnv) if arg1.value(anEnv).eql?(arg2)
|
91
92
|
end
|
92
93
|
elsif arg2.kind_of?(CompositeTerm)
|
93
94
|
if arg1_freshness.degree == :fresh
|
94
|
-
result = Outcome.
|
95
|
+
result = Outcome.success(anEnv)
|
95
96
|
arg1.associate(arg2, result)
|
96
97
|
else
|
97
98
|
# Ground case...
|
@@ -103,11 +104,14 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
103
104
|
when [false, false] # TODO: confirm this...
|
104
105
|
result = unification(arg1.value(anEnv), arg2.value(anEnv), anEnv)
|
105
106
|
when [true, true]
|
106
|
-
result = Outcome.
|
107
|
+
result = Outcome.success(anEnv)
|
107
108
|
if arg1.var_name != arg2.var_name
|
108
109
|
arg1.associate(arg2, result)
|
109
110
|
arg2.associate(arg1, result)
|
110
111
|
end
|
112
|
+
when [true, false]
|
113
|
+
result = Outcome.success(anEnv)
|
114
|
+
arg1.associate(arg2, result)
|
111
115
|
else
|
112
116
|
raise StandardError, "Unsupported freshness combination #{freshness}"
|
113
117
|
end
|
@@ -123,7 +127,7 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
123
127
|
# @return [Freshness]
|
124
128
|
def unify_composite_terms(arg1, arg2, anEnv)
|
125
129
|
# require 'debug'
|
126
|
-
result = Outcome.
|
130
|
+
result = Outcome.failure(anEnv)
|
127
131
|
children1 = arg1.children
|
128
132
|
children2 = arg2.children
|
129
133
|
|
@@ -136,7 +140,7 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
136
140
|
end
|
137
141
|
total_success = subresults.all?(&:successful?)
|
138
142
|
if total_success
|
139
|
-
memo = Outcome.
|
143
|
+
memo = Outcome.success(anEnv)
|
140
144
|
associations = subresults.reduce(memo) do |sub_total, outcome|
|
141
145
|
sub_total.merge(outcome)
|
142
146
|
sub_total
|
@@ -148,8 +152,8 @@ unless MiniKraken::Core.constants(false).include? :Equals
|
|
148
152
|
result
|
149
153
|
end
|
150
154
|
end # class
|
151
|
-
|
155
|
+
|
152
156
|
Equals.instance.freeze
|
153
157
|
end # module
|
154
158
|
end # module
|
155
|
-
end # unless
|
159
|
+
end # unless
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniKraken
|
4
|
+
module Core
|
5
|
+
# The generalization of any iem that can be
|
6
|
+
# passed as arugement to a goal.
|
7
|
+
class FormalArg
|
8
|
+
# @return [String]
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(aName)
|
12
|
+
@name = validated_name(aName)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def validated_name(aName)
|
18
|
+
aName
|
19
|
+
end
|
20
|
+
end # class
|
21
|
+
end # module
|
22
|
+
end # module
|