{rat 2 3}stands for the rational number 2/3, "two thirds" (and with more precision than double numbers !-) "Construction" of such "rats" could be dead easy:

proc make(rat) {num den} {list rat $num $den}but we also wish simplified representation, e.g.

*make(rat) 3 6*should result in

*{rat 1 2}*, so we better determine the greatest common denominator and normalize with that:

proc make(rat) {num den} { set gcd [gcd $num $den] list rat [expr {$num/$gcd}] [expr {$den/$gcd}] } proc gcd {a b} {expr {$b==0? $a: [gcd $b [expr {$a % $b}]]}}#.. and some accessor functions:

proc num(rat) rat {lindex $rat 1} proc den(rat) rat {lindex $rat 2}Notice the unusual proc names that look like elements of a (virtual) array of procs - so far, that's only sugar, but will soon come into full play. Now we can implement addition of rationals:

proc +(rat) {a b} { set an [num(rat) $a]; set ad [den(rat) $a] set bn [num(rat) $b]; set bd [den(rat) $b] make(rat) [expr {$an*$bd + $bn*$ad}] [expr {$ad * $bd}] }So far the "rat-specific" code. Generic addition shall dispatch on the type of the arguments, which recognizes

*rat*s (and future other tagged data types) by their tag, and else implicitly returns an empty string ("") for regular Tcl numbers, which can be seen as one-element lists:

proc type x {if {[llength $x]>1} {lindex $x 0}}For now assume that both operands are of same type, so it suffices to check the first. Generic addition, which immediately calls the appropriate specific addition routine, may look like this:

proc + {a b} {+([type $a]) $a $b}#--- and the addition for "regular" numbers is of course easy:

proc +() {a b} {expr {$a + $b}}...which has the empty string in its parens. Quick test:

+ {rat 1 2} {rat 1 3} ==> {rat 5 6}But of course the restriction that both operands must be of same type is too strict (hey, this is not Ada;-). We better determine the "maximal" type of the operands, and "coerce" (upgrade) those that are behind:

proc maxtype {a b} { set types {"" rat real compl} set typea [lsearch $types [type $a]] set typeb [lsearch $types [type $b]] lindex $types [max $typea $typeb] } proc max args {lindex [lsort -real $args] end}Coercion (raising arguments to the required height) and the possible lowering of the result is best triggered in the generic addition, "+":

proc + {a b} { set type [maxtype $a $b] lower [+($type) [raise($type) $a] [raise($type) $b]] } proc raise(rat) x { switch -- [type $x] { rat {return $x} "" {make(rat) $x 1} } } proc raise() x {set x} ;# identity #-- Lowering works under certain constraints, otherwise it's identity: proc lower x {lower([type $x]) $x} proc lower() x {set x} proc lower(rat) x {expr {[den(rat) $x]==1? [num(rat) $x]: $x}}Testing again:

+ 1 {rat 2 3} ==> {rat 5 3} + {rat 1 2} {rat 3 2} ==> 2Obviously, our generic addition works, both in raising an integer to a rational, and lowering a rational result back to integer. The end-user should only see and use the generic routines, while the specific implementation works under-cover. In SICP's Scheme, a global table of mappings from generic to specific was used, but I chose the simpler way of constructing command names, which like everything in Tcl are just strings, from a constant and a variable (type-dependent) part. The parens around the latter make no difference to Tcl (they matter only in variable references), but they gave me the opportunity to

- "generalize" the array concept, virtually, to procs, and
- allow the empty string as type name, so + and +() are different.

proc make(compl) {real im} {list compl $real $im} ;# constructor proc real(compl) c {lindex $c 1} proc im(compl) c {lindex $c 2} proc abs(compl) c {expr {hypot([real(compl) $c],[im(compl) $c])}} proc angle(compl) c {expr {atan2([im(compl) $c],[real(compl) $c])}} proc +(compl) {x y} { set xr [real(compl) $x]; set xi [im(compl) $x] set yr [real(compl) $y]; set yi [im(compl) $y] make(compl) [expr {$xr+$yr}] [expr {$xi+$yi}] } proc raise(compl) x { switch -- [type $x] { compl {return $x} rat {make(compl) [expr 1.0*[num(rat) $x]/[den(rat) $x]] 0} "" {make(compl) $x 0} } } proc lower(compl) x { expr {[im(compl) $x]==0? [real(compl) $x]: $x} }In my never-ending quest for the limits of Tcl I can only say: here they aren't... A final example for how we can now mix and match:

+ {compl 1 1} {rat 1 4} compl 1.25 1

AK Using

make(compl) [+ $xr $yr] [+ $xi $yi]instead might allow rationals as components of complex numbers. Of course, we have to make sure that users don't try to use a complex number as a component of complex number. This is not part of this code snippet.