This Is How I Glyph – Tcl Namespaces (Part 3)

This is the third in a series of posts about the Tcl namespace command. Each subsequent post will cover more complex uses of namespaces. While this topic is strictly related to Tcl only, it could help you when developing more complex Glyph scripts.

The topics to be covered in this post depend upon a clear understanding of concepts outlined in both Tcl Namespaces (Part 1) and Tcl Namespaces (Part 2). If you haven’t already, then please review them before continuing.

Objects are Good

If you have done any Pointwise Glyph scripting, then you know that Glyph is an object oriented (oo) Tcl package that supports datatypes such as pw::Point. These datatypes follow the create/manipulate/delete paradigm.

Many instances (a.k.a. objects) of a datatype can be created. Each object has its own internal variables that can be manipulated independently from all other objects using the object’s subcommands. The code below demonstrates the oo create/manipulate/delete paradigm using the pw::Point datatype.

To create a point object, we call the pw::Point create command. The value returned is captured in the variable dbPt. As can be seen in Manipulate section of the code, $dbPt supports subcommands just like a namespace command ensemble. That is because $dbPt is a namespace command ensemble!

When the $dbPt object is no longer needed, a call to the $dbPt delete command removes the $dbPt object namespace and all of its internal data from the system.

[code toolbar=”false” wraplines=”false” collapse=”false” gutter=”true” lang=”python”]
package require PWI_Glyph 2.18.0

# Create
set dbPt [pw::Point create]
puts "dbPt’s object name == ‘$dbPt’"

# Manipulate
$dbPt setPoint {1.0 2.0 3.5}
puts "dbPt getXYZ == [$dbPt getXYZ]"
puts "dbPt isConstrained == [$dbPt isConstrained]"

# Delete
$dbPt delete

# $dbPt no longer exists – this will fail
catch {$dbPt isConstrained} msg
puts "dbPt isConstrained == $msg"

# Output
# dbPt’s object name == ‘::pw::Point_1’
# dbPt getXYZ == 1.0 2.0 3.5
# dbPt isConstrained == 0
# dbPt isConstrained == invalid command name "::pw::Point_1"
[/code]

RangeInt Revisited

Part 2 of this series introduced the RangeInt command ensemble. While RangeInt is functional, its usage tended to be verbose (RangeInt command name args). In addition, you can’t pass the named values into procs because the values are all hidden within RangeInt itself. As shown in the code below, only the value’s name can be passed to a proc and the proc would then need to call one of the RangeInt commands using that name.

[code toolbar=”false” wraplines=”false” collapse=”false” gutter=”true” lang=”python”]
# Using non-object oriented RangeInt from Part 2
source "RangeInt-ensemble.tcl"

proc calcProgress { cnt total name } {
RangeInt set $name [expr {int(100.0 * $cnt / $total)}]
}

RangeInt add percent 0 0 100
calcProgress 12 200 percent
puts "percent: 12 / 200 == [RangeInt get percent]%"

# Output
# percent: 12 / 200 == 6%
[/code]

If RangeInt was like pw::Point, the code would be more flexible. For instance, an object of any type could be passed to a proc as long the object supports the needed subcommands as demonstrated in the code below.

[code toolbar=”false” wraplines=”false” collapse=”false” gutter=”true” lang=”python”]
source "RangeInt-oo.tcl"

proc calcProgressObj { cnt total obj } {
$obj set [expr {int(100.0 * $cnt / $total)}]
}

set percent [RangeInt create 0 0 100]
calcProgressObj 12 200 $percent
puts "percent: 12 / 200 == [$percent get]%"
$percent delete

# This works if $other supports "set N" and "get"
set other [OtherType create]
calcProgressObj 20 400 $other
puts " other: 20 / 400 == [$other get]%"
$other delete

# Output
# percent: 12 / 200 == 6%
# other: 20 / 400 == 5%
[/code]

Let’s explore using namespaces to make RangeInt object oriented just like pw::Point.

RangeInt create

The first step on the road to an object oriented implementation of RangeInt is the need for a RangeInt create subcommand. This command will do the work needed to establish the new object’s command ensemble, initialize its variables, and then return the namespace name to the caller. The create proc is shown below.

[code toolbar=”false” wraplines=”false” collapse=”false” gutter=”true” lang=”python”]
namespace eval RangeInt {
variable cnt_ 0 ;# used to create unique object names

namespace export create
proc create { {val 0} {min inf} {max inf} } {
set ns [namespace current]
# Build unique object name == "${ns}::_N"
set objName "${ns}::_[incr ${ns}::cnt_]"
# Create the object command ensemble
namespace eval $objName [set ${ns}::proto_]
# Init the object – error if $val is out of range
${objName}::ctor $val $min $max
return $objName
}
namespace ensemble create ;# create the RangeInt ensemble

#———————————————————-
# RangeInt object ensembles support these subcommands
variable proto_ {

} ;# end proto_
}
[/code]

The create proc first constructs a unique name (objName) by concatenating the namespace name stored in $ns with the incremented namespace variable cnt_. This produces names of the form ::RangeInt::_N.

