Version 8 of Playing predicate logic

Updated 2002-04-18 13:43:29

Richard Suchenwirth - As one more little step in playing Prolog, here is some predicate logic in Tcl, where predicates are implemented as procs created by string manipulation. The procedure with the fancy name "!" takes a Horn clause (one or more assertions) as arguments, for instance

 ! {human Socrates}
 ! {mortal $x} {human $x}

With one assertion, it is taken for granted (read, in the example, "it is true that the predicate 'human' holds for Socrates", or, "Socrates is a human"). With more than one, the first assertion is true if all other assertions are true, with variables (prefixed with a dollar sign) acting as expected ("something is mortal if it is human"). Such non-first assertions may also be negated by prepending an exclamation mark:

 ! {bad Satan}
 ! {good $x} {!bad $x} ;# so everyone except Satan is considered good...

Each first assertion creates or extends a proc of same name (which returns truth values 1 or 0), so you can try

 mortal Socrates ;# => 1
 mortal Diogenes ;# 0 - binary logic: all that's not true is false

The generated procedures look like this:

 proc human {0} {expr {$0 == "Socrates"}}
 proc mortal {x} {expr {[human $x]}}

(remember that digits are valid variable names in Tcl - for one-arg assertions, the arguments are just numbered from 0 up with the index generator iota). After another call like

 ! {human Plato}

proc human is rewritten to

 proc human {0} {expr {$0 == "Socrates" || $0 == "Plato"}}

Similarly, ! {mortal $x} {animal $x} extends mortal to

 proc mortal {x} {expr {[human $x] || [animal $x]}}

and so on. This is still far from Prolog: backtracking is missing, as well as semi-free variables, e.g.

 ! {famous $x} {founderOf $x $y} {bigCompany $y}

where $y should be filled with a suitable value, but currently just raises errors. Also, predicates of different arity cannot yet be mixed. However, complex structures like

 ! {healthy $x} {early2bed $x} {early2rise $x} {man $x}
 ! {wealthy $x} {early2bed $x} {early2rise $x} {man $x}
 ! {wise $x} {early2bed $x} {early2rise $x} {man $x}
 ! {man Bill}
 ! {early2bed  Bill}
 ! {early2rise Bill}

can be used and go to prove that "early to bed and early to rise, makes a man healthy, wealthy, and wise", even if that may not be true in reality... } proc ! {args} {

    set head [lindex $args 0]
    set pred [lindex $head 0]
    set argl [lrange $head 1 end]    
    if {[info proc $pred] != ""} {
        set ebody "[lindex [info body $pred] 1] || "
    } else { 
        set ebody ""
    }
    if {[llength $args]==1} {
        set argv [iota [llength $argl]]
        foreach name $argv value $argl {
            lappend body "\$$name == \"$value\""
        }
        append ebody [join $body { && }]
    } else {
        set argv [string map {$ ""} $argl]
        set body "\[[join [lrange $args 1 end] {] && [}]\]"
        append ebody [string map "\[! !\[" $body] 
        }
     proc $pred $argv "[list expr $ebody] " ;# bug 545644 workaround
 }
 proc iota n {
    set res {}
    for {set i 0} {$i<$n} {incr i} {lappend res $i}
    set res
 }

if 0 {Unification (tentative assignment of possible values to an unbound variable) is a harder nut to crack. For the moment, all I have is the following proc all that extracts all possible values from a "terminal" proc body, and one could iterate over that with foreach:}

 proc all {what} {
    set ebody [lindex [info body $what] 1]
    if  {[string first == $ebody]<0} {error "$what is not a terminal predicate"}
    set res {}
    foreach {- - word -} $ebody {lappend res $word}
    set res
 }

 proc iota n {
    set res {}
    for {set i 0} {$i<$n} {incr i} {lappend res $i}
    set res
 }

Arts and crafts of Tcl-Tk programming