6.1.2 Predicate Functions - naver/lispe GitHub Wiki

Predicate Functions: defpred and defprol

defpred: Predicate-like Approach Based on Pattern Matching

defpred is a function definition mechanism in LispE that extends pattern matching with predicate-based function selection, pred stands for predicate in this context. Similar to defpat, it allows multiple function definitions with the same name that are selected based on pattern matching.

(defpred name (P1...Pn) ...)

A predicate function can take as many parameters (Pi) as you want (well... actually up to 15). A parameter is defined as followed:

Parameter Definition Rules

  1. P -> (type P1...Pn) where type is a data type or a basic type
  2. P -> (condition v1...vN P1) where condition is either a function call or a basic instruction. v1...vn are values, there can be only one parameter, always at the end of the condition
  3. P -> atom a simple variable, which can be unified with an argument.
  4. P -> value that should match the argument. Note that value can be a list, if the first element of that list is not a type, a function or an instruction.

Of course, all these definition can be nested one into the others.

Important: The order in which functions are applied is constrained by the presence of a type on the first parameter. If this type is missing, when P is only a condition or an atom, then this function is considered as a fallback and will only be applied once all the corresponding typed functions have been tested. The selection is done according to the argument type...

Key Differences from defpat

  1. In defpred, each instruction in the function body is evaluated as a Boolean.
  2. If any instruction returns false (nil or 0), the function fails
  3. Upon failure, the system attempts the next function definition
  4. It provides built-in backtracking similar to Prolog's logical programming approach

Parameter Matching:

  1. Supports type-based and pattern-based parameter matching like defpat
  2. Uses the same parameter definition rules
  3. Can match against lists, dictionaries, and custom data types

Example:

; Functions are tried in order of definition
; Each function body is a series of boolean tests
; Successful execution stops further function searching
; Failed tests trigger searching for alternative function definitions

(defpred teste ([]) 
   true
)

(defpred teste ([a $ b])
   (< a 10)
   (println a)
   (teste b)
)

(defpred teste (l)
   (println "We stop" l)
)

(teste '(1 2 11 12 13))

; yields

1
2
We stop (11 12 13)

Operator '@': non unifiable parameter

defpred also adds another operator, the non unifiable operator. This operator is used to indicate that an element is not unifiable, which means that if you switch to another function, its value will not be reset to the value it had in the previous call.

Basically, to ensure proper backtracking, arguments are duplicated in a same session, so that each new call to a different body, will start with the same values as the previous calls.

You can also use this operator to avoid duplication, if you know that this value will remain constant through out the execution.


; b here is a non unifiable parameter
; which means that any modification of it is kept through the execution
; i is a unifiable parameter, any modification to it is lost when executing the next
; function
(defpred non_unif(i b@)
   (println "1" i b) ; display 1 () ()
   (push i 1)
   (push b 10)
; false is used here to force the execution of the next function
   false
)

; i value remains the same through out the execution
(defpred non_unif(i b@)
   (println "2" i b) ; display 2 () (10)
   (push i 2)
   (push b 20)
   false
)

(defpred non_unif(i b@)
   (println "3" i b) ; display 3 () (10 20)
   (push i 3)
   (push b 30)
   false
)

(setq x())
(non_unif (list) x)
(println x) ; display (10 20 30)

defprol : Prolog-like implementation

defprol allows defining multiple functions with the same name, similar to defpred. When a defprol function is called, each definition is attempted in order. For a given definition, the code body is executed sequentially. If any instruction in the body evaluates to false, that definition fails, and the system moves to the next definition. If a definition's body executes completely without failing, the last evaluated value of the body is collected. The final result of a defprol call is a list containing the collected values from all definitions that successfully completed their execution for that call.

The cut_ predicate can be used within a defprol body. If a definition successfully reaches a cut_, no further defprol definitions with the same name will be attempted for that call, regardless of whether the instructions after the cut_ in the same definition succeed or fail.

(defprol test(a b)
   (< a b)
   (+ a b)
)

(defprol test(a b)
   (= (- b a) 2)
   cut_
   (* a b)
)

(defprol test(a b)
   (< (+ a b) 100)
   (- b a)
)

(test 10 12) ; returns (22 120)
(test 10 14) ; return (24 4)