Next, the object’s ensemble is created by the call:

  • namespace eval $objName [set ${ns}::proto_]

After variable substitution, the command evaluated above would be similar to:

  • namespace eval ::RangeInt::_1 $::RangeInt::proto_

The variable $::RangeInt::proto_ contains commands that define the new object’s namespace ensemble. We will take a closer look at this variable later. This is similar to how we created the RangeInt ensemble in Part 2. Except here, the ensemble name is different for each object created.

Finally, the new object ensemble is initialized (constructed) with a direct call the the namespace proc ${objName}::ctor and the name of object’s namespace ensemble, $objName, is returned to the caller.

RangeInt::proto_

The RangeInt ensemble has been expanded below to include the value of the RangeInt::proto_ variable. Notice that it looks a lot like a namespace ensemble. Actually, if you were to replace variable proto_ below with namespace eval proto_ you would have a valid namespace ensemble named proto_. However, we don’t want one ensemble. We want many, identical ensembles that all support the same procs and variables.

This is how RangeInt create works. Every time RangeInt create is called, it uses the ensemble definition stored in RangeInt::proto_ to create a uniquely named, yet functionally identical ensemble. The ensemble name returned by RangeInt create is the object’s name and it provides access to the ensemble commands.

RangeInt::proto_ defines the three variables min_, max_, and val_. It also exports the three procs set, get, and delete. It also defines the unexported, helper procs ctor and check. As mentioned above, ctor is called by RangeInt create to initialize each new object. While check is used by the $object set subcommand to validate incoming values.

[code toolbar=”false” wraplines=”false” collapse=”false” gutter=”true” lang=”python”]
namespace eval RangeInt {
variable cnt_ 0 ;# used to create unique object names

namespace export create
proc create { {val 0} {min inf} {max inf} } {
set ns [namespace current]
# Build unique object name == "${ns}::_N"
set objName "${ns}::_[incr ${ns}::cnt_]"
# Create the object command ensemble
namespace eval $objName [set ${ns}::proto_]
# Init the object – error if $val is out of range
${objName}::ctor $val $min $max
return $objName
}
namespace ensemble create ;# create the RangeInt ensemble

#———————————————————-
# RangeInt object ensembles support these subcommands
variable proto_ {
variable min_ inf ;# object’s range min
variable max_ inf ;# object’s range max
variable val_ 0 ;# object’s current value

# constructor – NOT EXPORTED
proc ctor { val min max } {
variable min_ $min
variable max_ $max
[namespace current]::set $val ;# error if out of range
}

# helper – NOT EXPORTED
proc check { val op lim } {
return [expr [list "$lim" == "inf" || $val $op $lim]]
}

namespace export set
proc set { val } {
variable min_
variable max_
if { [check $val >= $min_] && [check $val <= $max_] } {
variable val_ $val
return $val_
}
return -code error "Value $val not in ($min_, $max_)"
}

namespace export get
proc get {} {
variable val_
return $val_
}

namespace export delete
proc delete {} {
namespace delete [namespace current]
}

namespace ensemble create ;# create the object ensemble
} ;# end proto_
}
[/code]

Using RangeInt

Some usage examples are given below to demonstrate the new oo functionality available in the enhanced RangeInt.

[code toolbar=”false” wraplines=”false” collapse=”false” gutter=”true” lang=”python”]
# Usage
set vA [RangeInt create 22 -5 25] ;# range -5 … 25
puts "vA’s object name is ‘$vA’"
puts "vA == [$vA get]"
puts "vA == [$vA set -3]"
catch {$vA set 99} msg
puts "vA == $msg"
$vA delete
catch {$vA set 0} msg
puts "vA == $msg"
puts {}
set vB [RangeInt create 1 0 5] ;# range 0 … 5
puts "vB’s object name is ‘$vB’"
puts "vB == [$vB get]"
puts "vB == [$vB set 5]"
catch {$vB set -1} msg
puts "vB == $msg"
$vB delete
catch {$vB set 0} msg
puts "vB == $msg"

# Output
# vA’s object name is ‘::RangeInt::_3’
# vA == 22
# vA == -3
# vA == Value 99 not in (-5, 25)
# vA == invalid command name "::RangeInt::_3"
#
# vB’s object name is ‘::RangeInt::_4’
# vB == 1
# vB == 5
# vB == Value -1 not in (0, 5)
# vB == invalid command name "::RangeInt::_4"
[/code]

Until Next Time

This post covered the creation of object oriented namespace command ensembles. I will be covering additional namespace wizardry in future posts. If you want to learn more about namespaces, then take a look at the full Tcl namespace documentation.

About David Garlisch

Illini by birth, Texan by choice.
This entry was posted in Applications, Software and tagged , , . Bookmark the permalink.

1 Response to This Is How I Glyph – Tcl Namespaces (Part 3)

  1. Pingback: This Is How I Glyph – Tcl Namespaces (Part 2) | Another Fine Mesh

Leave a Reply