wongi-engine 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +155 -92
- data/lib/wongi-engine/dsl/any_rule.rb +1 -1
- data/lib/wongi-engine/version.rb +1 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -26,14 +26,16 @@ Now let's add some facts to the system.
|
|
26
26
|
|
27
27
|
### Facts
|
28
28
|
|
29
|
-
All knowledge in Wongi::Engine is represented
|
29
|
+
All knowledge in Wongi::Engine is represented by triples of { subject, predicate, object }. Predicates usually stand for subjects' properties, and objects for values of those properties. More complex types can always be decomposed into such triples.
|
30
30
|
|
31
31
|
Triples can contain any Ruby object that defines the `==` comparison in a meaningful way, but some symbols have special meaning, as we will see.
|
32
32
|
|
33
33
|
Try this:
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
```ruby
|
36
|
+
engine << [ "Alice", "friend", "Bob" ]
|
37
|
+
engine << [ "Alice", "age", 35 ]
|
38
|
+
```
|
37
39
|
|
38
40
|
What can we do with this information?
|
39
41
|
|
@@ -41,9 +43,11 @@ What can we do with this information?
|
|
41
43
|
|
42
44
|
Suppose we want to list all we know about Alice. You could, for instance, do:
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
46
|
+
```ruby
|
47
|
+
engine.each "Alice", :_, :_ do |item|
|
48
|
+
puts "Alice's #{item.predicate} is #{item.object}"
|
49
|
+
end
|
50
|
+
```
|
47
51
|
|
48
52
|
`each` takes three arguments for every field of a triple and tries to match the resulting template against the known facts. `:_` is the special value that matches anything. This kind of pattern matching plays a large role in Wongi::Engine; more on that later.
|
49
53
|
|
@@ -53,19 +57,23 @@ In a similar way, you can use `select` to get an array of matching facts and `fi
|
|
53
57
|
|
54
58
|
It's not very interesting to use the engine like that, though. Rule engines are supposed to be declarative. Let's try this:
|
55
59
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
```ruby
|
61
|
+
friends = engine.rule "friends" do
|
62
|
+
forall {
|
63
|
+
has :PersonA, "friend", :PersonB
|
64
|
+
}
|
65
|
+
end
|
66
|
+
```
|
61
67
|
|
62
68
|
Here's your first taste of the engine's DSL. A rule, generally speaking, consists of a number of conditions the dataset needs to meet; those are defined in the `forall` section (also spelled `for_all`, if you prefer that). `has` (or `fact`) specifies that there needs to be a fact that matches the given pattern; in this case, one with the predicate `"friends"`.
|
63
69
|
|
64
70
|
When a pattern contains a symbol that starts with an uppercase letter, it introduces a variable which will be bound to an actual triple field. Their values can be retrieved from the result set:
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
|
72
|
+
```ruby
|
73
|
+
friends.tokens.each do |token|
|
74
|
+
puts "%s and %s are friends" % [ token[ :PersonA ], token[ :PersonB ] ]
|
75
|
+
end
|
76
|
+
```
|
69
77
|
|
70
78
|
A **token** represents all facts that passed the rule's conditions. If you think of the dataset as of a long SQL table being joined with itself, then a token is like a row in the resulting table.
|
71
79
|
|
@@ -77,16 +85,18 @@ Once a variable is bound, it can be used to match further facts within a rule. L
|
|
77
85
|
|
78
86
|
and another rule:
|
79
87
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
88
|
+
```ruby
|
89
|
+
remote = engine.rule "remote friends" do
|
90
|
+
forall {
|
91
|
+
has :PersonA, "friend", :PersonB
|
92
|
+
has :PersonB, "friend", :PersonC
|
93
|
+
}
|
94
|
+
end
|
86
95
|
|
87
|
-
|
88
|
-
|
89
|
-
|
96
|
+
remote.tokens.each do |token|
|
97
|
+
puts "%s and %s are friends through %s" % [ token[ :PersonA ], token[ :PersonC ], token[ :PersonB ] ]
|
98
|
+
end
|
99
|
+
```
|
90
100
|
|
91
101
|
(`engine.rule` returns the created **production node** - an object that accumulates the rule's result set. You don't have to carry it around if you don't want to - it is always possible to retrieve it later as `engine.productions["remote friends"]`.)
|
92
102
|
|
@@ -94,17 +104,19 @@ and another rule:
|
|
94
104
|
|
95
105
|
Taking the SQL metaphor further, you can use the engine to do fancy searches:
|
96
106
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
107
|
+
```ruby
|
108
|
+
q = engine.query "friends" do
|
109
|
+
search_on :Name
|
110
|
+
forall {
|
111
|
+
has :Name, "friend", :Friend
|
112
|
+
}
|
113
|
+
end
|
103
114
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
115
|
+
engine.execute "friends", { Name: "Alice" }
|
116
|
+
q.tokens.each do |token|
|
117
|
+
... # you know the drill
|
118
|
+
end
|
119
|
+
```
|
108
120
|
|
109
121
|
Not that this is a particularly fancy search, but you get the idea.
|
110
122
|
|
@@ -116,16 +128,18 @@ You can also retrieve the query's production node from `engine.results["friends"
|
|
116
128
|
|
117
129
|
There's more to rules than passive accumulation:
|
118
130
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
131
|
+
```ruby
|
132
|
+
engine.rule "self-printer" do
|
133
|
+
forall {
|
134
|
+
has :PersonA, "friend", :PersonB
|
135
|
+
}
|
136
|
+
make {
|
137
|
+
action { |token|
|
138
|
+
puts "%s and %s are friends" % [ token[ :PersonA ], token[ :PersonB ] ]
|
127
139
|
}
|
128
|
-
|
140
|
+
}
|
141
|
+
end
|
142
|
+
```
|
129
143
|
|
130
144
|
The `make` section (also spelled `do!`, if you find it more agreeable English, because `do` is a keyword in Ruby) lists everything that happens when a rule's conditions are fully matched (we say the production node is activated). Wongi::Engine provides only a small amount of build-in actions, but you can define define your own ones, and the simplest one is just `action` with a block.
|
131
145
|
|
@@ -133,17 +147,19 @@ The `make` section (also spelled `do!`, if you find it more agreeable English, b
|
|
133
147
|
|
134
148
|
Note how our facts define relations that always go from subject to object - they form a directed graph. In a perfect world, friendships go both ways, but to specify this in out model, we need to have two facts for each couple. Instead of duplicating everything by hand, let's automate that:
|
135
149
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
150
|
+
```ruby
|
151
|
+
engine.rule "symmetric predicate" do
|
152
|
+
forall {
|
153
|
+
has :Predicate, "symmetric", true
|
154
|
+
has :X, :Predicate, :Y
|
155
|
+
}
|
156
|
+
make {
|
157
|
+
gen :Y, :Predicate, :X
|
158
|
+
}
|
159
|
+
end
|
145
160
|
|
146
|
-
|
161
|
+
engine << ["friend", "symmetric", true]
|
162
|
+
```
|
147
163
|
|
148
164
|
If you still have the "self-printer" rule installed, you will see some new friendships pop up immediately!
|
149
165
|
|
@@ -165,9 +181,9 @@ Passes whether or not the template matches anything. It's only useful if it intr
|
|
165
181
|
|
166
182
|
The `none` block contains other matchers and passes if that *entire subchain* returns an empty set. In other words, it corresponds to an expression `not ( a and b and ... )`.
|
167
183
|
|
168
|
-
#### `any {
|
184
|
+
#### `any { option { ... } ... }`
|
169
185
|
|
170
|
-
The `any` block contains several `
|
186
|
+
The `any` block contains several `option` blocks, each of them containing other matchers. It passes if any of the `option` subchains matches. It's a shame that disjunction has to be so much more verbose than conjunction, but life is cruel.
|
171
187
|
|
172
188
|
#### `same x, y`
|
173
189
|
|
@@ -195,8 +211,10 @@ The following matchers are nothing but syntactic sugar for a combination of prim
|
|
195
211
|
|
196
212
|
Short for:
|
197
213
|
|
198
|
-
|
199
|
-
|
214
|
+
```ruby
|
215
|
+
neg subject, predicate, object, -1
|
216
|
+
has subject, predicate, object, 0
|
217
|
+
```
|
200
218
|
|
201
219
|
That is, it passes if the fact was missing in the previous state but exists in the current one. Alias: `added`.
|
202
220
|
|
@@ -204,8 +222,10 @@ That is, it passes if the fact was missing in the previous state but exists in t
|
|
204
222
|
|
205
223
|
Short for:
|
206
224
|
|
207
|
-
|
208
|
-
|
225
|
+
```ruby
|
226
|
+
has subject, predicate, object, -1
|
227
|
+
neg subject, predicate, object, 0
|
228
|
+
```
|
209
229
|
|
210
230
|
The reverse of `asserted`. Alias: `removed`.
|
211
231
|
|
@@ -213,8 +233,10 @@ The reverse of `asserted`. Alias: `removed`.
|
|
213
233
|
|
214
234
|
Short for:
|
215
235
|
|
216
|
-
|
217
|
-
|
236
|
+
```ruby
|
237
|
+
has subject, predicate, object, -1
|
238
|
+
has subject, predicate, object, 0
|
239
|
+
```
|
218
240
|
|
219
241
|
Alias: `still_has`.
|
220
242
|
|
@@ -222,8 +244,10 @@ Alias: `still_has`.
|
|
222
244
|
|
223
245
|
Short for:
|
224
246
|
|
225
|
-
|
226
|
-
|
247
|
+
```ruby
|
248
|
+
neg subject, predicate, object, -1
|
249
|
+
neg subject, predicate, object, 0
|
250
|
+
```
|
227
251
|
|
228
252
|
Alias: `still_missing`.
|
229
253
|
|
@@ -250,9 +274,11 @@ The debugging action that will print a message every time it's activated. Possib
|
|
250
274
|
|
251
275
|
We've seen one way to specify custom actions: using `action` with a block. Another way to use it is to say:
|
252
276
|
|
253
|
-
|
254
|
-
|
255
|
-
|
277
|
+
```ruby
|
278
|
+
action class, ... do
|
279
|
+
...
|
280
|
+
end
|
281
|
+
```
|
256
282
|
|
257
283
|
Any additional arguments or blocks will be given to `initialize`, and the class must define an `execute` method taking a token. Passing any object with an `execute` method also works.
|
258
284
|
|
@@ -260,7 +286,7 @@ If your action class inherits from `Wongi::Engine::Action`, you'll have the foll
|
|
260
286
|
|
261
287
|
* `rete`: the engine instance
|
262
288
|
* `rule`: the rule object that is using this action
|
263
|
-
* `name`: the extension clause used to define this action (read more under [DSL extensions])
|
289
|
+
* `name`: the extension clause used to define this action (read more under [DSL extensions](#dsl-extensions))
|
264
290
|
* `production`: the production node
|
265
291
|
|
266
292
|
If you can't or don't want to inherit, you can define the accessors yourself. Having just the ones you need is fine.
|
@@ -269,48 +295,58 @@ If you can't or don't want to inherit, you can define the accessors yourself. Ha
|
|
269
295
|
|
270
296
|
Using `engine.rule` and `engine.query` is fine if you want to experiment, but to make rules and queries more manageable, you will probably want to keep them separate from the engine instance. One way to do that is to just say:
|
271
297
|
|
272
|
-
|
273
|
-
|
274
|
-
|
298
|
+
```ruby
|
299
|
+
my_rule = rule "name" do
|
300
|
+
...
|
301
|
+
end
|
275
302
|
|
276
|
-
|
303
|
+
engine << my_rule
|
304
|
+
```
|
277
305
|
|
278
306
|
For even more convenience, why not group rules together:
|
279
307
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
308
|
+
```ruby
|
309
|
+
my_ruleset = ruleset {
|
310
|
+
rule "rule 1" do
|
311
|
+
...
|
312
|
+
end
|
313
|
+
rule "rule 2" do
|
314
|
+
...
|
315
|
+
end
|
316
|
+
}
|
288
317
|
|
289
|
-
|
318
|
+
engine << my_ruleset
|
319
|
+
```
|
290
320
|
|
291
321
|
Again, you don't need to hold on to object references if you don't want to:
|
292
322
|
|
293
|
-
|
294
|
-
|
295
|
-
|
323
|
+
```ruby
|
324
|
+
ruleset "my set" do
|
325
|
+
...
|
326
|
+
end
|
296
327
|
|
297
|
-
|
328
|
+
engine << Wongi::Engine::Ruleset[ "my set" ]
|
329
|
+
```
|
298
330
|
|
299
331
|
### DSL extensions
|
300
332
|
|
301
333
|
This is a more advanced method of customising. In general, DSL extensions have the form:
|
302
334
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
335
|
+
```ruby
|
336
|
+
dsl {
|
337
|
+
section [ :forall | :make ]
|
338
|
+
clause :my_action
|
339
|
+
[ action | accept | body ] ...
|
340
|
+
}
|
341
|
+
```
|
308
342
|
|
309
343
|
which is then used in a rule like this:
|
310
344
|
|
311
|
-
|
312
|
-
|
313
|
-
|
345
|
+
```ruby
|
346
|
+
make {
|
347
|
+
my_action ...
|
348
|
+
}
|
349
|
+
```
|
314
350
|
|
315
351
|
DSL extensions are globally visible to all engine instances.
|
316
352
|
|
@@ -322,7 +358,34 @@ This simply allows you to group several other actions or matchers. It is perhaps
|
|
322
358
|
|
323
359
|
#### `action class`, `action do |token| ... end`
|
324
360
|
|
325
|
-
This works almost exactly like using the `action` action directly in a rule, but gives it a more meaningful alias. Arguments to `initialize`, however, are taken from the action's invocation in `make`, not definition.
|
361
|
+
This works almost exactly like using the `action` action directly in a rule, but gives it a more meaningful alias. Arguments to `initialize`, however, are taken from the action's invocation in `make`, not the definition.
|
362
|
+
|
363
|
+
A useful pattern is having specialised named collectors, defined like this:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
dsl {
|
367
|
+
section :make
|
368
|
+
clause :my_collection
|
369
|
+
action Wongi::Engine::SimpleCollector.collector
|
370
|
+
}
|
371
|
+
```
|
372
|
+
|
373
|
+
installed like this:
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
rule('collecting') {
|
377
|
+
...
|
378
|
+
make {
|
379
|
+
my_collection :X
|
380
|
+
}
|
381
|
+
}
|
382
|
+
```
|
383
|
+
|
384
|
+
and accessed like this:
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
collection = engine.collection :my_collection
|
388
|
+
```
|
326
389
|
|
327
390
|
#### `accept class`
|
328
391
|
|
data/lib/wongi-engine/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wongi-engine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-16 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A rule engine.
|
15
15
|
email:
|