Hey! I'm nearing the end of a 9-hour first-time-coding-with-pyke marathon. Pyke is fantastic! Thank you!
The only reason I'm taking a break soon is because I think I need a special.eval(...) function to do what I want. I want to store patterns in the fact base and then eval them in the rules. My use case is that I am storing pre and post conditions for python functions in the fact base so that I can reason over them. However, to reason over them, I need to be able to evaluate them. But, to reason over them, I also need to be able to treat them as facts (to query, etc). So I need to combine fact and rule base functionality. I think a special.eval(...) that takes one bound variable and evals it in the current rule context or in a specifically supplied context would do what I need. I am also completely open to other suggestions that will enable my use case but don't involve a special.eval(...).
I have no idea how hard this would be to implement. I hadn't looked at Pyke before 12 hours ago, but it's close to allowing me to do some stuff I've been dreaming about doing in Python for over a year. So, please pardon my 5:30 AM stupor. ;)
Though the documentation does not mention this, you can store values other than tuples, strings and numbers as fact arguments. (I just didn't want mutable arguments -- but don't actually check for this).
I'm wondering whether you could store your pre/post-conditions as python functions in your facts rather than a python expression as a string.
Then you could use any of the following in your rules:
check $fn($arg1, $arg2) -- if the $fn just returns a boolean value that you want to drive the inferencing
python $fn($arg1, $arg2) -- if the $fn is just called for side-effects (eg., to output something)
$ans = $fn($arg1, $arg2) -- if the $fn returns a more complicated answer
$item in $fn($arg1, $arg2) -- if the $fn returns an iterable that you want to iterate over through back-tracking.
One limitation, if this works for you, is that there is no way to specify a python function as an argument to a fact in a .kfb file.
Do you think that this would work for you?
Thanks for the quick reply!
I see I was unclear - my pre and post conditions are expressed as Pyke patterns, not in raw Python. I was hoping for a way to retrieve a Pyke pattern as a fact and eval it. Say:
function('open, ('exists(filename)'), ('exists(File)', 'bound_attribute(File, name, $filename)'))
So that the rule could match like:
Function($name, $pre, $post)
and then might simply state
which would assert that there is now a File named $filename.
This example is *very* rought - I haven't figured out the knowledge engineering part fully yet - but I do think I need to eval.
Well, the first thing that comes to mind is to have 'pre' and 'post' rules. I don't know if you're using forward chaining or backward chaining. I'll use backward chaining examples here, but you could do the same thing forward chaining...
bound_attribute(File, name, $filename)
then instead of the "Function" lookup followed by special.eval, you'd just have:
meaning "do the post condition for $name".
You could then have other pre and post rules for other situations (pre(close), etc).
What this solution ignores (for now) is the parameter passing. If you have a fixed set of parameters, you can add those to the pre and post rules:
use pre(open, $filename)
use post(open, $filename)
Whether a single parameter makes sense everywhere or not for you, I don't know. But you get the idea. You can use parameters to pass values both into and out of these rules.
On the parameter question, I think that you'll find that if you want to deal with these rules generically by some top-level rules (where you imagine your calls to "Function" and "special.eval" are), you're going to need a standard set of parameters there anyway, even if these only get passed to lower level pre/post kind of rules and not examined directly by your top-level rules.
Thanks! I'll have to ponder this more to see if I can make it work. The initial problem I see is that, for my purposes, I need to be able to reason over the pre and post conditions. Also, as my reasoner synthesizes new functions (such as by constructing a chain; e.g., open-read-close), it will need to store the plans that describe these functions, as well as pre and post conditions for them, so that they can be used just like the primitive python functions. Since released Python versions don't change, putting the pre and post conditions for functions that are builtins or in the standard lib into the rule base is probably all right. However, I am concerned that I am merely delaying the problem until I start synthesizing new functions and trying to reason over them, as well. Of course, I guess I could write pure Python to add new rules, but I was hoping to avoid manually manipulating the rule base.
Are there calls to add new rules, akin to engine.add_universal_fact()? I didn't see them documented....
I am no expert rule-based programmer, as is probably apparent. ;) Please let me know whether my hesitation makes sense to you or not.
If you need to be able to do inferencing based on the individual conditions listed in the pre/post conditions for a function (for example, to check whether the post condition for one function satisfies the pre condition for the next function), then it may be better to stick with the tuple approach that you had first shown. This lets you treat the conditions as data for inferencing.
In that case, you can do something like:
........($name, $argument) in $condition_list
If different conditions have different numbers of arguments, you might want to always put all of the arguments into a tuple, so that there is always one argument (the tuple).
If you want to use pattern variables as arguments, you might represent them as a string starting with a dollar sign (say) and then convert them to pattern variables through another set of rules:
....use convert_argument($arg_in, $arg_in, $_)
........check $arg_in != '$'
....use convert_argument($arg_in, $arg_out, $bindings)
........check $arg_in == '$'
........lookup_name($arg_in, $arg_out, $bindings)
....use lookup_name($arg_in, $arg_out, (($arg_in, $arg_out), *$rest))
I don't think that you can use *$_ rather than *$rest in the prior rule, because bindings to anonymous variables are ignored...
....use lookup_name($arg_in, $arg_out, ($_, *$rest))
........lookup_name($arg_in, $arg_out, $rest)
then you'd need a recursive set of rules to handle each condition in the condition_list (rather than 'foreach', so that the pattern variable bindings are saved):
....use run_conditions((), $_)
....use run_conditions((($name, $argument), *$cond_rest), $bindings)
........convert_argument($argument, $arg_out, $bindings)
This example doesn't handle multiple arguments, or number arguments or tuple arguments with possible pattern variables as elements. That's an exercise left for the reader.
There is a little bit of magic here. You would do the initial call to run_conditions with an unbound second argument (e.g., just $bindings). The lookup_name would initially bind this to ((first_arg, $arg_out), *$rest), where $arg_out and $rest are still unbound. That lets the $name($arg_out) call bind $arg_out to a value. The later lookup_name calls will return the same $arg_out (bound or unbound) when presented with the same variable name. But when presented with a new variable name, it will bind the first $rest to another ((next_arg, $next_arg_out), *$next_rest). Each time, it leaves another $rest parameter to bind further variables to, so the $bindings tuple can just keep growing. This implementation means that the $bindings tuple is never fully bound (and, thus, can't be used in python code). To terminate the $bindings variable, you should be able to use:
....use terminate(($_, *$rest))
then just call terminate($bindings) before passing them to any python code.
Maybe some of this will help? Anyway, some things to think about and play with! ...
P.S. I have not tested these examples, so please excuse any bugs....
Oh, the lookup_found rule should have had a special.claim_goal() call on it to prevent backtracking to the lookup_not_found rule:
....use lookup_name($arg_in, $arg_out, (($arg_in, $arg_out), *$rest))
OK, I'm trying to understand all that. ;)
Almost all the syntax is recognizable. The only stumbling block is:
After both browsing and searching, I can't find the docs for the 'require' clause. A pointer or explanation will be most appreciated. After that, it's just a matter of understanding the semantics of your examples. :)
Thanks for all your help. This will, I'm sure, provide me with several hours of fun!
Oops! foreach should be forall. You'll find the require clause as part of the forall clause here:
Also, you asked whether you can add rules on the fly from python. The answer is no.