Adding, multiplying and composing functions

Arjen Markus (16 august 2004) In mathematics, you can combine functions just as you can add or multiply numbers:

    h = f + g

can be read as: the value h(x) of the function h at x is h(x) = f(x) + g(x)

More advanced:

   h = f o g

can be read as: the value h(x) of the function h at x is h(x) = f( g(x) )

It is quite easy to create procedures in Tcl that first apply the function f to x and then g to x and then add the results or to compose them like in the second example.

It is only slightly more complicated if you want to avoid creating new procedures! If you manipulate functions by creating new procedures each time, you create a memory leak - not to mention a naming problem.

So, here is a little script that does this: functions are stored in variables (note: no new procedures are created!) and they go away automatically. The only complication: we need a new version of the service procedure unknown.

Warning: in its present form it does very little error checking. It is just a thought experiment after all...


 # funcs.tcl --
 #    Experiments with function arithmetic
 #
 #

 proc unknown {args} {
   foreach {op f g fargs} $args {break}
   if { [llength $op] != 1 } {
      set fargs $f
      foreach {op f g} $op {break}
   }

   $op $f $g $fargs
 }

 # + --
 #    Compute the sum of two functions
 #
 # Arguments:
 #    f       First function
 #    g       Second function
 #    farg    Function arguments (optional)
 # Result:
 #    Either a new function definition or the sum
 #    of the value of the two functions
 #
 proc + {f g {fargs {}}} {
    if { [llength $fargs] == 0 } {
       list + $f $g
    } else {
       expr {[$f $fargs]+[$g $fargs]}
    }
 }

 # * --
 #    Compute the product of two functions
 #
 # Arguments:
 #    f       First function
 #    g       Second function
 #    farg    Function arguments (optional)
 # Result:
 #    Either a new function definition or the product
 #    of the value of the two functions
 #
 proc * {f g {fargs {}}} {
    if { [llength $fargs] == 0 } {
       list * $f $g
    } else {
       expr {[$f $fargs]*[$g $fargs]}
    }
 }


 # o --
 #    Compose two functions
 #
 # Arguments:
 #    f       First function
 #    g       Second function
 #    farg    Function arguments (optional)
 # Result:
 #    Either a new function definition or the value
 #    f(g(x))
 #
 proc o {f g {fargs {}}} {
    if { [llength $fargs] == 0 } {
       list o $f $g
    } else {
       $f [$g $fargs]
    }
 }

 # I --
 #    Identity function
 #
 # Arguments:
 #    farg    Function arguments
 # Result:
 #    The argument
 #
 proc I {fargs} {
    return $fargs
 }

 # main --
 #    Testing the functions ...
 #
 set twox [+ I I]
 puts "Double 2: [$twox 2]"
 set sqx  [* I I]
 puts "Square of 3: [$sqx 3]"
 set twosqx [o $twox $sqx]
 puts "Doubled square of 3: [$twosqx 3]"

RS: Nice! Note however that you lose all other features of unknown (like auto-loading) - better overload it, e.g. like in Let unknown know ...


See also Functional composition