Skip to content

Commit 4e2347f

Browse files
committed
simplified definition of fact variables and allowed lambda function strings as user functions
1 parent b7f3cad commit 4e2347f

5 files changed

Lines changed: 73 additions & 23 deletions

File tree

faces.lua

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,48 @@ local faces = {}
3131
-- STATIC FUNCTIONS --
3232
----------------------
3333

34+
-- inspired by Penlight string_lambda:
35+
-- http://stevedonovan.github.io/Penlight/api/libraries/pl.utils.html#string_lambda
36+
local lambda
37+
do
38+
local memory = {}
39+
lambda = setmetatable({ clear = function() memory = {} end, },
40+
{
41+
__call = function(self,fstr)
42+
local fun = memory[fstr]
43+
if not fun then
44+
local args,code = fstr:match("^%s*|(.+)|(.+)$")
45+
assert(args and code, "Needs args and code sections, e.g., |args|code")
46+
local fun_src = ("return function(%s) return %s end"):format(args,code)
47+
fun = assert(loadstring(fun_src))()
48+
memory[fstr] = fun
49+
end
50+
return fun
51+
end,
52+
})
53+
end
54+
55+
-- transform a user function string into a lambda function
56+
local function lambda_transform(func_str)
57+
return function(vars)
58+
local values,keys = {},{}
59+
for k,v in pairs(vars) do
60+
values[#values+1] = v
61+
keys[#keys+1] = k
62+
end
63+
local code = ('|%s|%s'):format(table.concat(keys,","),func_str)
64+
local f = lambda(code)
65+
return f(table.unpack(values))
66+
end
67+
end
68+
3469
-- converts to in-mutable the given table argument
3570
local function inmutable(tbl)
3671
return setmetatable({}, {
3772
__index = function(_,k) return tbl[k] end,
3873
__newindex = function() error("Unable to modify an in-mutable table") end,
3974
__len = function() return #tbl end,
75+
__pairs = function() return pairs(tbl) end,
4076
})
4177
end
4278

@@ -131,15 +167,16 @@ do
131167
end
132168

133169
assign_variables = function(self, vars, patterns, sequence, var_matches,
134-
user_clauses)
170+
user_clauses, fact_vars)
135171
for i,pat in ipairs(patterns) do
136172
local fid = sequence[i]
137173
local fact = self.fact_list[fid]
138174
if not assign_fact_vars(vars, pat, fact, var_matches) then return false end
139175
end
176+
for vname,i in pairs(fact_vars) do vars[vname] = sequence[i] end
140177
local inmutable_vars = inmutable(vars)
141178
for _,func in ipairs(user_clauses) do
142-
if not func(sequence, inmutable_vars) then return false end
179+
if not func(inmutable_vars) then return false end
143180
end
144181
return true
145182
end
@@ -186,7 +223,8 @@ local function regenerate_agenda(self)
186223
if not rule_entailements[sequence] then
187224
local seq_vars = {}
188225
if assign_variables(self, seq_vars, rule.patterns, sequence,
189-
rule.var_matches, rule.user_clauses) then
226+
rule.var_matches, rule.user_clauses,
227+
rule.fact_vars) then
190228
table.insert(combinations, sequence)
191229
table.insert(variables, seq_vars)
192230
end
@@ -231,7 +269,7 @@ local function fire_rule(self, rule_name, args, vars)
231269
for i,v in ipairs(args) do self.fact_entailment[v] = args end
232270
-- execute rule actions
233271
for _,action in ipairs(rule.actions) do
234-
action(args, inmutable(vars))
272+
action(inmutable(vars))
235273
end
236274
end
237275

