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.
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.