Language Extensions

Currently QuLog includes only a core subset of Qu-Prolog functionality. It is our intention to include more functionality as the need arises but, in the meantime, it is easy to “lift” Qu-Prolog functionality to QuLog for a specific application as describle below. Given Qu-Prolog has a foreign function interface to the C-level, this means a QuLog application can also access the C-level.

The first question the programmer needs to ask is “Am I lifting functionality as a relation, function or action?”. The answer to this question determines the required approach. Remember that any functionality that is stateful needs to be lifted as an action. Because QuLog cannot check the various requirements imposted on definitions, for example modes and types, it is the programmers responsibility to make sure these requirements are met. The extra requirements on actions are given later. The examples given below can be found in lift_eg.qlg and lift_eg_support.ql in the examples/introduction folder.

The easiest of these to lift are relations. As an example consider listing gmtime which is a predicate for converting between seconds since the Unix epoch and the GM time structure. We consider the simplest case first - when the programmer wants to use the same name with the arguments in the same order and having the same types and modes. In this case, because we have a time data structure we declare this type as well as the relation type as below. This is all that is required to lift this predicate.

def time_t ::= time(nat, nat, nat, nat, nat, nat)
rel gmtime(!nat, ?time_t), gmtime(?nat, !time_t)

and then calling gmtime at the QuLog level (after type and mode checking) will simply call the Qu-Prolog predicate.

For various technical reasons we do not support lifting of functions: we have compiled in declarations that allow user defined functions to be used in higher order functions and we also compile in an extra rule that raises an exception in case the function would otherwise fail. Instead the approach that should be taken is to lift a relational version of the function and then define the required function in terms of this relation. So, for example, we want the function to delete an element from a list. Qu-Prolog already has the delete predicate defined and so we can lift that to the QuLog level by simply declaring the type:

rel delete(!T, !list(T), ?list(T))

and then defining the required function in terms of this relation:

fun deleteF(T, list(T)) -> list(T)
deleteF(Item, List) :: delete(Item, List, Result) -> Result

We can't use delete as the name of the function as it will get compiled into a delete/3 predicate which conflicts with the builtin predicate.

In fact any relation that has exactly one argument in ? mode and all the other arguments in ! mode can be turned into a function using the above approach.

Lifting actions require more effort. The main problem is that actions are deterministic and are not allowed to fail. A given Qu-Prolog predicate that we might want to lift might be non-deterministic and typically might fail under certain situations. It might also throw exceptions. We therefore need to consider how these situations need to be handled and write Qu-Prolog code to take this into account.

It is often the case that we might want to use the same name as the Qu-Prolog predicate and so we need to map the name at the QuLog level to the support code at the Qu-Prolog level.

For an example we consider the TCP support Qu-Prolog provides and look at the process of lifting this support. For this example we will only consider tcp_server.

The first thing to notice is that many of these predicates have a socket as an argument and that is an integer at the Qu-Prolog level. To provide better type checking we fist declare a socket type:

def socket_t ::= socket(nat)

We can then make the declaration for the action (noting that we can use default arguments).

act tcp_server(Socket:?socket_t, Port:!nat default 0,
Host:!atom default localhost)
"Create a Socket for a tcp_server for the given Port and Host"

Since the TCP predicates can raise exceptions we should trap them and turn them into QuLog exceptions by declaring the user exceptions:

def user_exception ::= tcp_exception(string)

The support code at the Qu-Prolog level is then

?-assert(qulog2qp_map(tcp_server(Socket, Port, Host),
tcp_server_interface(Socket, Port, Host))).

tcp_server_interface(Socket, Port, Host) :-
catch(tcp_server(Socket, Port, Host), Pattern,
handle_exception(Pattern)), !.

%% convert the QuProlog exception to the corresponding Qulog exception
%% where the argument is a string representation of the QuProlog exception
handle_exception(Pattern) :-
open_string(write, Stream),
write_term_list(Stream, [Pattern]),
stream_to_string(Stream, Str),
throw(tcp_exception(Str)).

The asserted fact qulog2qp_map is used by the QuLog compiler to replace the QuLog call by the Qu-Prolog level version of the call.

To access the C-level we use the Qu-Prolog foreign function interface to define predicates that call the C-level. Once this is done we simply follow the approach above to lift that Qu-Prolog interface code to the QuLog level.

The folder examples/ev3 contains a simple example using a Mindstorm EV3 robot. The C-code is specific to this application but is realatively easy to modify for other applications.

The folder examples/ROS contains a foreign-function interface to ROS: ROS messages are assumed to be strings representing Prolog terms and are placed in the message buffer of the thread that started the ROS interface.