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