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.
- data/.autotest +23 -0
- data/.gemtest +0 -0
- data/History.txt +5 -0
- data/Manifest.txt +8 -0
- data/README.rdoc +307 -0
- data/README.txt +307 -0
- data/Rakefile +23 -0
- data/lib/unific.rb +245 -0
- data/test/test_unific.rb +54 -0
- metadata +112 -0
data/.autotest
ADDED
@@ -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
|
data/.gemtest
ADDED
File without changes
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -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.
|
data/README.txt
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/lib/unific.rb
ADDED
@@ -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
|
data/test/test_unific.rb
ADDED
@@ -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
|