Subject: tcllib: experiences and suggestions for improvement - DN [1]
Mark Harrison <markh@usai.asiainfo.com> - 17 Apr 2000 - comp.lang.tcl
As promised, here is my followup message regarding my
experience with the preliminary release of tcllib:
Good things
-----------
Even in its nascent state, it includes a lot of useful
functions. Having a standard library is good, etc...
Bad things
----------
1. Varying interfaces
As has been noted over the past 7-8 years, there is not
a standard Tcl component model. Therefore, everybody that
writes a Tcl package has to create his own. Different
parts of tcllib follow different models of how components
are put together. Some do the FILE-like thing:
set id [create-something]
do-something $id
Some do the widget-like thing:
something foo
foo do-something
Some do subtle variations of the above:
set foo [something]
$foo do-something
Some (like ::struct::tree) combine the two:
tree t
set foo [t insert root]
t get $foo
This applies to many other popular packages as well
as to tcllib. One of the first things a Tcl package
author has to do is create his own packaging scheme.
2. It's ugly to create a package
The ::struct components make an attempt to encapsulate
their structure into abstract data types. From the
user level, it mostly succeeds (but see below for more
comments):
% stack foo
foo
% foo push 1 2 3
% foo size
3
But from the implementation viewpoint, the code is just
plain awful. I spend a lot of time explaining Tcl
programming to people, and is just plain difficult
to explain how to implement an ADT using this method.
It also confirms everyone's worst accusations regarding
Tcl as being a hacky (bad sense) and one-off language.
3. Performance suffers
Some ADTs, such as trees, need to have data associated
with them. The current method is to have "get" and
"set" methods for each of the data types:
tree t
t set root -key name "Mark"
puts [t get root -key name]
Unfortunately, this kills a lot of the speed improvement
introduced to Tcl over the past several revisions. Some
simple timing tests I've done, consisting of appending
elements to a list:
N plain incr tcllib
1000 15552 15645 835529
2000 28475 30526 1936207
3000 43242 45564 3013813
4000 54734 60202 5957176
5000 356977 85017 8348434
6000 89041 100088 10148768
plain: a simple for loop, "lappend mylist $i" (control)
incr: itcl class, "lappend mylist $i", mylist is a class var.
tcllib: tree, using "get, lappend, set", as above
A component model is needed
---------------------------
Having a standard tcl-level component model solves a lot
of these problems. This should be no news to anybody...
We've rehashed the megawidget issue repeatedly, and this
is just the language-level (non-gui) part of that discussion.
Let's look at an example... I picked the smallest tcllib
struct, ::struct::stack and converted it to an itcl class.
This was a straightforward process and took about 20 minutes.
I replaced the crufty initialization code with an itcl class,
moved the data element to the class, and used the itcl::body
command to incorporate the actual "real" code into the class.
The code shrunk by about 25%, from 114 NCSL to either 81 or 74 NCSL,
depending if the class methods were coded inline or after the class
definition. More important than the actual number of lines is the
content of the lines that went away. Let us look at the
initialization code, and one of the small function bodies.
I will explain the new version... I leave it to somebody with
more patience and/or fortitude to explain the current version.
Notice how you have to have (and explain) arrays of stacks,
stack counters, and lines like:
interp alias {} ::$name {} ::struct::stack::StackProc $name
new:
---
# create a class ::struct::stack which has the usual stack
# operations. Each instance of stack has a list "mystack",
# which is a list of the data which has been pushed onto
# the stack. so after "push 1 2 3", mystack = {1 2 3}.
namespace eval ::struct
class stack {
variable mystack
method clear {} {}
method peek {{count 1}} {}
method pop {{count 1}} {}
method push {arg1 args} {}
method rotate {count steps} {}
method size {} {}
constructor {} {}
}
body stack::clear {} {
set mystack [list ]
return
}
body stack::peek {{count 1}} {
#...
}
#etc...
}
old:
namespace eval ::struct::stack {
# The stacks array holds all of the stacks you've made
variable stacks
# counter is used to give a unique name for unnamed stacks
variable counter 0
# commands is the list of subcommands recognized by the stack
variable commands [list \
"clear" \
"destroy" \
"peek" \
"pop" \
"push" \
"rotate" \
"size" \
]
# Only export one command, the one used to instantiate a new stack
namespace export stack
}
# ::struct::stack::stack --
#
# Create a new stack with a given name; if no name is given, use
# stackX, where X is a number.
#
# Arguments:
# name name of the stack; if null, generate one.
#
# Results:
# name name of the stack created
proc ::struct::stack::stack {{name ""}} {
variable stacks
variable counter
if { [llength [info level 0]] == 1 } {
incr counter
set name "stack${counter}"
}
if { ![string equal [info commands ::$name] ""] } {
error "command \"$name\" already exists, unable to create stack"
}
set stacks($name) [list ]
# Create the command to manipulate the stack
interp alias {} ::$name {} ::struct::stack::StackProc $name
return $name
}
# ::struct::stack::StackProc --
#
# Command that processes all stack object commands.
#
# Arguments:
# name name of the stack object to manipulate.
# args command name and args for the command
#
# Results:
# Varies based on command to perform
proc ::struct::stack::StackProc {name {cmd ""} args} {
# Do minimal args checks here
if { [llength [info level 0]] == 2 } {
error "wrong # args: should be \"$name option ?arg arg ...?\""
}
# Split the args into command and args components
if { [llength [info commands ::struct::stack::_$cmd]] == 0 } {
variable commands
set optlist [join $commands ", "]
set optlist [linsert $optlist "end-1" "or"]
error "bad option \"$cmd\": must be $optlist"
}
eval [list ::struct::stack::_$cmd $name] $args
}
proc ::struct::stack::_clear {name} {
set ::struct::stack::stacks($name) [list ]
return
}
Why itcl?
---------
Let's not beat this dead horse any farther than necessary,
but here are the reasons I think itcl is a good candidate
for a standard component system.
- it's the most popular
- it works well
- the poll on dev.scriptics.com shows good itcl numbers
- etc, etc, blah blah
(and review the old conference proceedings... when people
say "big-name company X uses Tcl on big-name project Y",
deep in the fine print you usually see they are using itcl.)
I'm not keen to rehash the hot discussion topic of the
1990's (objects), but let us at least agree that hot discussion
topic of the 1980's (abstract data types) has been resolved.
We can have discuss the usual fine points
- the CLOS model is better than the C++ model
- but objects aren't REALLY necessary
- objects are so oversold
- itcl is slow
- blah blah
but let us try to move forward in some meaninful way on
this issue.
Mark.
PS, you can reference all the above code at
http://www.markharrison.net/tcllib
PPS, sorry this got to be so long... but I think these
are some important issues for the longterm viability
of Tcl.
--
Mark Harrison markh@usai.asiainfo.com
AsiaInfo Computer Networks http://www.markharrison.net
Beijing / Santa Clara http://usai.asiainfo.com:8080
Last modified
2000-07-20
2000-07-20
(195.108.246.52)
Note: you are looking at
the snapshot of an old wiki
- much of this information
is likely to be very outdated
