next up previous contents index
Next: References Up: The Generic Frame Protocol Previous: Differences amongst FRSs

The GFP Procedure Language

    In a networked environment, bandwidth and latency are typically the limiting factors for system performance. In order to improve efficiency in a networked environment, GFP defines an FRS and implementation language independent procedure language. The procedure language allows an application writer to combine several GFP operations into a single call. If that call requires transmission over a network, this results in a substantial performance boost.

For example, computing the information necessary to display a complete class graph for a knowledge base would require calling at least two GFP operations for each class (one to get its subclasses, and one to get a printable representation of its name). This could result in many thousands of calls to GFP operations. Using the procedure language, only a single network call needs to be made; the remaining calls are executed on the GFP server and only the results are transmitted back over the network.

The procedure language supported by GFP is expressed in a simple Lisp-like syntax and provides a dynamic binding model for its variables. The procedure language supports all of the GFP operations, and the ability to create and register new procedures, and a small number of programming constructs (such as conditionals, iteration, and recursion). No operations are supported within the procedure language that might compromise the security of a GFP server machine.

The basic object in the procedure language is the procedure. Procedures are created with   create-procedure, which allows the parameter list, body, and an environment to be specified. Once created, a procedure is registered under a name using   register-procedure. It is invoked using the   call-procedure operation.

Procedure Language Syntax

The following grammar specifies the syntax for the parameter list and the body of a procedure. Literals in the grammar are enclosed in string quotes, and token names are in upper case.gif

parameter-list ::= "(" parameter* ")"
     parameter ::= SYMBOL
          body ::= body-form*
     body-form ::= procedure-call | simple | binding-form | loop-form
procedure-call ::= "(" SYMBOL body-form* ")"
  binding-form ::= "(" ["LET"|"LET*"] "(" binding* ")" body ")"
       binding ::= "(" SYMBOL body-form ")"
     loop-form ::= "(" "DO-LIST" binding body ")"
        simple ::= atom | quotation
     quotation ::= "(" "QUOTE" form ")" | "'" form
          form ::= atom | list
          list ::= "(" form* ")"
          atom ::= INTEGER | FLOAT | STRING | SYMBOL | true | false
          true ::= "T" | "TRUE"
         false ::= "NIL" | "FALSE" | "()"
In conditional expressions (e.g.,   if,   while), any non-false value is considered true just as any non-zero value is considered true in the C language.

A string literal is enclosed in double quotes. All characters are permitted within strings, including newlines and nulls; the double quote character and the backslash character must be escaped with a backslash character. The arguments to   create-procedure may be embedded within strings. This means that the level of escaping required by the host language will be necessary. For example, in Java, a procedure body for the expression (f1 x "hello") is specified by the string "(f1 x \"hello\")".

