Version 0 of Implementing enumerated types

Updated 2001-06-08 09:44:22

Is there a mechanism whereby I can create constants or even better, enumerated types within Tcl?


KBK: In Tcl, everything is a string, so you can simply use your names as constants. If you want to assign numeric values, you can do something like:

 set fruits { apple blueberry cherry date elderberry }
 set i 0
 foreach fruit $fruits {
     set fruit_number($fruit) $i
     incr i
 }
 proc fruit_to_number { fruit } {
     variable fruit_number
     if { [catch { set fruit_number($fruit) } number] } {
         return -code error "$fruit: no such fruit"
     } else {
        return $number
     }
 }
 proc number_to_fruit { number } {
     variable fruits
     if { [catch {
        if { $number < 0 || $number >= [llength $fruits] } {
            error {out of range}
        lindex $fruits $number
     } fruit] } {
         return -code error "no fruit with number $number"
     } else {
         return $fruit
     }
 }

DKF adds: I reckon it is easier to just load up a pair of arrays to map in each direction, and then use them directly, which we can dress up like this...

 proc makeEnum {type identifiers} {
     upvar #0 ${type}_number a1 number_${type} a2
     set n 0
     foreach id $identifiers {
         incr n
         set a1($id) $n
         set a2($n) $id
     }
     proc ${type}_to_number $type "
         upvar #0 ${type}_number ary
         if {\[catch {set ary(\$$type)} num\]} {
             return -code error \"unknown $type \\\"\$$type\\\"\"
         }
         return \$num
     "
     proc number_to_${type} {number} "
         upvar #0 number_${type} ary
         if {\[catch {set ary(\$number)} $type\]} {
             return -code error \"no $type for \\\"\$number\\\"\"
         }
         return \$$type
     "
 }
 makeEnum fruit {apple blueberry cherry date elderberry}

KBK: I like the 'makeEnum' syntax, but [lindex] is faster than an array search, at least in Tcl 8.3.2. Also, was there a reason you switched from zero-based to one-based indexing? (DKF: no, it just came out that way. :^)

Consider the alternative implementation:

 proc makeEnumKBK {type identifiers} {
     upvar #0 ${type}_number a1
     set n 0
     foreach id $identifiers {
         set a1($id) $n
         lappend list $id
         incr n
     }
     proc ${type}_to_number $type "
         upvar #0 ${type}_number ary
         if {\[catch {set ary(\$$type)} num\]} {
             return -code error \"unknown $type \\\"\$$type\\\"\"
         }
         return \$num
     "
     proc number_to_${type} {number} "
         if { \[catch {
             if { \$number < 0 || \$number >= [llength $list] } {
                 error {out of range}
             }
             lindex [list $list] \$number
         } $type\] } {
             return -code error \"no $type for \\\"\$number\\\"\"
         }
         return \$$type
     "
 }
 makeEnumKBK froot {apple blueberry cherry date elderberry}

Running comparative timings on my machine shows that the array+list implementation is significantly faster, mostly because it avoids the cost of the [upvar]:

                                    Time (us; 550 MHz PIII)
 Action
                                  Two arrays     Array + list
 ------------------------------------------------------------
 Convert enum to number               23              23

 Try to convert nonexistent
     enum to number                   80              81

 Convert number to enum               23              13

 Try to convert out-of-range
     number to enum                   79              60

 Try to convert non-number to enum    80              65
 ------------------------------------------------------------