@@ -464,14 +502,27 @@ end
464502
-- declares a new rule in the knowledge base
465503
function faces_methods:defrule(rule_name)
466504
local rule = { patterns={}, user_clauses = {},
467-
actions={}, salience=0, var_matches = {} }
505+
actions={}, salience=0, var_matches = {}, fact_vars = {} }
468506
self.kb_table[rule_name] = rule
469-
local rule_builder = {
507+
local rule_builder
508+
rule_builder = {
470509
pattern = function(rule_builder, pattern)
471510
table.insert(rule.patterns, tuple(pattern))
472511
return rule_builder
473512
end,
513+
var = function(rule_builder, varname)
514+
varname = assert(varname:match("%?([^%s]+)"),
515+
string.format("Incorrect variable name: %s", varname))
516+
rule.fact_vars[varname] = #rule.patterns + 1
517+
return {
518+
pattern = function(_,...)
519+
return rule_builder.pattern(rule_builder,...)
520+
end
521+
}
522+
end,
474523
u = function(rule_builder, func)
524+
-- user_func receives one argument: vars
525+
if type(func) == "string" then func = lambda_transform(func) end
475526
table.insert(rule.user_clauses, func)
476527
return rule_builder
477528
end,
@@ -502,7 +553,8 @@ function faces_methods:defrule(rule_name)
502553
__index = function(rule_builder, key)
503554
if key == "u" then
504555
return function(rule_builder, user_func)
505-
-- user_func receives two arguments (fact_ids, vars)
556+
-- user_func receives one argument: vars
557+
if type(func) == "string" then func = lambda_transform(func) end
506558
table.insert(rule.actions, user_func)
507559
return rule_builder
508560
end
@@ -514,7 +566,7 @@ function faces_methods:defrule(rule_name)
514566
local args = table.pack(...)
515567
for i=1,args.n do args[i] = tuple(args[i]) end
516568
table.insert(rule.actions,
517-
function(fact_ids, vars)
569+
function(vars)
518570
local new_args = replace_variables(args, vars)
519571
return self[key](self, table.unpack(new_args))
520572
end)

tests/animals.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ kb:defrule("Error"):
7878
-- Retracts error rule in case any classification is asserted
7979
kb:defrule("RetractError"):
8080
salience(100):
81-
pattern{ "ERROR" }:
81+
var("?f1"):pattern{ "ERROR" }:
8282
pattern{ ANIMAL_IS, ".*" }:
8383
ENTAILS("=>"):
84-
u(function(fact_ids, vars)
85-
kb:retract(fact_ids[1])
84+
u(function(vars)
85+
kb:retract(vars.f1)
8686
end)
8787

8888
-- Initial rule, asks the first question
@@ -149,7 +149,7 @@ kb:defrule("Show"):
149149
salience(-10):
150150
pattern{ ANIMAL_IS, "?x" }:
151151
ENTAILS("=>"):
152-
u(function(fact_ids,vars)
152+
u(function(vars)
153153
print("The animal is a: " .. vars.x)
154154
end)
155155

tests/factorial.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@ local kb = faces()
44
kb:defrule("expand"):
55
salience(100):
66
pattern{ "Factorial", "?x" }:
7-
u(function(fact_ids, vars)
7+
u(function(vars)
88
return vars.x > 1
99
end):
1010
ENTAILS("=>"):
11-
u(function(fact_ids, vars)
11+
u(function(vars)
1212
kb:fassert{ "Factorial", vars.x-1 }
1313
end)
1414

1515
kb:defrule("compute"):
1616
salience(0):
1717
pattern{ "Factorial", "?x" }:
1818
pattern{ "Result", "?y", "?z" }:
19-
u(function(fact_ids, vars)
19+
u(function(vars)
2020
return vars.x == vars.y+1
2121
end):
2222
ENTAILS("=>"):
23-
u(function(fact_ids, vars)
23+
u(function(vars)
2424
kb:fassert{ "Result", vars.x, vars.x * vars.z }
2525
end)
2626

tests/test.lua

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@ kb:fassert{ "duck sound", "poww" }
5858

5959
kb:defrule("duck2"):
6060
salience(100):
61-
pattern{ "duck sound", { "?name1", "?name2" } }:
61+
var("?f1"):pattern{ "duck sound", { "?name1", "?name2" } }:
6262
match("?name1", "q.*"):
6363
ENTAILS("=>"):
6464
fassert{ "sound is", { "?name1", "?name2" } }:
65-
u(function(fact_ids, vars)
66-
print("DEBUG", kb:consult(fact_ids[1]), vars.name1, vars.name2)
65+
u(function(vars)
66+
print("DEBUG", kb:consult(vars.f1), vars.name1, vars.name2)
6767
end)
6868

6969
kb:defrule("init"):
@@ -99,7 +99,7 @@ kb:facts()
9999
kb:defrule("MultiValuated"):
100100
pattern{ "duck sound", "$?p" }:
101101
ENTAILS("=>"):
102-
u(function(fact_ids, vars)
102+
u(function(vars)
103103
print(vars.p)
104104
end)
105105
kb:agenda()

tests/test2.lua

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ kb:defrule("user"):
1212
pattern{ "AnimalIs", "?x" }:
1313
numeric("?y"):
1414
numeric("?z"):
15-
u(function(fact_ids, vars)
16-
return (vars.z == vars.y*4) and (vars.y % 2)==0
17-
end):
15+
u("(z == y*4) and (y%2)==0"):
1816
ENTAILS("=>"):
1917
fassert{ "EvenAnimal", "?x", "?y", "?z" }
2018

0 commit comments

Comments
 (0)