unific 0.9

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.
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
File without changes
@@ -0,0 +1,5 @@
1
+ === 0.9 / 2012-01-12
2
+
3
+ * unific split out from the in-development rulog (Ruby With Logic) gem
4
+
5
+
@@ -0,0 +1,8 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.rdoc
5
+ README.txt
6
+ Rakefile
7
+ lib/unific.rb
8
+ test/test_unific.rb
@@ -0,0 +1,307 @@
1
+ = unific
2
+
3
+ https://github.com/jimwise/unific
4
+
5
+ Author:: Jim Wise (mailto:jwise@draga.com)
6
+ Copyright:: Copyright (c) 2011, 2012 Jim Wise
7
+ License:: 2-clause BSD-Style (see LICENSE.txt)
8
+
9
+ == DESCRIPTION:
10
+
11
+ Unific is a ruby unification engine.
12
+
13
+ A unification engine is an essential part of a logic programming environment
14
+ (the whole logic programming environment this is taken from is available as
15
+ the in-development Rulog[http://github.com/jimwise/rulog]] (Ruby With Logic)
16
+ gem), but can also be useful on its own as a pattern matching engine which
17
+ can enforce consistency across multiple matches.
18
+
19
+ === What is Unfication?
20
+
21
+ Unfication is a generalization of pattern matching -- it allows you to
22
+ compare two patterns or values, and determine if they match, possibly
23
+ substituting variables in each pattern to make a match possible.
24
+
25
+ Two values can be unified by passing both to the Unific::unify class method.
26
+ This method returns false if the two values cannot be unified, or a
27
+ (possibly empty) _environment_ if they can. For the moment, it is enough to
28
+ remember that this environment is a true value, but soon we will see that it
29
+ is much more.
30
+
31
+ ===== Simple unification
32
+
33
+ So, what does it mean to unify two values?
34
+
35
+ In the simplest case, we can compare two values:
36
+
37
+ Unific::unify("foo", "foo")
38
+ ==> succeeds, returns an empty environment, which is a true value (see below)
39
+
40
+ Unific::unify(42, 42)
41
+ ==> succeeds, returns an empty environment, which is a true value (see below)
42
+
43
+ Unific::unify("foo", 42)
44
+ ==> false
45
+
46
+ If two Enumerables are compared, they match if (and only if) their
47
+ corresponding members match (and thus Enumerables of different lengths do
48
+ not unify[1]):
49
+
50
+ Unific::unify([42, "a", "b"], [42, "a", "b"])
51
+ ==> an empty environment, which is a true value (see below)
52
+
53
+ Unific::unify({"a" => 1, "b" => 2}, {"a" => 1, "b" => 2})
54
+ ==> an empty environment, which is a true value (see below)
55
+
56
+ Unific::unify([42, "a", "b", "hike!"], [42, "a", "b"])
57
+ ==> false
58
+
59
+ Unific::unify([42, 33, "b"], [42, "a", "b"])
60
+ ==> false
61
+
62
+ this implies that nested Enumerables are unified recursively:
63
+
64
+ Unific::unify([["a", 42], ["b", 33]], [["a", 42], ["b", 33]])
65
+ ==> returns an empty environment, which is a true value (see below)
66
+
67
+ So far, this does nothing that we could not do with the == operator... but
68
+ there's more.
69
+
70
+ ==== Pattern variables
71
+
72
+ A unification variable of class Unific::Var can be created with any name of
73
+ your choice, for use in unifications:
74
+
75
+ Unific::Var.new("x")
76
+ ==> #<Unific::Var:0x823b920 @name="x">
77
+
78
+ when used with Unific::unify, a variable will successfully unify with any
79
+ value:
80
+
81
+ x = Unific::Var.new("x")
82
+ Unific::unify(x, 42);
83
+ ==> a non-empty environment, which is a true value (see below)
84
+
85
+ This also applies when a variable is unified as part of a larger expression
86
+
87
+ x = Unific::Var.new("x")
88
+ Unific::unify([1, x, 3], [1, 42, 3]);
89
+ ==> a non-empty environment, which is a true value (see below)
90
+
91
+ Note that as a variable unifies with any object, a single variable can also
92
+ be unified with an entire Enumerable
93
+
94
+ x = Unific::Var.new("x")
95
+ Unific::unify(x, [1, 2, 3]);
96
+ ==> a non-empty environment, which is a true value (see below)
97
+
98
+ Note that when a variable matches a given value, it must match the _same_
99
+ value everywhere in the same expression:
100
+
101
+ x = Unific::Var.new("x")
102
+ e = Unific::unify([x, x], [1, 2])
103
+ ==> false; x cannot be unified with both 1 and 2 in the same expression
104
+ x = Unific::Var.new("x")
105
+ e = Unific::unify([x, x], [2, 2])
106
+ ==> a non-empty environment, which is a true value (see below)
107
+
108
+ Binding a variable to another variable always succeeds (but is very useful
109
+ when we start using the environments returned by unification, below).
110
+
111
+ So where does the environment returned by Unific::unify come in? The
112
+ returned environment, an object of class Unific::Env, matches ('binds') each
113
+ variable to the value with which it was actually unified. The method Env#[]
114
+ can be used to see whether a variable is bound in a given environment:
115
+
116
+ x = Unific::Var.new("x")
117
+ y = Unific::Var.new("y")
118
+ e = Unific::unify([1, x, 3], [1, 42, 3]);
119
+ e[x]
120
+ ==> 42
121
+ e[y]
122
+ ==> nil
123
+
124
+ So far, we can perform some relatively interesting pattern matches with
125
+ Unific:
126
+
127
+ jumper = Unific::Var.new("jumper")
128
+ jumpee = Unific::Var.new("jumpee")
129
+ pattern = ["The", "quick", "brown", jumper, "jumped", "over", "the", "lazy", jumpee]
130
+ sentence = "The quick brown fox jumped over the lazy dog"
131
+ e = Unific::unify(pattern, sentence.split)
132
+ e[jumper]
133
+ ==> "fox"
134
+
135
+ but where this becomes more interesting is when we want to perform multiple
136
+ unifications in a consistent way.
137
+
138
+
139
+ ==== Chaining unifications
140
+
141
+ Any unification can be performed against a given environment by using
142
+ Env#unify method. If the given environment is empty, this is the same as
143
+ calling Unific::unify.[2] If the environment already has bindings, however,
144
+ the new unification will use these bindings; this means that any variable
145
+ matches performed against the same variables must be consistent with the
146
+ values already bound to those variables:
147
+
148
+ animal = Unific::Var.new("animal")
149
+ e = Unific::unify([animal, "is", "a", "mammal"], "fido is a mammal".split)
150
+ e[animal]
151
+ ==> "fido"
152
+ e.unify([animal, "is", "a", "bear"], "teddy is a bear".split)
153
+ ==> false (cannot unify, as "animal" is bound to "fido" in environment e)
154
+
155
+ Note that unifying against a given environment returns a _new_ environment
156
+ in which any additional variables matched by that unification are also
157
+ bound; the original environment is not modified.
158
+
159
+ This is often used by chaining calls to unify (since each call returns a new
160
+ environment); note that this can only be done if none of the unifications
161
+ returns `false', however[3]:
162
+
163
+ a = Unific::Var.new("a")
164
+ a = Unific::Var.new("b")
165
+ e = Unific::unify([a, 1, 2], [0, 1, 2]).unify([a, b, 5], [0, 3, 5])
166
+ ==> a new environment where a is bound to 0, and b is bound to 3
167
+
168
+ Now, it becomes useful to be able to unify to variables:
169
+
170
+ # x = y + 3
171
+ # y = 2
172
+ x = Unific::Var.new("x")
173
+ y = Unific::Var.new("y")
174
+ e1 = Unific::unify(x, [y, "+", 3])
175
+ e2 = e1.unify(y, 2)
176
+
177
+ We can use the #instantiate method of Unific::Env to recursively substitute
178
+ a variable until we get an uninstantiated variable, or a non-variable
179
+ ("ground") value. Given the above, for instance:
180
+
181
+ e2[x]
182
+ ==> [y, "+", 3]
183
+ e2[y]
184
+ ==> 2
185
+ e2.instantate x
186
+ ==> [2, "+", 3]
187
+
188
+ The #instantiate method of Unific::Env is also more general than the #[]
189
+ method -- in addition to a variable, it can take _any_ value which could be
190
+ passed to unify, and will substitute any variables in the term.
191
+
192
+ jumper = Unific::Var.new("jumper")
193
+ jumpee = Unific::Var.new("jumpee")
194
+ pattern = ["The", "quick", "brown", jumper, "jumped", "over", "the", "lazy", jumpee]
195
+ sentence = "The quick brown fox jumped over the lazy dog"
196
+ e = Unific::unify(pattern, sentence.split)
197
+ e.instantiate(["The", jumpee, "chased", "the", jumper]).join(" ")
198
+ ==> "The dog chased the fox"
199
+
200
+ ==== Wildcards
201
+
202
+ Finally, the value Unific::_ is a special variable which matches any value:
203
+
204
+ x = Unific::Var.new("x")
205
+ e = Unific::unify([Unific::_, x, Unific::_], [1, 2, 3])
206
+ ==> a new environment where x is bound to 2
207
+
208
+ We could not use a plain variable for this purpose, since it would have to
209
+ evaluate to the same value whenever used in the same expression.
210
+
211
+ Matching against Unific::_ does not cause any binding in the returned
212
+ environment, either:
213
+
214
+ x = Unific::Var.new("x")
215
+ e = Unific::unify([Unific::_, x], [1, 2]).unify([x, Unific::_], [2, 3])
216
+ ==> a new environment where x is bound to 2
217
+
218
+ ==== Watching Unific work
219
+
220
+ The class method Unific::trace can be used to enable debug tracing of Unific
221
+ operations. Repeated calls to Unific::trace increase the verbosity of trace
222
+ output (though this has no effect in the current version), and a specific
223
+ trace level (as an integer) may also be passed to Unific::trace as an
224
+ optional argument.
225
+
226
+ Trace output is written to STDERR. Trace output can be disabled by
227
+ specifying a trace level of 0, or by calling Unific::untrace.
228
+
229
+ ==== Notes
230
+
231
+ [1] Unific does not currently have an equivalent of Prolog's incomplete data
232
+ structures. I am looking at a clean way to implement this in a future
233
+ release.
234
+
235
+ [2] Which actually just creates an empty environment and unifies against it
236
+
237
+ [3] This may be revisited in a future version, but the current behavior of
238
+ returning false on unification failure allows the idiom of
239
+
240
+ if e.unify(...)
241
+ ...
242
+ end
243
+
244
+ which is very useful.
245
+
246
+ ==== References
247
+
248
+ For more information on unification, see
249
+
250
+ * Sterling, Leon and Ehud Shapiro, <em>The Art of Prolog</em>, MIT Press, 1994
251
+
252
+ The implementation of unification given here is heavily influenced by the
253
+ presentation there.
254
+
255
+
256
+ == INSTALL:
257
+
258
+ To install:
259
+
260
+ $ gem install unific
261
+
262
+ == DEVELOPERS:
263
+
264
+ After checking out the source, run:
265
+
266
+ $ rake newb
267
+
268
+ This task will install any missing dependencies, run the tests/specs,
269
+ and generate the RDoc.
270
+
271
+ == SYNOPSIS:
272
+
273
+ FIX (code sample of usage)
274
+
275
+ == REQUIREMENTS:
276
+
277
+ This gem should run fine under Ruby 1.8.7 or 1.9. If you experience any
278
+ issues, please let me know.
279
+
280
+ == LICENSE:
281
+
282
+ (The BSD 2-clause License)
283
+
284
+ Copyright (c) 2011, 2012 Jim Wise
285
+ All rights reserved.
286
+
287
+ Redistribution and use in source and binary forms, with or without
288
+ modification, are permitted provided that the following conditions
289
+ are met:
290
+
291
+ 1. Redistributions of source code must retain the above copyright
292
+ notice, this list of conditions and the following disclaimer.
293
+ 2. Redistributions in binary form must reproduce the above copyright
294
+ notice, this list of conditions and the following disclaimer in the
295
+ documentation and/or other materials provided with the distribution.
296
+
297
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
298
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
299
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
300
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
301
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
302
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
303
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
304
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
305
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
306
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
307
+ POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,307 @@
1
+ = unific
2
+
3
+ https://github.com/jimwise/unific
4
+
5
+ Author:: Jim Wise (mailto:jwise@draga.com)
6
+ Copyright:: Copyright (c) 2011, 2012 Jim Wise
7
+ License:: 2-clause BSD-Style (see LICENSE.txt)
8
+
9
+ == DESCRIPTION:
10
+
11
+ Unific is a ruby unification engine.
12
+
13
+ A unification engine is an essential part of a logic programming environment
14
+ (the whole logic programming environment this is taken from is available as
15
+ the in-development Rulog[http://github.com/jimwise/rulog]] (Ruby With Logic)
16
+ gem), but can also be useful on its own as a pattern matching engine which
17
+ can enforce consistency across multiple matches.
18
+
19
+ === What is Unfication?
20
+
21
+ Unfication is a generalization of pattern matching -- it allows you to
22
+ compare two patterns or values, and determine if they match, possibly
23
+ substituting variables in each pattern to make a match possible.
24
+
25
+ Two values can be unified by passing both to the Unific::unify class method.
26
+ This method returns false if the two values cannot be unified, or a
27
+ (possibly empty) _environment_ if they can. For the moment, it is enough to
28
+ remember that this environment is a true value, but soon we will see that it
29
+ is much more.
30
+
31
+ ===== Simple unification
32
+
33
+ So, what does it mean to unify two values?
34
+
35
+ In the simplest case, we can compare two values:
36
+
37
+ Unific::unify("foo", "foo")
38
+ ==> succeeds, returns an empty environment, which is a true value (see below)
39
+
40
+ Unific::unify(42, 42)
41
+ ==> succeeds, returns an empty environment, which is a true value (see below)
42
+
43
+ Unific::unify("foo", 42)
44
+ ==> false
45
+
46
+ If two Enumerables are compared, they match if (and only if) their
47
+ corresponding members match (and thus Enumerables of different lengths do
48
+ not unify[1]):
49
+
50
+ Unific::unify([42, "a", "b"], [42, "a", "b"])
51
+ ==> an empty environment, which is a true value (see below)
52
+
53
+ Unific::unify({"a" => 1, "b" => 2}, {"a" => 1, "b" => 2})
54
+ ==> an empty environment, which is a true value (see below)
55
+
56
+ Unific::unify([42, "a", "b", "hike!"], [42, "a", "b"])
57
+ ==> false
58
+
59
+ Unific::unify([42, 33, "b"], [42, "a", "b"])
60
+ ==> false
61
+
62
+ this implies that nested Enumerables are unified recursively:
63
+
64
+ Unific::unify([["a", 42], ["b", 33]], [["a", 42], ["b", 33]])
65
+ ==> returns an empty environment, which is a true value (see below)
66
+
67
+ So far, this does nothing that we could not do with the == operator... but
68
+ there's more.
69
+
70
+ ==== Pattern variables
71
+
72
+ A unification variable of class Unific::Var can be created with any name of
73
+ your choice, for use in unifications:
74
+
75
+ Unific::Var.new("x")
76
+ ==> #<Unific::Var:0x823b920 @name="x">
77
+
78
+ when used with Unific::unify, a variable will successfully unify with any
79
+ value:
80
+
81
+ x = Unific::Var.new("x")
82
+ Unific::unify(x, 42);
83
+ ==> a non-empty environment, which is a true value (see below)
84
+
85
+ This also applies when a variable is unified as part of a larger expression
86
+
87
+ x = Unific::Var.new("x")
88
+ Unific::unify([1, x, 3], [1, 42, 3]);
89
+ ==> a non-empty environment, which is a true value (see below)
90
+
91
+ Note that as a variable unifies with any object, a single variable can also
92
+ be unified with an entire Enumerable
93
+
94
+ x = Unific::Var.new("x")
95
+ Unific::unify(x, [1, 2, 3]);
96
+ ==> a non-empty environment, which is a true value (see below)
97
+
98
+ Note that when a variable matches a given value, it must match the _same_
99
+ value everywhere in the same expression:
100
+
101
+ x = Unific::Var.new("x")
102
+ e = Unific::unify([x, x], [1, 2])
103
+ ==> false; x cannot be unified with both 1 and 2 in the same expression
104
+ x = Unific::Var.new("x")
105
+ e = Unific::unify([x, x], [2, 2])
106
+ ==> a non-empty environment, which is a true value (see below)
107
+
108
+ Binding a variable to another variable always succeeds (but is very useful
109
+ when we start using the environments returned by unification, below).
110
+
111
+ So where does the environment returned by Unific::unify come in? The
112
+ returned environment, an object of class Unific::Env, matches ('binds') each
113
+ variable to the value with which it was actually unified. The method Env#[]
114
+ can be used to see whether a variable is bound in a given environment:
115
+
116
+ x = Unific::Var.new("x")
117
+ y = Unific::Var.new("y")
118
+ e = Unific::unify([1, x, 3], [1, 42, 3]);
119
+ e[x]
120
+ ==> 42
121
+ e[y]
122
+ ==> nil
123
+
124
+ So far, we can perform some relatively interesting pattern matches with
125
+ Unific:
126
+
127
+ jumper = Unific::Var.new("jumper")
128
+ jumpee = Unific::Var.new("jumpee")
129
+ pattern = ["The", "quick", "brown", jumper, "jumped", "over", "the", "lazy", jumpee]
130
+ sentence = "The quick brown fox jumped over the lazy dog"
131
+ e = Unific::unify(pattern, sentence.split)
132
+ e[jumper]
133
+ ==> "fox"
134
+
135
+ but where this becomes more interesting is when we want to perform multiple
136
+ unifications in a consistent way.
137
+
138
+
139
+ ==== Chaining unifications
140
+
141
+ Any unification can be performed against a given environment by using
142
+ Env#unify method. If the given environment is empty, this is the same as
143
+ calling Unific::unify.[2] If the environment already has bindings, however,
144
+ the new unification will use these bindings; this means that any variable
145
+ matches performed against the same variables must be consistent with the
146
+ values already bound to those variables:
147
+
148
+ animal = Unific::Var.new("animal")
149
+ e = Unific::unify([animal, "is", "a", "mammal"], "fido is a mammal".split)
150
+ e[animal]
151
+ ==> "fido"
152
+ e.unify([animal, "is", "a", "bear"], "teddy is a bear".split)
153
+ ==> false (cannot unify, as "animal" is bound to "fido" in environment e)
154
+
155
+ Note that unifying against a given environment returns a _new_ environment
156
+ in which any additional variables matched by that unification are also
157
+ bound; the original environment is not modified.
158
+
159
+ This is often used by chaining calls to unify (since each call returns a new
160
+ environment); note that this can only be done if none of the unifications
161
+ returns `false', however[3]:
162
+
163
+ a = Unific::Var.new("a")
164
+ a = Unific::Var.new("b")
165
+ e = Unific::unify([a, 1, 2], [0, 1, 2]).unify([a, b, 5], [0, 3, 5])
166
+ ==> a new environment where a is bound to 0, and b is bound to 3
167
+
168
+ Now, it becomes useful to be able to unify to variables:
169
+
170
+ # x = y + 3
171
+ # y = 2
172
+ x = Unific::Var.new("x")
173
+ y = Unific::Var.new("y")
174
+ e1 = Unific::unify(x, [y, "+", 3])
175
+ e2 = e1.unify(y, 2)
176
+
177
+ We can use the #instantiate method of Unific::Env to recursively substitute
178
+ a variable until we get an uninstantiated variable, or a non-variable
179
+ ("ground") value. Given the above, for instance:
180
+
181
+ e2[x]
182
+ ==> [y, "+", 3]
183
+ e2[y]
184
+ ==> 2
185
+ e2.instantate x
186
+ ==> [2, "+", 3]
187
+
188
+ The #instantiate method of Unific::Env is also more general than the #[]
189
+ method -- in addition to a variable, it can take _any_ value which could be
190
+ passed to unify, and will substitute any variables in the term.
191
+
192
+ jumper = Unific::Var.new("jumper")
193
+ jumpee = Unific::Var.new("jumpee")
194
+ pattern = ["The", "quick", "brown", jumper, "jumped", "over", "the", "lazy", jumpee]
195
+ sentence = "The quick brown fox jumped over the lazy dog"
196
+ e = Unific::unify(pattern, sentence.split)
197
+ e.instantiate(["The", jumpee, "chased", "the", jumper]).join(" ")
198
+ ==> "The dog chased the fox"
199
+
200
+ ==== Wildcards
201
+
202
+ Finally, the value Unific::_ is a special variable which matches any value:
203
+
204
+ x = Unific::Var.new("x")
205
+ e = Unific::unify([Unific::_, x, Unific::_], [1, 2, 3])
206
+ ==> a new environment where x is bound to 2
207
+
208
+ We could not use a plain variable for this purpose, since it would have to
209
+ evaluate to the same value whenever used in the same expression.
210
+
211
+ Matching against Unific::_ does not cause any binding in the returned
212
+ environment, either:
213
+
214
+ x = Unific::Var.new("x")
215
+ e = Unific::unify([Unific::_, x], [1, 2]).unify([x, Unific::_], [2, 3])
216
+ ==> a new environment where x is bound to 2
217
+
218
+ ==== Watching Unific work
219
+
220
+ The class method Unific::trace can be used to enable debug tracing of Unific
221
+ operations. Repeated calls to Unific::trace increase the verbosity of trace
222
+ output (though this has no effect in the current version), and a specific
223
+ trace level (as an integer) may also be passed to Unific::trace as an
224
+ optional argument.
225
+
226
+ Trace output is written to STDERR. Trace output can be disabled by
227
+ specifying a trace level of 0, or by calling Unific::untrace.
228
+
229
+ ==== Notes
230
+
231
+ [1] Unific does not currently have an equivalent of Prolog's incomplete data
232
+ structures. I am looking at a clean way to implement this in a future
233
+ release.
234
+
235
+ [2] Which actually just creates an empty environment and unifies against it
236
+
237
+ [3] This may be revisited in a future version, but the current behavior of
238
+ returning false on unification failure allows the idiom of
239
+
240
+ if e.unify(...)
241
+ ...
242
+ end
243
+
244
+ which is very useful.
245
+
246
+ ==== References
247
+
248
+ For more information on unification, see
249
+
250
+ * Sterling, Leon and Ehud Shapiro, <em>The Art of Prolog</em>, MIT Press, 1994
251
+
252
+ The implementation of unification given here is heavily influenced by the
253
+ presentation there.
254
+
255
+
256
+ == INSTALL:
257
+
258
+ To install:
259
+
260
+ $ gem install unific
261
+
262
+ == DEVELOPERS:
263
+
264
+ After checking out the source, run:
265
+
266
+ $ rake newb
267
+
268
+ This task will install any missing dependencies, run the tests/specs,
269
+ and generate the RDoc.
270
+
271
+ == SYNOPSIS:
272
+
273
+ FIX (code sample of usage)
274
+
275
+ == REQUIREMENTS:
276
+
277
+ This gem should run fine under Ruby 1.8.7 or 1.9. If you experience any
278
+ issues, please let me know.
279
+
280
+ == LICENSE:
281
+
282
+ (The BSD 2-clause License)
283
+
284
+ Copyright (c) 2011, 2012 Jim Wise
285
+ All rights reserved.
286
+
287
+ Redistribution and use in source and binary forms, with or without
288
+ modification, are permitted provided that the following conditions
289
+ are met:
290
+
291
+ 1. Redistributions of source code must retain the above copyright
292
+ notice, this list of conditions and the following disclaimer.
293
+ 2. Redistributions in binary form must reproduce the above copyright
294
+ notice, this list of conditions and the following disclaimer in the
295
+ documentation and/or other materials provided with the distribution.
296
+
297
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
298
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
299
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
300
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
301
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
302
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
303
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
304
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
305
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
306
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
307
+ POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ # Hoe.plugin :compiler
7
+ # Hoe.plugin :compiler
8
+ # Hoe.plugin :gem_prelude_sucks
9
+ # Hoe.plugin :gem_prelude_sucks
10
+ # Hoe.plugin :inline
11
+ # Hoe.plugin :inline
12
+ # Hoe.plugin :racc
13
+ # Hoe.plugin :racc
14
+ # Hoe.plugin :rubyforge
15
+ # Hoe.plugin :rubyforge
16
+
17
+ Hoe.spec 'unific' do
18
+
19
+ developer('Jim Wise', 'jwise@draga.com')
20
+
21
+ end
22
+
23
+ # vim: syntax=ruby
@@ -0,0 +1,245 @@
1
+ require 'singleton'
2
+
3
+ module Unific
4
+
5
+ VERSION = '0.9'
6
+
7
+ # An environment (set of variable bindings) resulting from unification
8
+ class Env
9
+ @@trace = 0
10
+
11
+ # Allocate a new environment. Usually not needed -- use Unific::unify, instead.
12
+ #
13
+ # The new environment will be empty unless a hash of variable bindings
14
+ # is included. Use this with care.
15
+ def initialize prev = {}
16
+ @theta = prev.clone
17
+ end
18
+
19
+ # Turn on tracing (to STDERR) of Unific operations
20
+ #
21
+ # intended for use by Unific::trace
22
+ #
23
+ # The optional level argument sets the verbosity -- if not passed, each
24
+ # call to this method increases verbosity
25
+ def self.trace lvl #:nodoc:
26
+ if lvl
27
+ @@trace = lvl
28
+ else
29
+ @@trace = @@trace + 1
30
+ end
31
+ end
32
+
33
+ # Turn off tracing (to STDERR) of Unific operations
34
+ #
35
+ # intended for use by Unific::trace
36
+ def self.untrace #:nodoc:
37
+ @@trace = 0
38
+ end
39
+
40
+ # Return whether a given variable is fresh (not bound) in this environment
41
+ def fresh? x
42
+ not @theta.has_key? x
43
+ end
44
+
45
+ # Return the binding of a variable in this environment, or +nil+ if it is unbound
46
+ def [] x
47
+ @theta[x]
48
+ end
49
+
50
+ # private helper to extend this environment with one or more new mappings
51
+ def _extend mappings
52
+ Env.new @theta.update mappings.reject {|k, v| k.kind_of? Wildcard or v.kind_of? Wildcard }
53
+ end
54
+
55
+ def to_s
56
+ "{ " + @theta.map{|k, v| "#{k} => #{v}"}.join(", ") + "} "
57
+ end
58
+
59
+ # Unify two values against this environment, returning a new environment
60
+ #
61
+ # If the two values cannot be unified, `false' is returned. If they can, a _new_
62
+ # environment is returned which is this environment extended with any new bindings
63
+ # created by unification.
64
+ #
65
+ # Each value to unify can be
66
+ #
67
+ # a. a Unific::Var variable
68
+ # b. the wildcard variable, Unific::_
69
+ # c. any ruby Enumerable except a String, in which case unification recurs on the members
70
+ # e. a String or any other ruby object (as a ground term -- unification succeeds
71
+ # if the two are equal (with '=='))
72
+ #
73
+ # In logic programming terms, the returned env is the Most General Unifier (MGU) of the two
74
+ # terms
75
+ def unify a, b
76
+ puts "unifying #{a.to_s} and #{b.to_s}" if @@trace > 0
77
+
78
+ # if either is already bound, substitute up front
79
+ a = instantiate a
80
+ b = instantiate b
81
+
82
+ # any remaining Var is fresh.
83
+ if a.kind_of? Var and b.kind_of? Var
84
+ _extend a => b
85
+ elsif a.kind_of? Var
86
+ _extend a => b
87
+ elsif b.kind_of? Var
88
+ _extend b => a
89
+ elsif a.kind_of? String and b.kind_of? String # strings should be treated as ground
90
+ if a == b
91
+ self
92
+ else
93
+ Unific::fail
94
+ end
95
+ elsif a.kind_of? Enumerable and b.kind_of? Enumerable
96
+ return Unific::fail unless a.size == b.size
97
+ a.zip(b).inject(self) do |e, pair|
98
+ e.unify(pair[0], pair[1]) or return Unific::fail
99
+ end
100
+ else # both are ground terms
101
+ if a == b
102
+ self
103
+ else
104
+ Unific::fail
105
+ end
106
+ end
107
+ end
108
+
109
+ # Given a value, substitute any variables present in the term.
110
+ #
111
+ # If the passed value is a Ruby Enumerable other than a String, recurs on the members of
112
+ # the Enumerable. Unlike #[], also repeatedly substitutes each variable until it gets a
113
+ # ground (non-variable) term or a free variable
114
+ def instantiate s
115
+ _traverse s do |v|
116
+ if fresh? v
117
+ v
118
+ else
119
+ instantiate @theta[v]
120
+ end
121
+ end
122
+ end
123
+
124
+ # Perform alpha renaming on an expression
125
+ #
126
+ # Alpha-renaming an expression replaces all fresh variables in the
127
+ # expression with new variables of the same name. This is used by rulog
128
+ # to to give each Rule its own private copy of all of its variables.
129
+ def rename s
130
+ _traverse s do |v|
131
+ if fresh? v
132
+ n = Unific::Var.new(v.name)
133
+ @theta[v] = n;
134
+ n
135
+ else
136
+ instantiate @theta[v]
137
+ end
138
+ end
139
+ end
140
+
141
+ # private helper for instantiate and rename
142
+ # given an argument, if it is an:
143
+ # a.) var, replace it with the result of calling a block on it
144
+ # b.) enumerable, recur, instantiating it's members
145
+ # c.) any object, return it
146
+ def _traverse s, &block
147
+ case s
148
+ when Unific::Wildcard
149
+ s
150
+ when Var
151
+ block.call(s)
152
+ # XXX XXX rulog had handling for Functor here, we may need to provide something similar?
153
+ when String
154
+ # in ruby 1.8, strings are enumerable, but we want them to be ground
155
+ s
156
+ when Enumerable
157
+ s.map {|x| _traverse(x, &block)}
158
+ else
159
+ s
160
+ end
161
+ end
162
+
163
+ private :_extend, :_traverse
164
+ end
165
+
166
+ # Unify two terms against an empty environment
167
+ #
168
+ # See README.rdoc or Env#unify for details
169
+ #
170
+ # If the two values cannot be unified, `false' is returned. If they can, a _new_
171
+ # environment is returned which is this environment extended with any new bindings
172
+ # created by unification.
173
+ #--
174
+ # XXX This documentation must be kept in sync with that for Env#unify
175
+ #++
176
+ def self.unify a, b, env = Env.new
177
+ env.unify a, b
178
+ end
179
+
180
+ # A unification variable
181
+ class Var
182
+ attr_accessor :name
183
+
184
+ # Create a new variable
185
+ #
186
+ # The optional argument provides a name for use in printing the variable
187
+ def initialize name = "new_var"
188
+ @name = name
189
+ self.freeze
190
+ end
191
+
192
+ # Return a string representing a variable
193
+ #
194
+ # A variable named"foo" is presented as as "?foo"
195
+ def to_s
196
+ "?#{@name}"
197
+ end
198
+ end
199
+
200
+ # The unique Unific wildcard variable
201
+ class Wildcard < Var
202
+ include Singleton
203
+
204
+ # The wildcard variable is named "_"
205
+ def initialize #:nodoc:
206
+ super "_"
207
+ end
208
+
209
+ # The wildcard variable is presented as "_"
210
+ def to_s
211
+ "_"
212
+ end
213
+
214
+ # The wildcard variable matches any value
215
+ def == x
216
+ true
217
+ end
218
+ end
219
+
220
+ # Return the Unific wildcard variable
221
+ def self._
222
+ Unific::Wildcard.instance
223
+ end
224
+
225
+ # Turn on tracing (to STDERR) of Unific operations
226
+ #
227
+ # The optional level argument sets the verbosity -- if not passed, each
228
+ # call to this method increases verbosity
229
+ def self.trace lvl = false
230
+ Unific::Env::trace lvl
231
+ end
232
+
233
+ # Turn off tracing (to STDERR) of Unific operations
234
+ def self.untrace
235
+ Unific::Env::untrace untrace
236
+ end
237
+
238
+ # Return false
239
+ #
240
+ # Placeholder for possible future expansion of failed unification behavior
241
+ def self.fail
242
+ false
243
+ end
244
+
245
+ end
@@ -0,0 +1,54 @@
1
+ require "test/unit"
2
+ require "unific"
3
+
4
+ class TestUnific < Test::Unit::TestCase
5
+
6
+ def test_unify_simple
7
+ assert Unific::unify(42, 42)
8
+ assert Unific::unify("abc", "abc")
9
+ assert Unific::unify(42, Unific::Var.new)
10
+ assert Unific::unify(Unific::Var.new, 42)
11
+ assert Unific::unify(Unific::Var.new, Unific::Var.new)
12
+ v1 = Unific::Var.new("v1")
13
+ assert Unific::unify(v1, v1)
14
+ assert Unific::unify(v1, 42).unify(v1, 42)
15
+ assert !Unific::unify(v1, 42).unify(v1, 35)
16
+ end
17
+
18
+ def test_unify_enum
19
+ assert Unific::unify([1, 2, 3], [1, 2, 3])
20
+ assert !Unific::unify([1, 2, 3], [1, 2, 3, 4])
21
+ assert Unific::unify([1, 2, 3], [1, Unific::Var.new, 3])
22
+ assert Unific::unify({"a" => 1}, {"a" => 1})
23
+ assert !Unific::unify({"a" => 2}, {"a" => 3})
24
+ end
25
+
26
+ def test_unify_recursive_enum
27
+ assert Unific::unify([["a", 1], ["b", 2]], [["a", 1], ["b", 2]])
28
+ assert !Unific::unify([["a", 1], ["b", 2]], [["x", 3], ["y", 4]])
29
+ assert Unific::unify([[1,2], [3,4], [5,6]], [[1,2], Unific::Var.new, [5,6]])
30
+ end
31
+
32
+ def test_wildcard
33
+ assert Unific::unify(Unific::_, 42);
34
+ assert Unific::unify(Unific::_, Unific::Var.new);
35
+ assert Unific::unify([Unific::_, 2], [1, 2]).unify([2, Unific::_], [2, 3])
36
+ v1 = Unific::Var.new("v1")
37
+ e1 = Unific::Env.new
38
+ e2 = Unific::unify(v1, 42);
39
+ assert Unific::unify(v1, Unific::_, e2);
40
+ assert Unific::unify([1, 2, 3], Unific::_)
41
+ assert Unific::unify([1, Unific::_, 3], [1, 2, 3])
42
+ assert Unific::unify([Unific::_, 3, Unific::_], [2, 3, 4])
43
+ end
44
+
45
+ def test_vars
46
+ v1 = Unific::Var.new("v1")
47
+ v2 = Unific::Var.new("v2")
48
+ assert Unific::unify(v1, v2).unify(v1, 42).unify(v2, 42)
49
+ assert !Unific::unify(v1, v2).unify(v1, 42).unify(v2, 35)
50
+ assert Unific::unify(v1, 42).unify(v1, v2).unify(v2, 42)
51
+ assert !Unific::unify(v1, 42).unify(v1, v2).unify(v2, 35)
52
+ end
53
+
54
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unific
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 9
9
+ version: "0.9"
10
+ platform: ruby
11
+ authors:
12
+ - Jim Wise
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-01-12 00:00:00 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: rdoc
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 19
28
+ segments:
29
+ - 3
30
+ - 10
31
+ version: "3.10"
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: hoe
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ hash: 27
43
+ segments:
44
+ - 2
45
+ - 12
46
+ version: "2.12"
47
+ type: :development
48
+ version_requirements: *id002
49
+ description: |-
50
+ Unific is a ruby unification engine.
51
+
52
+ A unification engine is an essential part of a logic programming environment
53
+ (the whole logic programming environment this is taken from is available as
54
+ the in-development Rulog[http://github.com/jimwise/rulog]] (Ruby With Logic)
55
+ gem), but can also be useful on its own as a pattern matching engine which
56
+ can enforce consistency across multiple matches.
57
+ email:
58
+ - jwise@draga.com
59
+ executables: []
60
+
61
+ extensions: []
62
+
63
+ extra_rdoc_files:
64
+ - History.txt
65
+ - Manifest.txt
66
+ - README.txt
67
+ files:
68
+ - .autotest
69
+ - History.txt
70
+ - Manifest.txt
71
+ - README.rdoc
72
+ - README.txt
73
+ - Rakefile
74
+ - lib/unific.rb
75
+ - test/test_unific.rb
76
+ - .gemtest
77
+ homepage: https://github.com/jimwise/unific
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --main
83
+ - README.txt
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project: unific
107
+ rubygems_version: 1.8.13
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Unific is a ruby unification engine
111
+ test_files:
112
+ - test/test_unific.rb