tag:blogger.com,1999:blog-8366702.post7745046498482104673..comments2009-04-25T09:23:27.261+02:00Comments on // comments are lies!: Using printout and readline in PyCLIPS applicationsJohan Lindberghttp://www.blogger.com/profile/13455767001846504270noreply@blogger.comBlogger1125tag:blogger.com,1999:blog-8366702.post-14813383245493942692008-09-09T23:17:00.000+02:002008-09-09T23:17:00.000+02:00Hi Johan,I will not pretend that we didn't discuss...Hi Johan,<BR/><BR/>I will not pretend that we didn't discuss this privately before, as we both agreed that an explanation of what lead me to certain implementation choices is needed. I think, however, that a better approach is to propose a simple solution to the problem as an introduction to my comment. I have to say, that this is neither a complete nor an elegant solution, however it should be sufficient to handle a reasonable number of cases. It's based on the possibility that CLIPS gives, to run a program stepwise. In fact, you can call <B>clips.Run(1)</B> in order to only execute one step of inference in the CLIPS engine. <B>clips.Run(limit)</B> has a return value, that reports the number of actually performed inference steps: at the end it returns less than <B>limit</B>, zero in our case. When <B>clips.Run()</B> returns, all CLIPS "streams" can be read and examined, and you can, for example, print the results to the console. So, with a code snippet similar to the following:<BR/><BR/><B>while(clips.Run(1) != 0):<BR/> out = clips.StdoutStream.Read()<BR/> err = clips.ErrorStream.Read()<BR/> do_something_with(out, err)</B><BR/><BR/>you can partially achieve the goal of loading a CLIPS program, intercepting its output and redirecting it to whatever you like. The only requirement is that the CLIPS program should not contain a call to the <B>(run)</B> function. Obviously this only solves the problem of capturing CLIPS output: interactive user input is not handled at all, which still poses a problem if you need to bundle an application into an executable. In this case your solution is still the most viable one.<BR/><BR/>Now I'll try to explain why PyCLIPS does not provide a direct way to interact with the underlying CLIPS engine I/O. In fact, PyCLIPS wasn't intended as a replacement to the CLIPS shell in the first place, it was conceived as an interface to the CLIPS API: that is, CLIPS is considered in PyCLIPS more as a library. Apart from some little additions, the "low-level part" of PyCLIPS just maps the API functions described in the Advanced Programming Guide one-by-one to Python. With the "high-level" module I just tried to provide the Python user with a more "pythonic" interface, made of classes and general use functions. Since CLIPS gave the possibility, I also tried to implement some utilities to assist a Python programmer during PyCLIPS interactive use, such as the <B>PrintXxxxx</B> functions. I also implemented a method (namely <B>SendCommand</B>) to issue commands directly to the CLIPS engine, as if it were an interactive session, but this is not an API function and you will not find it in the APG.<BR/><BR/>Initially, since the CLIPS engine provides a lot of useful output, I thought that it would be nice to give the possibility to the Python user to intercept the calls to I/O routines, the ones for reading and printing respectively from and to the console. However, CLIPS has a rather complex way of handling basic (and file) I/O. It uses "logical names" for the files and the basic I/O streams, and the functions <B>(printout ...)</B> and <B>(read)</B> are used for writing and reading. Apart from files, whose namespace exists in PyCLIPS separated from the one of Python, there are other "I/O routers" that handle console I/O. Opposedly to usual systems, where there are three I/O streams (stdin, stdout and stderr), here we have streams also for the prompt, for the trace messages and more in addition to the usual three ones. This initially made me think that it would have been quite difficult to allow a Python user to optionally redirect basic I/O to the console. So I ended up thinking that the possibility to specify a function to call whenever a character was written to some stream (or expected from some other) could have been a solution: in other words, a callback or a hook. However this also poses some problems. Mostly these two:<BR/><BR/>1) slowness: when such a function is provided, each character has to be examined by it. There are already some performance issues that I'm trying to solve, and this implementation could have a heavy impact on an already slowed down system;<BR/><BR/>2) the fact that the Python and CLIPS subsystems are separated and do their reasoning in different ways: a provided callback should have a well defined signature (for those unfamiliar with C/C++ terminology, signature is related to the parameter types combined with the type of the return value). But Python is dynamically typed, so I cannot check the parameter types in a function, and I surely am not able to determine its return value without executing it. Mandatory type checks are left to the function implementor, that is, the module user... a Python user cannot be expected to take the same precautions that a C/C++ programmer would though. A Python user expects the exception system to be a valuable aid in finding bugs and handling exceptional situations. Well, in the case of callbacks exceptions are also more of a problem than an aid: when Python bails out with an exception, if the interpreter is in the middle of a callback, it could leave either CLIPS or Python (or both) in an inconsistent state, while the user would generally not know for sure whether the program is executing inside or outside the CLIPS engine.<BR/><BR/>These seemed to me quite difficult issues to cope with, and at the time of implementation I decided to mantain the router system intact, as it is documented in the <EM>Advanced Programming Guide</EM>. So, to monitor CLIPS output, the PyCLIPS user has still to periodically check the streams, and for direct input... there are other PyCLIPS tricks.<BR/><BR/>One could argue, that PyCLIPS implements callbacks in a feature that seems to be one of the most appreciated ones: the possibility to call Python code from CLIPS (via the <B>(python-call ...)</B> construct). Well: strangely, this shows actually less problems than a specific callback! In fact, when you call a Python function from CLIPS, from a PyCLIPS user point of view no type checking is performed on function parameters and return values: everything is left to the CLIPS code for return values and to the Python function for arguments. Exceptions aren't handled at their best though, they are ignored when uncaught - with the possibility to force the output of a traceback (which doesn't stop the CLIPS program flow). Coherently with usual CLIPS behaviour, functions that raise uncaught exceptions just return the <B>SYMBOL FALSE</B> to the CLIPS engine.<BR/><BR/>This for the explanations. However, you suggested that there are many users that want to use PyCLIPS as a framework, as well as a way to directly execute CLIPS programs (for instance, to embed the programs in an executable package or a web application), and (I would add) as an interactive shell too. I'm flattered: this means that PyCLIPS is really flexible enough to allow its use in ways that I wouldn't initially imagine. This also means that I'll have to put some effort in solving this problem: I don't know right now what I can do, but I'll try to find out something. If there is a possibility to act at the Python level (as you propose) it's much better, because I wouldn't like to populate the low-level module with too many non-API functions.<BR/><BR/>I hope that this comment could be still useful, despite its excessive length!<BR/><BR/>I hope that we'll find a way to improve PyCLIPS usability.<BR/><BR/>Cheers,<BR/><BR/>F.Almost Earthlinghttps://www.blogger.com/profile/15918152294360385192noreply@blogger.com