mini_kraken 0.1.08 → 0.1.13
Sign up to get free protection for your applications and to get access to all the features.
- 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
|