unific 0.9

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