Your second Tcl/Tk application
Ok, you've written your first Tcl program. Maybe it did nothing but print "hello, world", or maybe you copied some of the examples from a book or from the Tcler's Wiki. Now it's time to write something real. But where to begin? You can continue to just type code into a console or create a file with the editor of your choice and just start typing, but a few minutes spent organizing your code will reap benefits as your program grows in complexity.
Create a proper starting point
The Tcl/Tk interpreter works top-down, executing lines of code as they are encountered. This has more than once prompted someone new to tcl programming to ask the following in comp.lang.tcl:
From: Tcl newbie Subject: how do I call a proc before it is defined? Newsgroups: comp.lang.tcl How do I call a proc before I define it? I don't want my main logic at the bottom of a file. puts "my_function: [my_proc 42]" ... proc my_proc {n} { return [expr {$n*2}] }
On the surface, it appears impossible to put your main program logic at the top of the file. After all, the Tcl interpreter cannot execute a procedure that has yet to be defined. The solution is simple, though perhaps not obvious if you are new to programming. The trick is to put your main logic in a proc at the top of your main file and call that proc as the last line of code in your script.
I do this even if I'm only going to write a few lines of code. My few lines of code always turn into more lines of code, and starting with a main procedure is easier than starting without one and adding it later (though it's not really that hard to do in either case...). Thus, the first thing I enter when creating a new Tcl/Tk program is this following snippet:
proc main {} { } main
That's all there is to it. You are now free to put your main logic in the main procedure and place all other procedures after main but before the last line in the file.
Define package dependencies up front
The next thing to do after creating the above boilerplate is to add in any package requirements [1]. For example, are you going to be using the sqlite [2] extension? Or perhaps tcllib [3] or tile [4]? Before you use these extensions you must load them into the interpreter. This is best done at the beginning of your program, but that's more a convenience than a hard and fast rule. Tcl, of course, allows you to load them at any point before you use them.
Adding the package declarations at the start of the file not only guarantees they will be immediately available for use, it serves as documentation for future programmers that may be looking at your code. Your program file might now look something like this:
package require Tk package require tile package require sqlite proc main {} { } main
Strictly speaking, "package require Tk" isn't necessary if you run your script with wish or a wish-like program. However, because Tk is a dynamically loadable extension it's possible to run your script with tclsh or a tclsh-like (eg. tclkit [5]) program and load Tk at runtime. When using wish it becomes a no-op, but explicitly requiring Tk serves as documentation for someone reading your code to instantly know the script is a Tk script.
Rules are made to be broken
Realize that the above is just a suggestion. Some packages have side effects which you may want to avoid under certain circumstances. For instance, "package require tk" will cause the default window "." to be created. It may be that you want to process command line arguments before the window is created. Or not. The point is, it's good to put package definitions up front but don't consider it a rule written in stone.Keep global variables to a minimum
One of the things I like about Tcl is that variables are local by default, which means the creation of global variables requires a conscious effort. Well, a conscious effort to use globals from within a proc, that is. If you ignore the advice in this article and write all your code at the global level, all your varables will be global. If you compartmentalize your code as described here, you will have to declare any global variables you wish to use.
While it's tempting to create global variables "on demand" as your code grows, I recommend another approach. Settle on just one or two global arrays rather than a proliferation of singletons. For example, I typically use no more than two or three arrays. One, named "config", is for command line options or variables which denote some sort of configuration value. The other I usually name "runtime" or "state", used to track runtime state. Sometimes I'll use a third, "data", to represent GUI data:
proc main {} { global state init parseArgs $::argv ... } proc init {} { global state array set state { filename "" saveNeeded 0 } array set config { -advanced 0 -debug 0 -fontsize 12 } } proc parseArgs {argv} { global config ... if {[lindex $argv 0] eq "-advanced"} { set config(-advanced) 1 } ... } proc createGui {} { global config data if {$config(-advanced)} { frame .advanced entry .advanced.options -textvariable data(options) ... } }
For more on using arrays with variables check out the article Dynamic Forms and Variables
Summary
Some programmers have in the past complained that Tcl doesn't scale well for large projects. Perhaps that is true for multimillion-line programs but for the vast majority of tcl projects under, say, one million lines of code, Tcl scales quite well. The trick is to spend a few moments at the start of your project organizing your code and your data structures. Instead of bemoaning the lack of a required main() in Tcl, or the lack of advanced data structures, work with what Tcl gives you and use a dash of organization to create programs that can scale well into the 10's or 100's of thousands of lines of code without too many headaches.
References
- package man page, http://www.tcl.tk/man/tcl8.4/TkCmd/font.htm
- sqlite http://www.sqlite.org
- tcllib http://tcllib.sourceforge.net
- tile http://tktable.sourceforge.net/tile
- tclkit http://www.equi4.com/tclkit.html
Further Reading
- Dynamic Forms and Variables, http://www.tclscripting.com/articles/jan06/article2.html