mini_kraken 0.1.09 → 0.2.00

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -0
  3. data/README.md +96 -8
  4. data/lib/mini_kraken.rb +8 -1
  5. data/lib/mini_kraken/core/any_value.rb +5 -1
  6. data/lib/mini_kraken/core/association_walker.rb +1 -1
  7. data/lib/mini_kraken/core/base_arg.rb +10 -0
  8. data/lib/mini_kraken/core/conde.rb +143 -0
  9. data/lib/mini_kraken/core/conj2.rb +4 -1
  10. data/lib/mini_kraken/core/cons_cell.rb +28 -1
  11. data/lib/mini_kraken/core/def_relation.rb +49 -0
  12. data/lib/mini_kraken/core/disj2.rb +1 -0
  13. data/lib/mini_kraken/core/equals.rb +15 -11
  14. data/lib/mini_kraken/core/formal_arg.rb +22 -0
  15. data/lib/mini_kraken/core/formal_ref.rb +24 -0
  16. data/lib/mini_kraken/core/goal.rb +11 -4
  17. data/lib/mini_kraken/core/goal_arg.rb +5 -3
  18. data/lib/mini_kraken/core/goal_template.rb +60 -0
  19. data/lib/mini_kraken/core/k_boolean.rb +31 -0
  20. data/lib/mini_kraken/core/k_symbol.rb +11 -0
  21. data/lib/mini_kraken/core/outcome.rb +14 -0
  22. data/lib/mini_kraken/core/relation.rb +7 -0
  23. data/lib/mini_kraken/core/variable.rb +10 -4
  24. data/lib/mini_kraken/core/vocabulary.rb +18 -1
  25. data/lib/mini_kraken/glue/dsl.rb +104 -0
  26. data/lib/mini_kraken/glue/fresh_env.rb +6 -0
  27. data/lib/mini_kraken/glue/run_star_expression.rb +1 -0
  28. data/lib/mini_kraken/version.rb +1 -1
  29. data/spec/core/conde_spec.rb +147 -0
  30. data/spec/core/cons_cell_spec.rb +35 -0
  31. data/spec/core/def_relation_spec.rb +96 -0
  32. data/spec/core/equals_spec.rb +3 -3
  33. data/spec/core/goal_template_spec.rb +74 -0
  34. data/spec/core/k_boolean_spec.rb +107 -0
  35. data/spec/core/k_symbol_spec.rb +4 -0
  36. data/spec/core/outcome_spec.rb +48 -0
  37. data/spec/core/vocabulary_spec.rb +6 -0
  38. data/spec/glue/dsl_chap1_spec.rb +498 -0
  39. data/spec/glue/run_star_expression_spec.rb +341 -7
  40. data/spec/support/factory_methods.rb +15 -0
  41. metadata +22 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10a0607b5621effb10eab26d64ff7f52228f93e9c7907b91488613c1d72469aa
4
- data.tar.gz: 9b43c9b0d729f2c7944be72f6599decae48d977ea0be5257a365111508473ef9
3
+ metadata.gz: 4bbfe3d58d4c7271a5c8a54486a743c51d6fddcb3b068095cad44cacad575c83
4
+ data.tar.gz: 012b412a95752592b5be70859e00d94b379e2c4c564d37d932a300a03cbc9716
5
5
  SHA512:
6
- metadata.gz: 32c14d82b93ddf21d2b369745b1e556faf1736b53fd7f1782809370777e059c498e1d8347806dc2135aab3bedf429c8d2b93b5f9be4b9b361f86926edb1be20f
7
- data.tar.gz: 9b82b3afd53af4da54042f8a8852367250bc8225c1229b31486b4e8223ca533d55f3dcc6250c4de4c03d0d84b77a5e5cf737a6c151f734d7a08334ac442fa330
6
+ metadata.gz: 952d93b01de392c7168e7cfb4a3cc911e4d1b53be11a4a10d67012bb4ab4a8f69aa42f41bf13a756f89a6d09eb775a5594174d73334c57c2230039b7991e89fd
7
+ data.tar.gz: a60adbd5da627495badab63de355336f131a5e599c40226931366a7c1b4a349b2542ccb55cf30ef8e60990341c1f5d8547804b1552fbbbc504bd1bed40a36d9e
@@ -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
- An implemention of the [miniKanren](http://minikanren.org/) relational programming language in Ruby.
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
- - [ ] defrel
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
- ## Usage
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
- TODO: Write usage instructions here
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
 
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mini_kraken/version'
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
- rank == other.rank
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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'set'
4
4
  require_relative 'atomic_term'
5
- require_relative 'composite_term'
5
+ require_relative 'cons_cell'
6
6
 
7
7
  module MiniKraken
8
8
  module Core
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MiniKraken
4
+ module Core
5
+ # Abstract class that is a generalization for goal actual arguments or
6
+ # for arguments of goal template.
7
+ class BaseArg
8
+ end # class
9
+ end # module
10
+ end # module
@@ -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
- voc.clear if outcome1&.successful? && outcome2&.successful?
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
- @cdr = obj2
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.