The procedure language is whitespace-insensitive, but some whitespace is necessary to delimit tokens other than parentheses and quotes. The INTEGER and FLOAT data types have their normal textual representation, with exponential notation (as found in Java). Semicolons introduce end-of-line comments. All characters following a semicolon are ignored until the next newline. Hash (#) characters are not permitted.

The SYMBOL data type has its origins in Lisp, but is supported by all GFP servers. Symbols are used as identifiers for variables and procedures, but they are also permitted as literals. Any non-control character can be part of the name of a symbol, with the exception of colons, semicolons, hashes, parentheses, quotes, or whitespace. A symbol may not start with a digit.

The namespace of symbols is partitioned into regions called packages. The procedure language is case and package insensitive except for symbol literals. It is an error for a portable program to assume the existence of any package other than the GFP or keyword packages in a procedure. A KB or FRS may define packages other than the keyword and GFP packages. There is no need to be aware of the packages defined for the KB unless the application specifically needs to test for object identity with symbol literals defined in the KB. If this is the case, it is the responsibility of the client program to create any necessary packages on the client side.

In order to refer to a symbol X in a package FOO, we write a double colon between the package name and the symbol: FOO::X. The keyword package holds many symbols that are used as literals. Keywords are used so frequently that they have a special syntax. The symbol X in the keyword package is spelled :X. In addition to their special syntax, every keyword is also a global constant whose value is itself. Thus, the value of the keyword :X is always the symbol :X.

Strings, numeric literals, and keywords do not need to be quoted, they denote themselves. A quoted literal xxx can be expressed either as (QUOTE xxx) or as 'xxx. For example, the symbol FRED would be expressed as 'FRED. A list containing the elements A, 42, and "Hello" would be expressed as '(A 42 "Hello"). All non-quoted symbols within a procedure body are either variable references or the names of operators.

Each body-form returns a value, although that value may be ignored. The value returned by a procedure is the value returned by its last body form. The first element in a procedure-call is the name of an operator. That operator is applied to the other body-forms in the procedure-call - its arguments. The arguments themselves may be other procedure-calls. For example, (F1 1 2) means ``apply the F1 operator to the numbers 1 and 2.'' In the C language, one would express this as F1(1, 2). Unlike C or Java, there are no infix operators in the procedure language. The equivalent of 1 + 2 in C is (+ 1 2). Most characters are permitted in symbols, so whitespace must be used to delimit them. For example, (+1 2), means ``apply the operator +1 to the number 2''.

Suppose that F1 is the name of a procedure whose parameter list is (x y) and whose body is (* x (+ y 2)). I.e., F1 returns the result of multiplying x by the sum of y and 2.

Variable names (symbols, such as x, above) denote values. Attempting to retrieve the value of a variable with no assigned value signals an error. Variables acquire values either through being set, or through being bound. Constructs in the language establish bindings (e.g., LET, DO-LIST). The simplest means of establishing a binding, however, is through being a parameter to a procedure. In the above example, the procedure F1 has x and y as its parameters. Inside this procedure, we say that x and y are bound to the values passed in to the procedure when it was called. If we made a call to F1 such as (F1 3 4), x would be bound to 3, and y would be bound to 4 during the execution of F1. On exiting F1, x and y are restored to the values they had before, if any. During the execution of F1, we may set the value of either x or y to any other value, but the original value will still be restored upon exit from F1. GFP operations are supported within procedures using the standard Lisp keyword argument notation. Thus, if we have a frame that is the value of the variable F, and an own slot that is the value of the variable S, we could get the value of that slot in F with the call

       (get-slot-value F S :kb kb :slot-type :own)
where kb is a variable denoting the KB in which F resides. Note that the procedure language syntax may differ from the GFP syntax in the implementation language's binding. For example, in Java code we would write the above call to get-slot-value as
        kb.get_slot_value(F, S, _own);

We are now in a position to understand a somewhat more complicated procedure. The following Java code fragment creates, registers, and calls a procedure that will return a nested list structure representing the taxonomic structure below a given class down to a given maxdepth.

 1 mykb.register_procedure(
 2   mykb.intern("GET-TAXONOMY"),
 3   mykb.create_procedure(
 4     "(class depth maxdepth)",
 5     "(let ((pretty-name (get-frame-pretty-name class :kb kb)))" +
 6     "   (if (>= depth maxdepth)"                              " +
 7     "     (list (list class pretty-name) :maxdepth)           " +
 8     "     (list (list class pretty-name)                      " +
 9     "           (do-list (sub (get-class-subclasses           " +
10     "                            class :kb kb                 " +
11     "                            :inference-level :direct))   " +
12     "              (get-taxonomy sub (+ depth 1) maxdepth)))))" +
13     "))";
14 Cons classes = mykb.call_procedure(p, Cons.list(mykb._thing, 0, 4));
Line 4 specifies the parameter list. The procedure takes three arguments: a class that is the root of the taxonomy, the current depth, and the maxdepth. Lines 5-13 specify the procedure body as a multi-line string. First a binding is introduced for the variable pretty-name using the   let operator. The pretty-name is bound to the result of calling   get-frame-pretty-name on class, which was one of the arguments to the procedure, and the variable kb. The kb variable will be bound by the invocation of   call-procedure on line 14. In line 6, we check the depth. If it is greater than or equal to the maxdepth argument, then we return a list whose first element is itself a list of the class and its pretty name, and whose second element is the keyword :maxdepth. This keyword is being used as a flag to the caller that this class may have subclasses, but that they were not explored due to the depth limit. If the depth has not exceeded the limit, then we construct a return value on line 8. It is also a list whose first element is also a pair consisting of the class frame and its pretty name. The second element, however, is a list containing one sublist for each subclass of class. We collect the subclasses in line 9 by calling   get-class-subclasses using :inference-level :direct. The   do-list operator iterates over the list of subclasses, binding sub to each one in turn, and executing get-taxonomy recursively on each subclass in line 12. The   do-list returns a list with one value for each iteration. Line 14 actually invokes the get-taxonomy function on mykb starting from   :thing, at depth 0, up to maxdepth 4. The result will be a list such as
((fr0001 "thing")
 ((fr0002 "animal")
  ((fr0003 "mammal")
   ((fr0004 "feline")
    ((fr0005 "cat") :maxdepth))
   ((fr0006 "canine") ...))))
The class element of each pair will be returned as a   frame-handle, whose appearance will differ across implementations.

Procedure Language Forms

In addition to the GFP operations defined in Section 3.7, the procedure language supports the following forms.

Diadic multiplication of numbers.
(* 42 2.5) = 105.0

Diadic addition of numbers.
(+ 42 2.5) = 44.5
Diadic subtraction of numbers.
(- 42 2.5) = 39.5
Diadic division of numbers.
(/ 42 2.5) = 16.8
The numeric less-than operator.
(< 42 2.5) = NIL
The numeric less-than-or-equal operator.
(<= 42 2.5) = NIL
(<= 2.5 42) = T
(<= 42 42) = T
Equality of numeric quantities.
(= 42 2.5) = NIL
(= 42 42) = T
(= 42 42.0) = T
The numeric greater-than operator.
(> 42 2.5) = T
The numeric greater-than-or-equal operator.
(>= 42 2.5) = T
(>= 2.5 42) = NIL
(>= 42 42) = T
Short-circuit conjunction of any number of arguments. This is equivalent to the && operator in C or Java. A conjunct is true if it is not NIL-valued. The whole AND expression returns NIL immediately after finding the first NIL conjunct.
An operator that builds a list by appending its arguments, each of which must be a list. For example, if X has the list (D E F) as its value, (append '(A B C) x) will return (A B C D E F).
(Assoc <<key>> <<list>>) gets the element associated with the key in the list, where the list is a list of lists, keyed by the first element of each sublist. For example, (assoc 'b '((a 2) (b 200))) will return (b 200). If the key is not found, assoc returns NIL.
(Connection <<kb>>) returns the connection associated with kb
Loops over all the elements in a list binding the variable var to each successive list element, and executing a set of body forms, and finally returning a list whose elements are the values evaluated for each list element. Syntax:
       (do-list (var <<list expression>>)
          <<body form 1>>
          <<body form 2>>
          <<body form n>>)
For example,
       (do-list (x '(1 2 3 4 5))
          (+ x 100))
will return (101 102 103 104 105).
The object identity equality operator. This operator returns true if the arguments represent the same object or the arguments are numbers and the numbers are equal. This is similar to the == operator in C and Java.
(eql 42 42) = T
(eql 42.0 42.0) = NIL
(eql 'foo 'foo) = T
(eql '(foo "Hello") '(foo "Hello")) = NIL
(eql '(foo "Hello") '(foo "hello")) = NIL
(eql "A string" "A string") = NIL
(eql "A string" "A String") = NIL
An equality operator like EQL, but that also takes list structure into account, and that treats strings with the same characters as equal.
(equal 42 42) = T
(equal 42.0 42.0) = T
(equal 'foo 'foo) = T
(equal '(foo "Hello") '(foo "Hello")) = T
(equal '(foo "Hello") '(foo "hello")) = NIL
(equal "A string" "A string") = T
(equal "A string" "A String") = NIL
An equality operator like EQUAL, but that also does case-insensitive string comparison.
(equalp 42 42) = T
(equalp 42.0 42.0) = T
(equalp 'foo 'foo) = T
(equalp '(foo "Hello") '(foo "Hello")) = T
(equalp '(foo "Hello") '(foo "hello")) = T
(equalp "A string" "A string") = T
(equalp "A string" "A String") = NIL
(Error <<type>> &rest args) signals an error of the specified type. For example,
(error :not-coercible-to-frame :frame fff :kb kb)
signals a   not-coercible-to-error error, saying that the value of fff is not a frame in the KB identified by kb.
The first element of a list.
(first '(a b c)) = A
(first NIL) = NIL
Returns the zero-indexed first N elements of a list.
(firstn 0 '(a b c d)) = NIL
(firstn 2 '(a b c d)) = (A B)
(firstn 9 '(a b c d)) = (A B C D)
(Getf <<list>> <<key>>) gets the property value associated with the key in the list, where the list is an alternating list of keys and values. For example, (getf '(a 2 b 200) 'b) will return 200. If the key is not found, getf returns NIL.
The conditional operator.
Syntax: (if <<condition>> <<then>> <<else>>)
or (if <<condition>> <then>>)
Example: (if (= x 42) (list x 100) (- x 100))
If x is 42 then return the list containing x and 100, otherwise return x - 100. If no <<else>> clause is provided and <<condition>> evaluates to NIL, then returns NIL.
Establishes bindings for variables and executes a body with those bindings.
 (let ((<<var1>> <<val1>>)
       (<<var2>> <<val2>>)
       (<<varn>> <<valn>>))
    <<body expression-1>>
    <<body expression-2>>
    <<body expression-n>>)
All the <<vali>> expressions are evaluated before the bindings for the variables are established. I.e., it is as if the <<vali>> are evaluated in parallel. The value returned by the LET expression is the value of the last body expression. For example,
(let ((x '(a b c d))
      (y 2001))
  (push 100 x)
  (push y x)
will return (2001 100 A B C D).
Establishes bindings for variables and executes a body with those bindings. Syntax:
   (let* ((<<var1>> <<val1>>)
          (<<var2>> <<val2>>)
          (<<varn>> <<valn>>))
     <<body expression-1>>
     <<body expression-2>>
     <<body expression-n>>)
Each <<valN>> expression is evaluated and a binding is established for <<varN>> before the system proceeds to the next binding. The value returned by the LET* expression is the value of the last body expression. For example,
	(let* ((x '(a b c d))
       (y (list* 2001 x)))
  (push 100 x)
  (list x y))
will return ((100 A B C D) (2001 A B C D)). LET* is equivalent to a series of nested LET expressions, so
(let* ((x 42)
       (y (list x 200)))
is equivalent to
(let ((x 42))
  (let ((y (list x 200)))

An operator that builds a list out of its arguments. List can take any number of arguments. The arguments are evaluated, so you must quote any symbol or list literals, except for keywords, T, and NIL. For example, (list x 42 'x) returns the list of three elements; the current value of x, 42, and the symbol X.
An operator that builds a list by appending all but its last argument to its last argument, which must be a list. For example, if X has the list (D E F) as its value, (list* 'A 'B 'C x) will return (A B C D E F).
(Member <<value>> <<list>>) is true if the value is in the list. The operation   eql-in-kb is used to test equality. If the value is found, the first sub-list containing the value is returned. For example, (member 42 '(1 2 42 200 2001)) will return (42 200 2001). If the value is not found, member returns NIL.
The monadic negation operator. Non-NIL values map to NIL, and NIL maps to T.
Returns the zero-indexed Nth element of a list.
(nth 0 '(a b c d)) = A
(nth 2 '(a b c d)) = C
(nth 9 '(a b c d)) = NIL
Returns the zero-indexed Nth tail of a list.
(nth-rest 0 '(a b c d)) = (A B C D)
(nth-rest 2 '(a b c d)) = (C D)
(nth-rest 9 '(a b c d)) = NIL
Short-circuit polyadic disjunction. This is equivalent to the || operator in C or Java. A disjunct is true if it is not NIL. The whole OR expression returns the value of the first non-NIL disjunct.
Evaluates all of its arguments in sequence, and returns the value returned by the last argument. All arguments but the last are therefore interesting only if they perform side-effects. For example (progn (push 42 x) (push 2001 x) x) will push 42 onto X, and will then push 2001 onto the new value of x, and will finally return the current value of x. Thus, if x previously had the value (A B C), it will now have the value (2001 42 A B C).
Pushes a new value onto a list named by a variable. For example, if x has the value (B C D), then after evaluating the expression (push 'A x), x will have the value (A B C D). The call to push returns the new value of the variable.
The QUOTE operator denotes a literal object. QUOTE can be used either in the form (quote foo) or with the shorthand syntax 'foo to denote the literal symbol foo, as opposed to the value of the variable foo. For example, if foo has the value 42, then the expression (list (quote foo) foo) will have the value (FOO 42).
(Remove <<value>> <<list>>) returns a new list from which has been removed all instances of the value in the original list. The operation   eql-in-kb is used to test equality. List order is preserved. For example, (remove 42 '(1 2 42 200 42 2001)) will return (1 2 200 2001). If the value is not found, remove will return a copy of the original list.
(Remove-duplicates <<list>>) returns a new list from which has been removed all duplicate entries in the original list. The operation   eql-in-kb is used to test equality. List order is preserved. For example, (remove-duplicates '(1 2 42 200 42 2001)) will return (1 2 200 42 2001).
The tail of a list.
(rest '(a b c)) = (B C)
(rest NIL) = NIL
The non-destructive reversed elements of a list or string.
(reverse '(a b c)) = (C B A)
(reverse "Hello") = "olleH"
Sets a new value for a variable. For example, (setq A 42) assigns the value 42 to the variable A. It is an error to set the value of a variable that is not already bound. The call to setq returns the new value of the variable.
(Sort <<list>> <<kb>>) copies the <<list>>, sorts the copy, and returns it. The elements of the list are sorted according to the following predicate, with lower ranked values appearing closer to the front of the resulting list. If an element of <<list>> is itself a list, then the first element of that element is iteratively taken until a non-list is found. A total ordering is established within the data types understood by GFP. Objects of a type that is earlier in the following table are earlier in the sort. For pairs of objects of the same type as shown in the table, the predicate specified in the right hand column is used.

Loops while a condition is true, executing a body. Syntax:
  (while <<condition expression>>
    <<body form 1>>
    <<body form 2>>
    <<body form n>>)
For example,
 (while (has-more enumerator)
   (push (next enumerator) result))
will collect all the values in the enumerator by pushing them onto the list called result. Note that this will build a list in the reverse order of the list built in the example for while-collect.
Loops while a condition is true, collecting up the results of executing a body. Syntax:
 (while-collect <<condition expression>>
   <<body form 1>>
   <<body form 2>>
   <<result body form>>)
For example,
 (while-collect (has-more enumerator)
   (next enumerator))
will collect all the values in the enumerator.

next up previous contents index
Next: References Up: The Generic Frame Protocol Previous: Differences amongst FRSs

For questions regarding GFP