mini_kraken 0.1.09 → 0.2.00
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +96 -8
- data/lib/mini_kraken.rb +8 -1
- data/lib/mini_kraken/core/any_value.rb +5 -1
- 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 +4 -1
- data/lib/mini_kraken/core/cons_cell.rb +28 -1
- 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 +15 -11
- 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 +11 -4
- 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/k_symbol.rb +11 -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/dsl.rb +104 -0
- data/lib/mini_kraken/glue/fresh_env.rb +6 -0
- data/lib/mini_kraken/glue/run_star_expression.rb +1 -0
- data/lib/mini_kraken/version.rb +1 -1
- data/spec/core/conde_spec.rb +147 -0
- data/spec/core/cons_cell_spec.rb +35 -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/k_symbol_spec.rb +4 -0
- data/spec/core/outcome_spec.rb +48 -0
- data/spec/core/vocabulary_spec.rb +6 -0
- data/spec/glue/dsl_chap1_spec.rb +498 -0
- data/spec/glue/run_star_expression_spec.rb +341 -7
- data/spec/support/factory_methods.rb +15 -0
- metadata +22 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4bbfe3d58d4c7271a5c8a54486a743c51d6fddcb3b068095cad44cacad575c83
|
4
|
+
data.tar.gz: 012b412a95752592b5be70859e00d94b379e2c4c564d37d932a300a03cbc9716
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 952d93b01de392c7168e7cfb4a3cc911e4d1b53be11a4a10d67012bb4ab4a8f69aa42f41bf13a756f89a6d09eb775a5594174d73334c57c2230039b7991e89fd
|
7
|
+
data.tar.gz: a60adbd5da627495badab63de355336f131a5e599c40226931366a7c1b4a349b2542ccb55cf30ef8e60990341c1f5d8547804b1552fbbbc504bd1bed40a36d9e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,59 @@
|
|
1
|
+
## [0.2.00] - 2020-07-12
|
2
|
+
- First release of DSL (Domain Specific Language)
|
3
|
+
- Fix defect for fused variables that remain fresh
|
4
|
+
|
5
|
+
### NEW
|
6
|
+
- Mix-in module `Glue::DSL` hosting methods for implementing the DSL.
|
7
|
+
- Method `ConsCell#to_s` uses the Lisp convention for representing lists.
|
8
|
+
|
9
|
+
### CHANGED
|
10
|
+
- File `README.md` Added a couple of examples of DSL use.
|
11
|
+
- Method `AnyValue#==` can compare with symbols with format '_' + integer literal (e.g. :_0).
|
12
|
+
|
13
|
+
## [0.1.13] - 2020-07-01
|
14
|
+
- Cover all frames from Chapter One of "Reasoned Scheme" book.
|
15
|
+
- Fix defect for fused variables that remain fresh
|
16
|
+
|
17
|
+
### CHANGED
|
18
|
+
- Method `Variable#quote` now takes into account of cases when variables are fused.
|
19
|
+
- Method `Vocabulary#names_fused` now copes with cases where no variable with given name can be found.
|
20
|
+
|
21
|
+
## [0.1.12] - 2020-06-29
|
22
|
+
- Supports `conde`, that is, a relation that can take an arbitrary number of arguments.
|
23
|
+
- Cover all frames but one from Chapter One of "Reasoned Scheme" book.
|
24
|
+
|
25
|
+
### New
|
26
|
+
- Class `Conde` a relation that succeeds for each of its successful arguments.
|
27
|
+
|
28
|
+
### CHANGED
|
29
|
+
- Method `Goal#validated_actuals` add into account polyadic relations (= relations with arbitrary number of arguments)
|
30
|
+
|
31
|
+
## [0.1.11] - 2020-06-25
|
32
|
+
- Supports `defrel`, that is, the capability to define new relations by combining other relations.
|
33
|
+
- Covers frames from "The Reasoned Scheme" book up to frame [1:87]
|
34
|
+
|
35
|
+
### New
|
36
|
+
- Class `BaseArg` a generalization of goal or goal template argument.
|
37
|
+
- Class `DefRelation` A specialization of `Relation` class aimed for user-defined relation.
|
38
|
+
- Class `FormalArg` to represent goal template argument(s).
|
39
|
+
- Class `FormalRef` an allusion to a formal argument in a goal template.
|
40
|
+
- Class `GoalTemplate` a representation of a goal parametrized with formal arguments.
|
41
|
+
- Class `KBoolean` a MiniKraken representation of a boolean value.
|
42
|
+
|
43
|
+
### CHANGED
|
44
|
+
- File `README.md` minor change: added more TODO's.
|
45
|
+
|
46
|
+
## [0.1.10] - 2020-06-13
|
47
|
+
- Supports frames from "The Reasoned Scheme" book up to frame [1:81]
|
48
|
+
|
49
|
+
### New
|
50
|
+
- Factory methods `Outcome#failure`, `Outcome#success`
|
51
|
+
- Method `Vocabulary#inspect`
|
52
|
+
- File `outcome_spec.rb`
|
53
|
+
|
54
|
+
### FIXED
|
55
|
+
- `Conj2#conjunction` vocabulary wasn't cleared when outcome2 was nil.
|
56
|
+
|
1
57
|
## [0.1.09] - 2020-06-06
|
2
58
|
- Supports frames from "The Reasoned Scheme" book up to frame [1:76]
|
3
59
|
|
data/README.md
CHANGED
@@ -4,24 +4,39 @@
|
|
4
4
|
[![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/famished-tiger/mini_kraken/blob/master/LICENSE.txt)
|
5
5
|
|
6
6
|
### What is __mini_kraken__ ?
|
7
|
-
|
7
|
+
A library containing an implementation of the [miniKanren](http://minikanren.org/)
|
8
|
+
relational programming language in Ruby.
|
8
9
|
*miniKanren* is a small language for relational (logic) programming.
|
9
|
-
Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
|
10
|
+
Based on the reference implementation, in Scheme from the "The Reasoned Schemer" book.
|
10
11
|
Daniel P. Friedman, William E. Byrd, Oleg Kiselyov, and Jason Hemann: "The Reasoned Schemer", Second Edition,
|
11
12
|
ISBN: 9780262535519, (2018), MIT Press.
|
12
13
|
|
13
14
|
### Features
|
15
|
+
- Pure Ruby implementation, not a port from another language
|
16
|
+
- Object-Oriented design
|
17
|
+
- No runtime dependencies
|
18
|
+
|
19
|
+
### miniKanren Features
|
14
20
|
- [X] ==
|
15
21
|
- [X] run\*
|
16
|
-
- [X] fresh
|
22
|
+
- [X] fresh
|
23
|
+
- [X] conde
|
17
24
|
- [X] conj2
|
18
|
-
- [X] disj2
|
25
|
+
- [X] disj2
|
26
|
+
- [X] defrel
|
19
27
|
|
20
28
|
### TODO
|
21
|
-
|
22
|
-
- [ ] conde
|
29
|
+
|
23
30
|
- [ ] Occurs check
|
24
31
|
|
32
|
+
List-centric relations from Chapter 2
|
33
|
+
- [ ] caro
|
34
|
+
- [ ] cdro
|
35
|
+
- [ ] conso
|
36
|
+
- [ ] nullo
|
37
|
+
- [ ] pairo
|
38
|
+
- [ ] singletono
|
39
|
+
|
25
40
|
## Installation
|
26
41
|
|
27
42
|
Add this line to your application's Gemfile:
|
@@ -38,9 +53,82 @@ Or install it yourself as:
|
|
38
53
|
|
39
54
|
$ gem install mini_kraken
|
40
55
|
|
41
|
-
##
|
56
|
+
## Examples
|
57
|
+
|
58
|
+
The following __MiniKraken__ examples use its DSL (Domain Specific Language).
|
59
|
+
|
60
|
+
### Example 1
|
61
|
+
Let's first begin with a rather simplistic example.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
require 'mini_kraken' # Load MiniKraken library
|
65
|
+
|
66
|
+
extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
|
67
|
+
|
68
|
+
result = run_star('q', equals(q, :pea))
|
69
|
+
puts result # => (:pea)
|
70
|
+
```
|
71
|
+
|
72
|
+
The two first lines in the above code snippet are pretty standard:
|
73
|
+
- The first line loads the `mini_kraken` library.
|
74
|
+
- The second line add the DSL methods to the current object.
|
75
|
+
|
76
|
+
The next line constitutes a trivial `miniKanren` program.
|
77
|
+
The aim of a `miniKanren` program is to find one or more solutions involving provided variable(s)
|
78
|
+
and satisfying one or more goals.
|
79
|
+
In our example, the `run_star` method instructs `MiniKraken` to find all solutions,
|
80
|
+
knowing that each successful solution:
|
81
|
+
- binds a value to the provided variable `q` and
|
82
|
+
- meets the goal `equals(q, :pea)`.
|
83
|
+
|
84
|
+
The goal `equals(q, :pea)` succeeds because the logical variable `q` is _fresh_ (that is,
|
85
|
+
not yet bound to a value) and will be bound to the symbol `:pea` as a side effect
|
86
|
+
of the goal `equals`.
|
87
|
+
|
88
|
+
So the above program succeeds and the only found solution is obtained by binding
|
89
|
+
the variable `q` to the value :pea. Hence the result of the `puts` method.
|
90
|
+
|
91
|
+
### Example 2
|
92
|
+
The next example illustrates the behavior of a failing `miniKanren` program.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
require 'mini_kraken' # Load MiniKraken library
|
96
|
+
|
97
|
+
extend(MiniKraken::Glue::DSL) # Add DSL method to self (object in context)
|
98
|
+
|
99
|
+
# Following miniKanren program fails
|
100
|
+
result = run_star('q', [equals(q, :pea), equals(q, :pod)])
|
101
|
+
puts result # => ()
|
102
|
+
```
|
103
|
+
In this example, we learn that `run_star` can take multiple goals placed in an array.
|
104
|
+
The program fails to find a solution since it is not possible to satisfy the two `equals` goals simultaneously. In that case, the `run_star` return an empty list represented as `()` in the output.
|
105
|
+
|
106
|
+
|
107
|
+
### Example 3
|
108
|
+
The next example shows the use two logical variables.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# In this example and following, one assumes that DSL is loaded as shown in Example 1
|
112
|
+
|
113
|
+
result = run_star(['x', 'y'], [equals(:hello, x), equals(y, :world)])
|
114
|
+
puts result # => ((:hello :world))
|
115
|
+
```
|
42
116
|
|
43
|
-
|
117
|
+
This time, `run_star` takes two logical variables -`x` and `y`- and successfully finds the solution `x = :hello, y = :world`.
|
118
|
+
|
119
|
+
### Example 4
|
120
|
+
The next example shows the use of `disj2` goals.
|
121
|
+
```ruby
|
122
|
+
result = run_star(['x', 'y'],
|
123
|
+
[
|
124
|
+
disj2(equals(x, :blue), equals(x, :red)),
|
125
|
+
disj2(equals(y, :sea), equals(:mountain, y))
|
126
|
+
])
|
127
|
+
puts result # => ((:blue :sea) (:blue :mountain) (:red :sea) (:red :mountain))
|
128
|
+
```
|
129
|
+
|
130
|
+
Here, `run_star` takes two logical variables and two `disj2` goals. A `disj2` succeeds if any of its arguments succeeds.
|
131
|
+
This program finds four distinct solutions for x, y pairs.
|
44
132
|
|
45
133
|
## Development
|
46
134
|
|
data/lib/mini_kraken.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# This file acts as a jumping-off point for loading dependencies expected
|
4
|
+
# for a MiniKraken client.
|
5
|
+
|
6
|
+
require_relative './mini_kraken/version'
|
7
|
+
require_relative './mini_kraken/glue/dsl'
|
8
|
+
|
9
|
+
|
10
|
+
# End of file
|
4
11
|
|
5
12
|
module MiniKraken
|
6
13
|
class Error < StandardError; end
|
@@ -12,7 +12,11 @@ module MiniKraken
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def ==(other)
|
15
|
-
|
15
|
+
if other.is_a?(AnyValue)
|
16
|
+
rank == other.rank
|
17
|
+
elsif other.id2name =~ /_\d+/
|
18
|
+
rank == other.id2name.sub(/_/, '').to_i
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
# Use same text representation as in Reasoned Schemer.
|
@@ -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
|
@@ -31,6 +31,7 @@ unless MiniKraken::Core.constants(false).include? :Conj2
|
|
31
31
|
# @param g2 [Goal] Second goal argument
|
32
32
|
# @param voc [Vocabulary] A vocabulary object
|
33
33
|
def conjunction(g1, g2, voc)
|
34
|
+
# require 'debug'
|
34
35
|
if g1.relation.kind_of?(Fail) || g2.relation.kind_of?(Fail)
|
35
36
|
Fiber.yield Outcome.new(:"#u", voc)
|
36
37
|
else
|
@@ -62,7 +63,9 @@ unless MiniKraken::Core.constants(false).include? :Conj2
|
|
62
63
|
else
|
63
64
|
Fiber.yield outcome1
|
64
65
|
end
|
65
|
-
|
66
|
+
if outcome1.successful? && (outcome2&.successful? || outcome2.nil?)
|
67
|
+
voc.clear
|
68
|
+
end
|
66
69
|
end
|
67
70
|
end
|
68
71
|
|
@@ -11,7 +11,11 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
|
|
11
11
|
|
12
12
|
def initialize(obj1, obj2 = nil)
|
13
13
|
@car = obj1
|
14
|
-
|
14
|
+
if obj2.kind_of?(ConsCell) && obj2.null?
|
15
|
+
@cdr = nil
|
16
|
+
else
|
17
|
+
@cdr = obj2
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
def children
|
@@ -42,9 +46,32 @@ unless MiniKraken::Core.constants(false).include? :ConsCell
|
|
42
46
|
ConsCell.new(new_car, new_cdr)
|
43
47
|
end
|
44
48
|
|
49
|
+
# Use the list notation from Lisp as a text representation.
|
50
|
+
def to_s
|
51
|
+
return '()' if null?
|
52
|
+
|
53
|
+
"(#{pair_to_s})"
|
54
|
+
end
|
55
|
+
|
45
56
|
def append(another)
|
46
57
|
@cdr = another
|
47
58
|
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def pair_to_s
|
63
|
+
result = +car.to_s
|
64
|
+
if cdr
|
65
|
+
result << ' '
|
66
|
+
if cdr.kind_of?(ConsCell)
|
67
|
+
result << cdr.pair_to_s
|
68
|
+
else
|
69
|
+
result << ". #{cdr}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
result
|
74
|
+
end
|
48
75
|
end # class
|
49
76
|
|
50
77
|
# Constant representing the null (empty) list.
|