snitfaq(3tcl)

πŸ’– NAME πŸ’–

hii bestie, welcome to snitfaq(3tcl) β€” the manual page for the Snit's Not Incr Tcl, OO system (yes that's the real expansion, we'll get to it). This particular page is the FAQ.

snitfaq - Snit Frequently Asked Questions

✨ DESCRIPTION ✨

πŸ¦‹ OVERVIEW πŸ¦‹

πŸŽ€ WHAT IS THIS DOCUMENT? πŸŽ€

okay so first thing, and this is important: this is an atypical FAQ list. Like, few of these questions are actually frequently asked 😭. They're the questions the author thinks a newcomer to Snit should be asking. Which is honestly a serve, but it's not what you'd expect from the genre.

And second thing β€” this file is not a complete reference to Snit. It's not that girl and it's not trying to be. The full deep-dive reference info lives over in the snit man page. Go there for the complete picture. πŸ’…

πŸ’… WHAT IS SNIT? πŸ’…

Snit is a framework for defining abstract data types and megawidgets in pure Tcl. The name "Snit" stands for "Snit's Not Incr Tcl", and that name is doing a lot of work β€” it's signifying that Snit takes a totally different approach to defining objects than Incr Tcl does, Incr Tcl being the best-known object framework for Tcl. And ngl the author says that if he'd realized Snit would become at all popular, he'd probably have chosen something else πŸ’€. iconic self-awareness fr.

The primary purpose of Snit is to be object glue β€” to help you compose diverse objects from diverse sources into types and megawidgets with clean, convenient interfaces, so that you can more easily build your application. glue. bestie. it's giving craft supplies.

Snit isn't about theoretical purity or minimalist design. It's about being able to do powerful things easily and consistently without having to think about them β€” so that you can concentrate on building your application. 🫢

Snit also isn't about implementing thousands of nearly identical, carefully-specified lightweight thingamajigs β€” not as individual Snit objects, anyway. For that, traditional Tcl methods will be much faster, and not much more complicated. But here's the plot twist: Snit is about implementing a clean interface to manage a collection of thousands of nearly identical carefully-specified lightweight thingamajigs. (e.g., think of the text widget and text tags, or the canvas widget and canvas objects 🌸). Snit lets you hide the details of just how those thingamajigs are stored β€” so that you can ignore it, and concentrate on building your application.

And finally, for the record: Snit isn't a way of life, a silver bullet, or the Fountain of Youth ✨. It's just a way of managing complexity β€” and of managing some of the complexity of managing complexity β€” so that you can concentrate on building your application. no cap.

πŸ’œ WHAT VERSION OF TCL DOES SNIT REQUIRE? πŸ’œ

here's the tea, version by version:

  • Snit 1.3 requires Tcl 8.3 or later.
  • Snit 2.2 requires Tcl 8.5 or later.

And if you wanna know the differences between Snit 1.3 and Snit 2.2, go check out SNIT VERSIONS. πŸ¦‹

πŸ“ WHERE CAN I DOWNLOAD SNIT? πŸ“

plot twist: you might already have it πŸ‘‘. Snit is part of Tcllib, the standard Tcl library. It's also available at the Snit Home Page, http://www.wjduquette.com/snit.

⭐ WHAT ARE SNIT'S GOALS? ⭐

the vision board, bestie:

  • A Snit object should be at least as efficient as a hand-coded Tcl object (see http://www.wjduquette.com/tcl/objects.html).
  • The fact that Snit was used in an object's implementation should be transparent (and irrelevant) to clients of that object.
  • Snit should be able to encapsulate objects from other sources, particularly Tk widgets.
  • Snit megawidgets should be (to the extent possible) indistinguishable in interface from Tk widgets.
  • Snit should be Tclish β€” that is, rather than trying to emulate C++, Smalltalk, or anything else, it should try to emulate Tcl itself.
  • It should have a simple, easy-to-use, easy-to-remember syntax.

🌈 HOW IS SNIT DIFFERENT FROM OTHER OO FRAMEWORKS? 🌈

okay this is the part where Snit really does its own thing. Snit is unique among Tcl object systems in that it's based not on inheritance but on delegation. πŸ‘‘

Object systems based on inheritance only allow you to inherit from classes defined using the same system, and honestly? that's a shame. In Tcl, an object is anything that acts like an object; it shouldn't matter how the object was implemented. The author designed Snit to help build applications out of the materials at hand β€” so Snit is designed to be able to incorporate and build on any object, whether it's a hand-coded object, a Tk widget, an Incr Tcl object, a BWidget, or almost anything else. 🧚

And btw, note that you can achieve the effect of inheritance using COMPONENTS and DELEGATION β€” and you can inherit from anything that looks like a Tcl object. periodt.

πŸ‘‘ WHAT CAN I DO WITH SNIT? πŸ‘‘

Using Snit, a programmer can:

  • Create abstract data types and Tk megawidgets.
  • Define instance variables, type variables, and Tk-style options.
  • Define constructors, destructors, instance methods, type methods, procs.
  • Assemble a type out of component types. Instance methods and options can be delegated to the component types automatically.

🌈 SNIT VERSIONS 🌈

ok so here's the tea about versions, bestie, bc this trips people up literally every single time πŸ’…

πŸ’– WHICH VERSION OF SNIT SHOULD I USE? πŸ’–

So the current Snit distribution ships with two versions β€” Snit 1.3 and Snit 2.2 β€” and there's a whole reason both are in there, it's not just vibes.

Snit 2.2 takes advantage of a bunch of new features in Tcl 8.5 to make run-time efficiency way better, and as a lil side-effect, the ugliness of Snit's error messages and stack traces has been reduced considerably (glow up fr fr ✨). The cost of using Snit 2.2, of course, is that you must target Tcl 8.5. No 8.5, no 2.2, that's the deal 🫢

Snit 1.3, on the other hand, lacks Snit 2.2's optimizations, but it only requires Tcl 8.3 and later. Low maintenance queen πŸ‘‘

In short:

  • If you're targetting Tcl 8.3 or 8.4, you should use Snit 1.3.
  • If you can afford to target Tcl 8.5, you should definitely use Snit 2.2.
  • If you will be targetting both, you can use Snit 1.3 exclusively, or β€” if your code is unaffected by the minor incompatibilities between the two versions β€” you can use Snit 1.3 for Tcl 8.4 and Snit 2.2 for Tcl 8.5. Iconic dual-wield energy πŸŽ€

πŸ¦‹ HOW DO I SELECT THE VERSION OF SNIT I WANT TO USE? πŸ¦‹

To always use Snit 1.3 (or a later version of Snit 1.x), invoke Snit like this:

package require snit 1.3

To always use Snit 2.2 (or a later version of Snit 2.x), say this instead:

package require snit 2.2
Heads up bestie: if you request Snit 2.2 explicitly, your application will halt with Tcl 8.4, since Snit 2.2 is unavailable for Tcl 8.4. Not a vibe. Not a slay. Just a crash πŸ’€

If you wish your application to always use the latest available version of Snit, don't specify a version number at all:

package require snit

Tcl will find and load the latest version that's available relative to the version of Tcl being used. In this case, be careful to avoid using any incompatible features β€” the universe picks for you, so you gotta stay in your lane 😌


🌸 HOW ARE SNIT 1.3 AND SNIT 2.2 INCOMPATIBLE? 🌸

To the extent possible, Snit 2.2 is intended to be a drop-in replacement for Snit 1.3. Unfortunately, some incompatibilities were inevitable because Snit 2.2 uses Tcl 8.5's new namespace ensemble mechanism to implement subcommand dispatch. This approach is much faster than the mechanism used in Snit 1.3, and also results in much better error messages; however, it also places new constraints on the implementation.

There are four specific incompatibilities between Snit 1.3 and Snit 2.2. Buckle up, we're going through all of them πŸ’…

  • Incompatibility #1 β€” Snit 1.3 supports implicit naming of objects. Suppose you define a new snit::type called dog. You can create instances of dog in three ways:
dog spot               ;# Explicit naming
set obj1 [dog %AUTO%]  ;# Automatic naming
set obj2 [dog]         ;# Implicit naming

In Snit 2.2, type commands are defined using the namespace ensemble mechanism; and namespace ensemble doesn't allow an ensemble command to be called without a subcommand. In short, using namespace ensemble there's no way to support implicit naming.

All is not lost, however. If the type has no type methods, then the type command is a simple command rather than an ensemble, and namespace ensemble is not used. In this case, implicit naming is still possible.

In short, you can have implicit naming if you're willing to do without type methods (including the standard type methods, like $type info). To do so, use the -hastypemethods pragma:

pragma -hastypemethods 0
  • Incompatibility #2 β€” Hierarchical methods and type methods are implemented differently in Snit 2.2.

A hierarchical method is an instance method which has subcommands; these subcommands are themselves methods. The Tk text widget's tag command and its subcommands are examples of hierarchical methods. You can implement such subcommands in Snit simply by including multiple words in the method names:

method {tag configure} {tag args} { ... }

method {tag cget} {tag option} {...}

Here we've implicitly defined a tag method which has two subcommands, configure and cget.

In Snit 1.3, hierarchical methods could be called in two ways:

$obj tag cget -myoption      ;# The good way
$obj {tag cget} -myoption    ;# The weird way

In the second call, we see that a hierarchical method or type method is simply one whose name contains multiple words.

In Snit 2.2 this is no longer the case, and the "weird" way of calling hierarchical methods and type methods no longer works. RIP to the weird way, you will not be missed πŸ™ˆ

  • Incompatibility #3 β€” The third incompatibility derives from the second. In Snit 1.3, hierarchical methods were also simply methods whose name contains multiple words. As a result, $obj info methods returned the full names of all hierarchical methods. In the example above, the list returned by $obj info methods would include tag configure and tag cget but not tag, since tag is defined only implicitly.

In Snit 2.2, hierarchical methods and type methods are no longer simply ones whose name contains multiple words; in the above example, the list returned by $obj info methods would include tag but not tag configure or tag cget. Flip-flopped, and that's just how it is 😭

  • Incompatibility #4 β€” The fourth incompatibility is due to a new feature. Snit 2.2 uses the new namespace path command so that a type's code can call any command defined in the type's parent namespace without qualification or importation. For example, suppose you have a package called mypackage which defines a number of commands including a type, ::mypackage::mytype. Thanks to namespace path, the type's code can call any of the other commands defined in ::mypackage::.

This is extremely convenient. However, it also means that commands defined in the parent namespace, ::mypackage::, can block the type's access to identically named commands in the global namespace. This can lead to bugs. For example, Tcllib includes a type called ::tie::std::file. This type's code calls the standard file command. When run with Snit 2.2, the code broke β€” the type's command, ::tie::std::file, is itself a command in the type's parent namespace, and so instead of calling the standard file command, the type found itself calling itself. Not me becoming my own enemy 😭😭 that's a canon event fr


✨ ARE THERE OTHER DIFFERENCES BETWEEN SNIT 1.X AND SNIT 2.2? ✨

Yes. Periodt.

  • Method dispatch is considerably faster. ⚑
  • Many error messages and stack traces are cleaner. πŸ’…
  • The -simpledispatch pragma is obsolete, and ignored if present. In Snit 1.x, -simpledispatch substitutes a faster mechanism for method dispatch, at the cost of losing certain features. Snit 2.2 method dispatch is faster still in all cases, so -simpledispatch is no longer needed. She's retired, let her rest πŸŽ€
  • In Snit 2.2, a type's code (methods, type methods, etc.) can call commands from the type's parent namespace without qualifying or importing them, i.e., type ::parentns::mytype's code can call ::parentns::someproc as just someproc.

This is extremely useful when a type is defined as part of a larger package, and shares a parent namespace with the rest of the package; it means that the type can call other commands defined by the package without any extra work. Main character convenience, honestly πŸ‘‘

This feature depends on the new Tcl 8.5 namespace path command, which is why it hasn't been implemented for V1.x. V1.x code can achieve something similar by placing

namespace import [namespace parent]::*

in a type constructor. This is less useful, however, as it picks up only those commands which have already been exported by the parent namespace at the time the type is defined. So like, it's giving the idea of the thing, not the thing itself πŸ¦‹

πŸŽ€ OBJECTS πŸŽ€

WHAT IS AN OBJECT?

Okay bestie, a full deep-dive on object-oriented programming is literally beyond the scope of this FAQ, obviously. But in simple terms? An object is an instance of an abstract data type β€” a coherent bundle of code and data. There are sooo many ways to represent objects in Tcl/Tk, and the most famous ones are the Tk widgets.

A Tk widget is an object; it's represented by a Tcl command. The object's methods are subcommands of that Tcl command. The object's properties are options accessed using the configure and cget methods. And Snit? Snit uses the exact same conventions as Tk widgets do. Periodt. πŸ’…


WHAT IS AN ABSTRACT DATA TYPE?

In computer science terms, an abstract data type is a complex data structure along with a set of operations β€” a stack, a queue, a binary tree, etc β€” which is to say, in modern terms, an object. In systems that include some form of inheritance, the word class is usually used instead of abstract data type, but since Snit doesn't implement inheritance as it's ordinarily understood, the older term seems more appropriate. Sometimes this is called object-based programming as opposed to object-oriented programming. And psst β€” you can easily create the effect of inheritance using COMPONENTS and DELEGATION. iykyk. ✨

In Snit, as in Tk, a type is a command that creates instances β€” objects β€” which belong to the type. Most types define some number of options which can be set at creation time, and usually can be changed later.

Further, an instance is also a Tcl command β€” a command that gives access to the operations which are defined for that abstract data type. Conventionally, the operations are defined as subcommands of the instance command. For example, to insert text into a Tk text widget, you use the text widget's insert subcommand:

# Create a text widget and insert some text in it.
text .mytext -width 80 -height 24
.mytext insert end "Howdy!"

In this example, text is the type command and .mytext is the instance command.

In Snit, object subcommands are generally called INSTANCE METHODS. 🌸


WHAT KINDS OF ABSTRACT DATA TYPES DOES SNIT PROVIDE?

Snit lets you define three kinds of abstract data type, bestie:

  • snit::type
  • snit::widget
  • snit::widgetadaptor

WHAT IS A SNIT::TYPE?

A snit::type is a non-GUI abstract data type, e.g., a stack or a queue. snit::types are defined using the snit::type command. So like, if you were designing a kennel management system for a dog breeder, you'd need a dog type. Obviously. 🐢

% snit::type dog {
    # ...
}
::dog
%

This definition defines a new command (::dog, in this case) that can be used to define dog objects.

An instance of a snit::type can have INSTANCE METHODS, INSTANCE VARIABLES, OPTIONS, and COMPONENTS. The type itself can have TYPE METHODS, TYPE VARIABLES, TYPE COMPONENTS, and PROCS.


WHAT IS A SNIT::WIDGET?, THE SHORT STORY

A snit::widget is a Tk megawidget built using Snit; it is very similar to a snit::type. See WIDGETS. πŸ¦‹


WHAT IS A SNIT::WIDGETADAPTOR?, THE SHORT STORY

A snit::widgetadaptor uses Snit to wrap an existing widget type (e.g., a Tk label), modifying its interface to a lesser or greater extent. It is very similar to a snit::widget. See WIDGET ADAPTORS. 🫢


HOW DO I CREATE AN INSTANCE OF A SNIT::TYPE?

You create an instance of a snit::type by passing the new instance's name to the type's create method. In the following example, we create a dog object called spot.

% snit::type dog {
    # ....
}
::dog
% dog create spot
::spot
%

In general, the create method name can be omitted so long as the instance name doesn't conflict with any defined TYPE METHODS. (See TYPE COMPONENTS for the special case in which this doesn't work.) So the following example is identical to the previous example:

% snit::type dog {
    # ....
}
::dog
% dog spot
::spot
%

This document generally uses the shorter form.

If the dog type defines OPTIONS, these can usually be given defaults at creation time:

% snit::type dog {
    option -breed mongrel
    option -color brown

    method bark {} { return "$self barks." }
}
::dog
% dog create spot -breed dalmation -color spotted
::spot
% spot cget -breed
dalmation
% spot cget -color
spotted
%

Once created, the instance name now names a new Tcl command that is used to manipulate the object. For example, the following code makes the dog bark:

% spot bark
::spot barks.
%

HOW DO I REFER TO AN OBJECT INDIRECTLY?

Some programmers prefer to save the object name in a variable, and reference it that way. For example,

% snit::type dog { ... }
::dog
% set d [dog spot -breed dalmation -color spotted]
::spot
% $d cget -breed
dalmation
% $d bark
::spot barks.
%

If you prefer this style, you might prefer to have Snit generate the instance's name automatically. πŸ‘€


HOW CAN I GENERATE THE OBJECT NAME AUTOMATICALLY?

If you'd like Snit to generate an object name for you, use the %AUTO% keyword as the requested name:

% snit::type dog { ... }
::dog
% set d [dog %AUTO%]
::dog2
% $d bark
::dog2 barks.
%

The %AUTO% keyword can be embedded in a longer string:

% set d [dog obj_%AUTO%]
::obj_dog4
% $d bark
::obj_dog4 barks.
%

CAN TYPES BE RENAMED?

Tcl's rename command renames other commands. It's a common technique in Tcl to modify an existing command by renaming it and defining a new command with the original name; the new command usually calls the renamed command.

snit::type commands, however, should NEVER be renamed β€” to do so breaks the connection between the type and its objects. Like, do not do this. 😭

CAN OBJECTS BE RENAMED?

Tcl's rename command renames other commands. It's a common technique in Tcl to modify an existing command by renaming it and defining a new command with the original name; the new command usually calls the renamed command.

All Snit objects (including widgets and widgetadaptors) can be renamed, though this flexibility has some consequences:

  • In an instance method, the implicit argument self will always contain the object's current name, so instance methods can always call other instance methods using $self.
  • If the object is renamed, however, then $self's value will change. Therefore, don't use $self for anything that will break if $self changes. For example, don't pass a callback command to another object like this:
.btn configure -command [list $self ButtonPress]

You'll get an error if .btn calls your command after your object is renamed.

  • Instead, your object should define its callback command like this:
.btn configure -command [mymethod ButtonPress]

The mymethod command returns code that will call the desired method safely; the caller of the callback can add additional arguments to the end of the command as usual.

  • Every object has a private namespace; the name of this namespace is available in method bodies, etc., as the value of the implicit argument selfns. This value is constant for the life of the object. Use $selfns instead of $self if you need a unique token to identify the object.
  • When a snit::widget's instance command is renamed, its Tk window name remains the same β€” and is still extremely important. Consequently, the Tk window name is available in method bodies as the value of the implicit argument win. This value is constant for the life of the object. When creating child windows, it's best to use $win.child rather than $self.child as the name of the child window.

HOW DO I DESTROY A SNIT OBJECT?

Any Snit object of any type can be destroyed by renaming it to the empty string using the Tcl rename command.

Snit megawidgets (i.e., instances of snit::widget and snit::widgetadaptor) can be destroyed like any other widget: by using the Tk destroy command on the widget or on one of its ancestors in the window hierarchy.

Every instance of a snit::type has a destroy method:

% snit::type dog { ... }
::dog
% dog spot
::spot
% spot bark
::spot barks.
% spot destroy
% spot barks
invalid command name "spot"
%

Finally, every Snit type has a type method called destroy; calling it destroys the type and all of its instances:

% snit::type dog { ... }
::dog
% dog spot
::spot
% spot bark
::spot barks.
% dog destroy
% spot bark
invalid command name "spot"
% dog fido
invalid command name "dog"
%

that's the whole objects era, bestie β€” moving on to the next part 🧚

πŸ’… INSTANCE METHODS πŸ’…

✨ WHAT IS AN INSTANCE METHOD? ✨

Okay so bestie, an instance method is a proc that's married to one specific object and you call it as a subcommand of that object's command. That's it, that's the whole vibe. And the glow-up? It gets free access to literally everything β€” all the object's type variables, instance variables, all of it. No keys needed, it lives there. 🏠


✨ HOW DO I DEFINE AN INSTANCE METHOD? ✨

You define them right inside the type definition with the method statement. So say we're building a computer simulation and we want to add some dogs to it 🐢:

% snit::type dog {
    method bark {} {
        return "$self barks."
    }

    method chase {thing} {
        return "$self chases $thing."
    }
}
::dog
%

Periodt. A dog can bark, and a dog can chase things. That's the whole skill tree.

The method statement is giving normal Tcl proc, honestly β€” same shape β€” except it lives inside a snit::type definition. And notice the little ✨magic✨: every single instance method gets an implicit argument called self, and that argument holds the object's name. (There's more tea on implicit method arguments below, keep scrolling.)


✨ HOW DOES A CLIENT CALL AN INSTANCE METHOD? ✨

The method name just becomes a subcommand of the object. Easy. Let's put a simulated dog through its paces:

% dog spot
::spot
% spot bark
::spot barks.
% spot chase cat
::spot chases cat.
%

Look at spot go 😭 she's thriving.


✨ HOW DOES AN INSTANCE METHOD CALL ANOTHER INSTANCE METHOD? ✨

If method A wants to call method B on the same object, it does it exactly like a client would: calls method B as a subcommand of the object itself, using the object name sitting in the implicit self argument. Same energy, no special back door. πŸšͺ

Say, for example, our dogs absolutely refuse to chase anything without barking at it first (rude, but consistent):

% snit::type dog {
    method bark {} {
        return "$self barks."
    }

    method chase {thing} {
        return "$self chases $thing.  [$self bark]"
    }
}
::dog
% dog spot
::spot
% spot bark
::spot barks.
% spot chase cat
::spot chases cat.  ::spot barks.
%

See how chase reached back and called [$self bark]? Chef's kiss. πŸ‘¨β€πŸ³πŸ’‹


✨ ARE THERE ANY LIMITATIONS ON INSTANCE METHOD NAMES? ✨

Not really, bestie β€” just avoid the standard instance method names: configure, configurelist, cget, destroy, and info. Those are taken, they're spoken for, do not fight them. βš”οΈ

Also FYI: method names made of multiple words define hierarchical methods. Which brings us to…


✨ WHAT IS A HIERARCHICAL METHOD? ✨

So an object's methods are subcommands of the object's instance command, right? Hierarchical methods let an object's methods have their own subcommands β€” and those can have subcommands, and on and on, forever, like a lil command family tree 🌳. This lets you build a tree-shaped command structure, which is exactly the kind of thing a ton of Tk widgets do β€” the subcommands of the Tk text widget's tag method are hierarchical methods. You've seen them. You just didn't know they had a name. πŸ‘‘


✨ HOW DO I DEFINE A HIERARCHICAL METHOD? ✨

You define methods whose names are made of multiple words. Those words define the hierarchy implicitly β€” Snit just gets it. So this code defines a tag method with the subcommands cget and configure:

snit::widget mytext {
    method {tag configure} {tag args} { ... }

    method {tag cget} {tag option} {...}
}

Important lil detail: there's no explicit definition for the tag method itself. It's implicit, it emerges from defining tag configure and tag cget. So if you tried to also define tag explicitly in this example? Error. Snit is not playing that game. πŸ’€


✨ HOW DO I CALL HIERARCHICAL METHODS? ✨

Subcommands of subcommands, obviously. Down the chain we go:

% mytext .text
.text
% .text tag configure redtext -foreground red -background black
% .text tag cget redtext -foreground
red
%

It's giving nested. It's giving layers. It's giving onion. πŸ§…


✨ HOW DO I MAKE AN INSTANCE METHOD PRIVATE? ✨

Super useful move: private methods β€” instance methods that are only meant to be called by other methods of the same object. The inner circle only. πŸ«‚

Now here's the thing, and I need you to hear me: Snit doesn't implement any access control on instance methods. Zero. None. Every method is de facto public, whether you like it or not. The privacy is purely by convention: public methods start with a lower-case letter, private methods start with an upper-case letter. That's the whole system. It's a vibe check, not a lock. πŸ”“

Example time. Suppose our simulated dogs only bark in response to other stimuli β€” they never bark just for fun (very serious dogs, very focused). So bark becomes Bark to flag it as private:

% snit::type dog {
    # Private by convention: begins with uppercase letter.
    method Bark {} {
        return "$self barks."
    }

    method chase {thing} {
        return "$self chases $thing. [$self Bark]"
    }
}
::dog
% dog fido
::fido
% fido chase cat
::fido chases cat. ::fido barks.
%

Capital B, capital boundaries. πŸ’…


✨ ARE THERE ANY LIMITATIONS ON INSTANCE METHOD ARGUMENTS? ✨

Method argument lists work just like normal Tcl proc argument lists β€” no notes, no drama. So yes, they can include arguments with default values, and yes, they can include the args argument. Standard Tcl buffet. 🍽️

BUT. Every method also has a bunch of implicit arguments that Snit hands it on top of whatever you explicitly defined. And the names of those implicit arguments? You may not use them to name explicit arguments. They're taken. Don't.


✨ WHAT IMPLICIT ARGUMENTS ARE PASSED TO EACH INSTANCE METHOD? ✨

The ones Snit quietly slips into every method are: type, selfns, win, and self. Four lil ghosts in every method call. πŸ‘»


✨ WHAT IS $TYPE? ✨

The implicit argument type holds the fully qualified name of the object's type:

% snit::type thing {
    method mytype {} {
        return $type
    }
}
::thing
% thing something
::something
% something mytype
::thing
%

It's your object's origin story. ✨


✨ WHAT IS $SELF? ✨

The implicit argument self holds the object's fully qualified name.

And here's the plot twist πŸŒ€: if the object's command gets renamed, then $self changes to match on subsequent calls. So like… your code should not assume $self is constant, unless you know for a fact the object will never be renamed. Otherwise you're building on sand, bestie.

% snit::type thing {
    method myself {} {
        return $self
    }
}
::thing
% thing mutt
::mutt
% mutt myself
::mutt
% rename mutt jeff
% jeff myself
::jeff
%

She said "I go by jeff now" and meant it. πŸ’…


✨ WHAT IS $SELFNS? ✨

Every Snit object has its own private namespace where it stashes its INSTANCE VARIABLES and OPTIONS. The implicit argument selfns holds the name of that namespace. And here's the beautiful part: its value never changes. It's constant for the whole life of the object, even if the object's name changes. Stable queen. πŸ‘‘

% snit::type thing {
    method myNameSpace {} {
        return $selfns
    }
}
::thing
% thing jeff
::jeff
% jeff myNameSpace
::thing::Snit_inst3
% rename jeff mutt
% mutt myNameSpace
::thing::Snit_inst3
%

See how it stayed ::thing::Snit_inst3 even after the rename? Iconic.

⚠️ Okay so that example does show you how Snit names an instance's private namespace β€” but do not write code that depends on that specific naming convention. It might change in future releases. Admire it, don't marry it. πŸ’”

✨ WHAT IS $WIN? ✨

The implicit argument win is defined for all Snit methods, even though it only really makes sense for the methods of WIDGETS and WIDGET ADAPTORS. And what is it? It's simply the original name of the object β€” whether it's been renamed or not. For widgets and widgetadaptors, that means it's also the name of a Tk window. πŸͺŸ

Here's why that matters: when a snit::widgetadaptor is used to modify the interface of a widget or megawidget, it has to rename the widget's original command and replace it with its own. So if you use win every time you need the Tk window name, then a snit::widget or snit::widgetadaptor can itself be adapted by a snit::widgetadaptor. Everybody's adaptable. It's a whole ecosystem. 🌈

See WIDGETS for more.


✨ HOW DO I PASS AN INSTANCE METHOD AS A CALLBACK? ✨

Depends on the context, bestie. There's no one answer, there's a situation. 😌

Say in my application I've got a dog object named fido, and I want fido to bark when a Tk button called .bark gets pressed. Easy mode β€” I create the callback command the usual way, with list:

button .bark -text "Bark!" -command [list fido bark]

Very typical Tcl style, we're using a callback to hook two independent components together. Clean. Simple. But β€” and here's where it gets spicy 🌢️ β€” suppose the dog object has a graphical interface and owns the button itself? Now the dog has to pass one of its own instance methods to a button that it owns. The obvious thing to do is this:

% snit::widget dog {
    constructor {args} {
        #...
        button $win.barkbtn -text "Bark!" -command [list $self bark]
        #...
    }
}
::dog
%

(Note: in this example our dog becomes a snit::widget, because it's got GUI behavior now. See WIDGETS for more on that.) So if we create a dog called .spot, it'll make a Tk button called .spot.barkbtn, and when you press it, the button calls $self bark. πŸ•

Now β€” this will work, provided .spot is never renamed to something else. And you might be like "bestie, surely renaming widgets is abnormal??" And yeah, it is… unless .spot is the hull component of a snit::widgetadaptor. Because if it is, it will get renamed, and .spot becomes the name of the snit::widgetadaptor object instead. Then when the button gets pressed, that $self bark command is handled by the snit::widgetadaptor β€” which might do the right thing, or might not. Coin flip. πŸͺ™

There's a safer way, and it looks like this:

% snit::widget dog {
    constructor {args} {
        #...
        button $win.barkbtn -text "Bark!" -command [mymethod bark]
        #...
    }
}
::dog
%

mymethod takes any number of arguments and you can use it just like list to build up a callback command. The only difference is that mymethod returns a form of the command that won't change even if the instance's name changes. Rename-proof. Bulletproof. Slay. πŸ›‘οΈβœ¨

On the other hand πŸ–οΈ β€” maybe you actually want to let a widgetadaptor override a method, so that your renamed widget calls the widgetadaptor's method instead of its own. In that case, using [list $self bark] does exactly what you want… but that's a technique to be used only in carefully controlled circumstances. Handle with care, bestie. 🧀


✨ HOW DO I DELEGATE INSTANCE METHODS TO A COMPONENT? ✨

See DELEGATION. (Yes, there's a whole section for that. Go look, it's cute.) 🫢

πŸ’– INSTANCE VARIABLES πŸ’–

Okay bestie, this is the part where we talk about your object's personal stuff. The little secrets. The private diary entries. Let's go. πŸŽ€


🌸 WHAT IS AN INSTANCE VARIABLE? 🌸

An instance variable is a private variable that belongs to one specific Snit object. Just that one. Not shared, not communal, hers. πŸ’…

And they can be either scalars or arrays, so pick your fighter.


✨ HOW IS A SCALAR INSTANCE VARIABLE DEFINED? ✨

You define scalar instance variables right in the type definition using the variable statement. You can literally just name it, or you can be extra and give it a starting value:

snit::type mytype {
    # Define variable "greeting" and initialize it with "Howdy!"
    variable greeting "Howdy!"
}

That's it. That's the whole vibe. 🌈


πŸ¦‹ HOW IS AN ARRAY INSTANCE VARIABLE DEFINED? πŸ¦‹

Array instance variables? Also defined in the type definition, also with the variable command. And you can initialize them at the same time by passing the -array option, which is honestly so convenient:

snit::type mytype {
    # Define array variable "greetings"
    variable greetings -array {
        formal "Good Evening"
        casual "Howdy!"
    }
}

Look at her go. Two moods, one array. πŸ‘‘


😭 WHAT HAPPENS IF I DON'T INITIALIZE AN INSTANCE VARIABLE? 😭

Okay so real talk: variables do not really exist until they are given values. That's just the rules.

So if you don't initialize a variable when you define it, you must make sure you assign a value to it before you ever reference it β€” in the constructor, say, or in some method. No skipping. Otherwise it's giving uninitialized chaos energy and you will not enjoy it. πŸ’€


πŸŽ€ ARE THERE ANY LIMITATIONS ON INSTANCE VARIABLE NAMES? πŸŽ€

Just a few, and they're all pretty reasonable:

  • First: every Snit object has a built-in instance variable called options, and you should never redefine it. Hands off, bestie.
  • Second: all names beginning with Snit_ are reserved for use by Snit internal code. That's Snit's turf, not yours.
  • Third: instance variable names containing the namespace delimiter (::) are likely to cause great confusion. Like, a lot of it. Just don't. πŸ™ˆ

πŸ’… DO I NEED TO DECLARE MY INSTANCE VARIABLES IN MY METHODS? πŸ’…

Nope! Once you've defined an instance variable in the type definition, it can be used in any instance code β€” instance methods, the constructor, and the destructor β€” with no declaration needed.

And that's different from normal Tcl practice, where every non-local variable in a proc needs to be declared. Snit just says "she's with me" and lets her in. ✨

⚠️ But here's the tea: there's a speed penalty to having all instance variables implicitly available in all instance code. Even though your code doesn't need to declare the variables explicitly, Snit still has to declare them, and that takes time. If you have ten instance variables, a method that uses none of them must still pay the declaration penalty for all ten. In most cases, the additional runtime cost is negligible. In extreme cases, you might wish to avoid it β€” and there are two methods for doing so.

Method one: define a single instance variable, an array, and store all of your instance data in that array. This way you're only paying the declaration penalty for one variable β€” and you probably need the variable most of the time anyway. This method breaks down if your instance variables include multiple arrays; in Tcl 8.5, however, the dict command might come to your rescue. 🫢

Method two: declare your instance variables explicitly in your instance code, while not including them in the type definition:

snit::type dog {
    constructor {} {
        variable mood

        set mood happy
    }

    method setmood {newMood} {
        variable mood

        set mood $newMood
    }

    method getmood {} {
        variable mood

        return $mood
    }
}

This lets you make sure that only the required variables are included in each method β€” at the cost of longer code and run-time errors when you forget to declare a variable you need. So, you know, pick your poison. πŸ“


πŸ”₯ HOW DO I PASS AN INSTANCE VARIABLE'S NAME TO ANOTHER OBJECT? πŸ”₯

Okay this one trips everybody up, so listen close.

In Tk, it's common to pass a widget a variable name. For example, Tk label widgets have a -textvariable option which names the variable that will contain the widget's text. That way the program can update the label's value just by assigning a new value to the variable. Clean, right? πŸ’–

But if you naively pass the instance variable name to the label widget, you'll be confused by the result, because Tk will assume that the name names a global variable. It won't find your instance variable and you'll be sitting there like ??? 😭

Instead, you need to provide a fully-qualified variable name. And from within an instance method or a constructor, you can fully qualify the variable's name using the myvar command:

snit::widget mywidget {
    variable labeltext ""

    constructor {args} {
        # ...

        label $win.label -textvariable [myvar labeltext]

        # ...
    }
}

myvar is the bestie that fixes everything here. Remember her. 🌟


πŸ‘‘ HOW DO I MAKE AN INSTANCE VARIABLE PUBLIC? πŸ‘‘

Practically speaking? You don't. πŸ’€

Instead, you'll implement public variables as OPTIONS. Alternatively, you can write INSTANCE METHODS to set and get the variable's value.

That's the whole answer. No public instance variables. We keep it private, we keep it cute. ✨

✨ OPTIONS ✨

πŸ’– WHAT IS AN OPTION? πŸ’–

okay so a type's options are basically what every other object-oriented language out there calls public member variables or properties β€” they're data values that the clients of an object can read, and (usually) write. simple as that, bestie.

Snit's whole implementation of options follows the Tk model pretty much exactly, with one lil twist: snit::type objects usually don't talk to THE TK OPTION DATABASE. but snit::widget and snit::widgetadaptor objects? those always do. no exceptions. periodt.


🐢 HOW DO I DEFINE AN OPTION? 🐢

You define options right in the type definition, using the option statement. Say you're building an app that manages a list of dogs for a pet store (iconic premise honestly), you'd write:

snit::type dog {
    option -breed -default mongrel
    option -color -default brown
    option -akc   -default 0
    option -shots -default 0
}

so according to this, a dog has four notable properties: a breed, a color, a flag that says whether it's pedigreed with the American Kennel Club, and another flag that says whether it's had its shots. and the default dog, evidently, is a brown mutt. we love a humble king. πŸ•

There are a bunch of options you can specify when you're defining an option. but if -default is the only one you need, you can just... drop the word -default entirely, like:

snit::type dog {
    option -breed mongrel
    option -color brown
    option -akc   0
    option -shots 0
}

same thing, less typing, we stan efficiency. πŸ’…

If you don't specify any -default value at all, the option's default value is just the empty string. (but heads up, see THE TK OPTION DATABASE for the plot twist.)

The Snit man page calls options like these "locally defined" options.


πŸŽ€ HOW CAN A CLIENT SET OPTIONS AT OBJECT CREATION? πŸŽ€

The normal convention is: the client can pass any number of options and their values after the object's name, right at creation time. so the ::dog command we defined up there can now make individual dogs, and any or all of the options can be set on the way in.

% dog spot -breed beagle -color "mottled" -akc 1 -shots 1
::spot
% dog fido -shots 1
::fido
%

so ::spot is a pedigreed beagle β€” fancy!! and ::fido is a typical mutt, but his owners evidently take care of him, because he's had his shots. responsible pet ownership, we love to see it. 🫢

Note: if the type defines a constructor, it can specify a totally different object-creation syntax. go read CONSTRUCTORS for the tea.

πŸ” HOW CAN A CLIENT RETRIEVE AN OPTION'S VALUE? πŸ”

You grab option values with the cget method:

% spot cget -color
mottled
% fido cget -breed
mongrel
%

look at them go. ✨


🌈 HOW CAN A CLIENT SET OPTIONS AFTER OBJECT CREATION? 🌈

Any number of options can be set at once using the configure instance method. suppose closer inspection reveals that ::fido is not a brown mongrel at all, but rather a rare Arctic Boar Hound of a lovely dun color (glow up!!):

% fido configure -color dun -breed "Arctic Boar Hound"
% fido cget -color
dun
% fido cget -breed
Arctic Boar Hound

Alternatively, the configurelist method takes a list of options and values, which is occasionally more convenient:

% set features [list -color dun -breed "Arctic Boar Hound"]
-color dun -breed {Arctic Boar Hound}
% fido configurelist $features
% fido cget -color
dun
% fido cget -breed
Arctic Boar Hound
%

And in Tcl 8.5, you can use the * keyword with configure for this same case:

% set features [list -color dun -breed "Arctic Boar Hound"]
-color dun -breed {Arctic Boar Hound}
% fido configure {*}$features
% fido cget -color
dun
% fido cget -breed
Arctic Boar Hound
%

The results are the same. pick your fighter. πŸ’œ


🧚 HOW SHOULD AN INSTANCE METHOD ACCESS AN OPTION VALUE? 🧚

There are two ways an instance method can set and retrieve an option's value.

One is to use the configure and cget methods, like so:

% snit::type dog {
    option -weight 10

    method gainWeight {} {
        set wt [$self cget -weight]
        incr wt
        $self configure -weight $wt
    }
}
::dog
% dog fido
::fido
% fido cget -weight
10
% fido gainWeight
% fido cget -weight
11
%

Alternatively β€” and this is the good part β€” Snit gives you a built-in array instance variable called options. the indices are the option names; the values are the option values. so gainWeight can be rewritten like:

    method gainWeight {} {
        incr options(-weight)
    }

see how much less typing that is?? the usual way to do it, fr fr. πŸ‘‘

But β€” and this is important β€” if you're using -configuremethod or -cgetmethod (described in the following answers), you might still wanna use the configure and cget methods anyway, just so that any special processing you've implemented is sure to get done. also, if the option is delegated to a component, then configure and cget are the only way to access it without touching the component directly. see DELEGATION for more.


πŸ”’ HOW CAN I MAKE AN OPTION READ-ONLY? πŸ”’

Define the option with -readonly yes. that's it, that's the whole trick.

Suppose you've got an option that determines how instances of your type get constructed; it has to be set at creation time, and after that it's constant. for example: a dog never changes its breed. it might or might not have had its shots, and if not, it can get them later. so -breed should be read-only, but -shots should not be.

% snit::type dog {
    option -breed -default mongrel -readonly yes
    option -shots -default no
}
::dog
% dog fido -breed retriever
::fido
% fido configure -shots yes
% fido configure -breed terrier
option -breed can only be set at instance creation
%

caught in 4k. 🚫 the breed stays.


πŸ‘€ HOW CAN I CATCH ACCESSES TO AN OPTION'S VALUE? πŸ‘€

Define a -cgetmethod for the option. boom.


🌸 WHAT IS A -CGETMETHOD? 🌸

A -cgetmethod is a method that gets called whenever the related option's value is queried via the cget instance method. the handler can compute the option's value, retrieve it from a database, or do literally anything else you'd like it to do. main character energy, your choice.

Here's what the default behavior would look like if it were written using a -cgetmethod:

snit::type dog {
    option -color -default brown -cgetmethod GetOption

    method GetOption {option} {
        return $options($option)
    }
}

Any instance method can be used, as long as it takes one argument: the name of the option whose value is to be retrieved.


πŸ”„ HOW CAN I CATCH CHANGES TO AN OPTION'S VALUE? πŸ”„

Define a -configuremethod for the option. yup.


πŸ’« WHAT IS A -CONFIGUREMETHOD? πŸ’«

A -configuremethod is a method that gets called whenever the related option is given a new value via the configure or configurelist instance methods. the method can pass that value on to some other object, store it in a database, or do anything else you'd like it to do.

Here's what the default configuration behavior would look like if written using a -configuremethod:

snit::type dog {
    option -color -default brown -configuremethod SetOption

    method SetOption {option value} {
        set options($option) $value
    }
}

Any instance method can be used, as long as it takes two arguments: the name of the option, and the new value.

Note: if your method doesn't store the value in the options array, then the options array won't get updated. no cap. that's on you bestie.

βœ… HOW CAN I VALIDATE AN OPTION'S VALUE? βœ…

Define a -validatemethod. πŸ’…


🎯 WHAT IS A -VALIDATEMETHOD? 🎯

A -validatemethod is a method that gets called whenever the related option is given a new value via the configure or configurelist instance methods. it's the method's whole job to figure out whether the new value is valid, and to throw an error if it isn't. the -validatemethod, if there is one, is called before the value is stored in the options array β€” and in particular, it's called before the -configuremethod, if there is one. order matters, bestie.

For example, suppose an option always takes a Boolean value. you can make sure the value really is a valid Boolean like this:

% snit::type dog {
    option -shots -default no -validatemethod BooleanOption

    method BooleanOption {option value} {
        if {![string is boolean -strict $value]} {
            error "expected a boolean value, got \"$value\""
        }
    }
}
::dog
% dog fido
% fido configure -shots yes
% fido configure -shots NotABooleanValue
expected a boolean value, got "NotABooleanValue"
%

denied. rejected. blocked. πŸ’€

Note that the same -validatemethod can be used to validate any number of boolean options β€” reuse is real.

Any method can be a -validatemethod, as long as it takes two arguments: the option name and the new option value.


🌸 TYPE VARIABLES 🌸

πŸ’­ WHAT IS A TYPE VARIABLE?

Okay so bestie, imagine a variable that belongs to the whole entire type instead of to any one little instance of it. That's a type variable πŸ’–. In C++ and Java land they call the exact same concept a static member variable. And yes, type variables can be scalars or arrays β€” both are on the menu πŸ“.


✨ HOW IS A SCALAR TYPE VARIABLE DEFINED?

You define scalar type variables right inside the type definition using the typevariable statement. You can just name it and vibe, or you can initialize it with a value too:

snit::type mytype {
    # Define variable "greeting" and initialize it with "Howdy!"
    typevariable greeting "Howdy!"
}

And now every single object of type mytype has access to one shared variable called greeting β€” iconic, one variable for the whole squad 🫢.


πŸŽ€ HOW IS AN ARRAY-VALUED TYPE VARIABLE DEFINED?

Array-valued type variables are also defined with the typevariable command; to initialize one, you toss in the -array option:

snit::type mytype {
    # Define typearray variable "greetings"
    typevariable greetings -array {
        formal "Good Evening"
        casual "Howdy!"
    }
}

😭 WHAT HAPPENS IF I DON'T INITIALIZE A TYPE VARIABLE?

Big important thing: variables do not really exist until they are given values. So if you don't initialize a variable when you define it, then you absolutely must make sure you assign a value to it β€” in the type constructor, say β€” before you reference it. No cap, don't skip this step πŸ’€.


πŸ™ˆ ARE THERE ANY LIMITATIONS ON TYPE VARIABLE NAMES?

Type variable names have the exact same restrictions as the names of INSTANCE VARIABLES do. Same rules, same energy ✨.


πŸ’… DO I NEED TO DECLARE MY TYPE VARIABLES IN MY METHODS?

Nope! Once you've defined a type variable in the type definition, it can be used in INSTANCE METHODS or TYPE METHODS without declaration. The way this differs from normal Tcl practice is that in normal Tcl, every non-local variable in a proc needs to be declared. Snit said "not today" πŸŽ€.

Type variables are subject to the same speed/readability tradeoffs as instance variables; see Do I need to declare my instance variables in my methods? β€” iykyk πŸ‘‘


🌈 HOW DO I PASS A TYPE VARIABLE'S NAME TO ANOTHER OBJECT?

So in Tk it's super common to hand a widget a variable name; for example, Tk label widgets have a -textvariable option which names the variable that will hold the widget's text. This lets the program update the label's value just by assigning a new value to that variable. Chef's kiss 😌.

But here's the trap πŸ•³οΈ: if you naively pass a type variable name to the label widget, you'll be confused by the result, because Tk will assume that name names a global variable. Instead, you need to provide a fully-qualified variable name. From inside an instance method or a constructor, you can fully qualify the type variable's name using the mytypevar command:

snit::widget mywidget {
    typevariable labeltext ""

    constructor {args} {
        # ...

        label $win.label -textvariable [mytypevar labeltext]

        # ...
    }
}

πŸ’– HOW DO I MAKE A TYPE VARIABLE PUBLIC?

Two ways, bestie. The preferred way is to write a pair of TYPE METHODS to set and query the type variable's value. Clean, classy, main character energy πŸ‘‘.

Type variables are stored in the type's namespace, which has the same name as the type itself. So you can also publicize the type variable's name in your documentation so that clients can access it directly. For example:

snit::type mytype {
    typevariable myvariable
}

set ::mytype::myvariable "New Value"

⭐ TYPE METHODS ⭐

πŸ’­ WHAT IS A TYPE METHOD?

A type method is a procedure associated with the type itself rather than with any specific instance of the type, and you call it as a subcommand of the type command. It's giving "the type has its own little set of tricks" ✨.


🌸 HOW DO I DEFINE A TYPE METHOD?

Type methods are defined in the type definition using the typemethod statement:

snit::type dog {
    # List of pedigreed dogs
    typevariable pedigreed

    typemethod pedigreedDogs {} {
        return $pedigreed
    }
}

So say the dog type maintains a list of the names of the dogs that have pedigrees. The pedigreedDogs type method returns that list. Slay πŸ•.

The typemethod statement looks just like a normal Tcl proc, except that it appears in a snit::type definition. And heads up: every type method gets an implicit argument called type, which contains the fully-qualified type name πŸ’….


πŸŽ€ HOW DOES A CLIENT CALL A TYPE METHOD?

The type method name becomes a subcommand of the type's command. For example, assuming the constructor adds each pedigreed dog to the list of pedigreedDogs:

snit::type dog {
    option -pedigreed 0

    # List of pedigreed dogs
    typevariable pedigreed

    typemethod pedigreedDogs {} {
        return $pedigreed
    }

    # ...
}

dog spot -pedigreed 1
dog fido

foreach dog [dog pedigreedDogs] { ... }

πŸ’€ ARE THERE ANY LIMITATIONS ON TYPE METHOD NAMES?

Not really, so long as you avoid the standard type method names: create, destroy, and info. Those three are taken, bestie, don't touch them πŸ™ˆ.


πŸ”’ HOW DO I MAKE A TYPE METHOD PRIVATE?

It's sometimes useful to define private type methods β€” that is, type methods intended to be called only by other type or instance methods of the same object.

Snit doesn't implement any actual access control on type methods; by convention, the names of public methods begin with a lower-case letter, and the names of private methods begin with an upper-case letter. Vibe-based security, periodt πŸ’….

Alternatively, a Snit proc can be used as a private type method; see PROCS.


πŸ¦‹ ARE THERE ANY LIMITATIONS ON TYPE METHOD ARGUMENTS?

Method argument lists are defined just like normal Tcl proc argument lists; in particular, they can include arguments with default values and the args argument. So flexible, so cute 🌈.

However, every type method is called with an implicit argument called type that contains the name of the type command. In addition, type methods should by convention avoid using the names of the arguments implicitly defined for INSTANCE METHODS. Watch out for collisions πŸ‘€.


πŸ‘‘ HOW DOES AN INSTANCE OR TYPE METHOD CALL A TYPE METHOD?

If an instance or type method needs to call a type method, it should use $type to do so:

snit::type dog {

    typemethod pedigreedDogs {} { ... }

    typemethod printPedigrees {} {
        foreach obj [$type pedigreedDogs] { ... }
    }
}

πŸ“ HOW DO I PASS A TYPE METHOD AS A CALLBACK?

It's super common in Tcl to pass a snippet of code to another object so it can call it later. And because types cannot be renamed, you can just use the type name, or β€” if the callback is registered from within a type method β€” type. For example, suppose we want to print a list of pedigreed dogs when a Tk button is pushed:

button .btn -text "Pedigrees" -command [list dog printPedigrees]
pack .btn

Alternatively, from a method or type method you can use the mytypemethod command, just as you would use mymethod to define a callback command for INSTANCE METHODS. Same energy, different scope ✨.


🌈 CAN TYPE METHODS BE HIERARCHICAL?

Yes! You can define hierarchical type methods in just the same way as you can define hierarchical instance methods. See INSTANCE METHODS for more. Go off, build that tree 🌳.


🧚 PROCS 🧚

πŸ’­ WHAT IS A PROC?

A Snit proc is really just a Tcl proc defined within the type's namespace. You can use procs for private code that isn't related to any particular instance. Little helper functions that just vibe in the background 🫢.


✨ HOW DO I DEFINE A PROC?

Procs are defined by including a proc statement in the type definition:

snit::type mytype {
    # Pops and returns the first item from the list stored in the
    # listvar, updating the listvar
   proc pop {listvar} { ... }

   # ...
}

πŸ”₯ ARE THERE ANY LIMITATIONS ON PROC NAMES?

Any name can be used, so long as it does not begin with Snit_; names beginning with Snit_ are reserved for Snit's own use. But the wise programmer will avoid proc names (set, list, if, etc.) that would shadow standard Tcl command names. Don't be that girl, bestie πŸ’€.

proc names, being private, should begin with a capital letter according to convention; however, as there are typically no public procs in the type's namespace it doesn't matter much either way 🀷.


πŸŽ€ HOW DOES A METHOD CALL A PROC?

Just like it calls any Tcl command. For example:

snit::type mytype {
    # Pops and returns the first item from the list stored in the
    # listvar, updating the listvar
    proc pop {listvar} { ... }

    variable requestQueue {}

    # Get one request from the queue and process it.
    method processRequest {} {
        set req [pop requestQueue]
    }
}

πŸ’– HOW CAN I PASS A PROC TO ANOTHER OBJECT AS A CALLBACK?

The myproc command returns a callback command for the proc, just as mymethod does for a method. Matching set, adorable πŸ’….


🌟 TYPE CONSTRUCTORS 🌟

πŸ’­ WHAT IS A TYPE CONSTRUCTOR?

A type constructor is a body of code that initializes the type as a whole β€” rather like a C++ static initializer. The body of a type constructor is executed once when the type is defined, and never again. One shot, one opportunity 🎀.

A type can have at most one type constructor.


✨ HOW DO I DEFINE A TYPE CONSTRUCTOR?

A type constructor is defined by using the typeconstructor statement in the type definition. For example, suppose the type uses an array-valued type variable as a look-up table, and the values in the array have to be computed at start-up:

% snit::type mytype {
    typevariable lookupTable

    typeconstructor {
        array set lookupTable {key value...}
    }
}

✨ CONSTRUCTORS ✨

πŸ’– WHAT IS A CONSTRUCTOR?

Okay bestie, so in OOP land, an object's constructor is the lil glow-up moment where the object gets fully initialized the second it's born. It receives the list of options that got passed to the snit::type command's create method, and then it can do literally whatever it wants with them. Like β€” computing instance variable values? Slay. Reading data from files? Sure. Creating other objects, updating type and instance variables, whatever β€” it's all fair game. πŸ’…

And here's the tea: the constructor's return value gets ignored. Yep, straight up. Unless it's an error, obviously β€” errors always get noticed, they're the main character. 😭


πŸŽ€ HOW DO I DEFINE A CONSTRUCTOR?

You define one with the constructor statement inside the type definition. Easy.

So like, imagine we wanna keep a list of all the pedigreed dogs πŸ• β€” maintained in a type variable, retrieved by a type method. Every time a dog is created, it adds itself to the list... but only if it's registered with the American Kennel Club. Fancy.

% snit::type dog {
    option -akc 0

    typevariable akcList {}

    constructor {args} {
        $self configurelist $args

        if {$options(-akc)} {
            lappend akcList $self
        }
    }

    typemethod akclist {} {
        return $akcList
    }
}
::dog
% dog spot -akc 1
::spot
% dog fido
::fido
% dog akclist
::spot
%

See how spot made the cut and fido is just... there? Iconic behavior, honestly. 🌟


🌸 WHAT DOES THE DEFAULT CONSTRUCTOR DO?

If you don't write a constructor yourself, no worries β€” you get the default one for free, and it's literally identical to this explicitly-defined version right here:

snit::type dog {
    constructor {args} {
        $self configurelist $args
    }
}

When the constructor gets called, args is set to the list of arguments that come after the object's name. And the constructor can interpret that list however it wants β€” but the normal convention (iykyk) is to treat it as a list of option names and values, just like the example above. If you just wanna save those option values, use the configurelist method. That's the move. ✨


πŸ¦‹ CAN I CHOOSE A DIFFERENT SET OF ARGUMENTS FOR THE CONSTRUCTOR?

Bestie, yes you can. Say we wanted to make absolutely sure the breed was stated explicitly for every dog at creation time, and couldn't be changed after. One way to do that:

% snit::type dog {
    variable breed

    option -color brown
    option -akc 0

    constructor {theBreed args} {
        set breed $theBreed
        $self configurelist $args
    }

    method breed {} { return $breed }
}
::dog
% dog spot dalmatian -color spotted -akc 1
::spot
% spot breed
dalmatian

Gorgeous. BUT β€” plot twist 😬 β€” the drawback is that this syntax is non-standard, and it may limit the compatibility of your new type with other people's code. Like, Snit literally assumes it can create COMPONENTS using the standard creation syntax. So do this with intention, okay? πŸ’€


πŸ’œ ARE THERE ANY LIMITATIONS ON CONSTRUCTOR ARGUMENTS?

Yep! Constructor argument lists have the exact same limitations as those on instance method argument lists. Same implicit arguments, and it can contain default values and the args argument. Same rules, same vibe. Periodt.


πŸ‘‘ IS THERE ANYTHING SPECIAL ABOUT WRITING THE CONSTRUCTOR?

Honestly? Yes, and it's giving tricky. Writing the constructor can get complicated if you're delegating options to components, and there are specific issues relating to snit::widgets and snit::widgetadaptors. So go read up on DELEGATION, WIDGETS, WIDGET ADAPTORS, and THE TK OPTION DATABASE. They're all in this doc, bestie, don't skip them. 🫢


πŸ”₯ DESTRUCTORS πŸ”₯

πŸ₯€ WHAT IS A DESTRUCTOR?

A destructor is a special kind of method that gets called when an object is destroyed. It's the responsible friend who handles all the clean-up when the object leaves the party: destroying COMPONENTS, closing files, and so forth. She's doing the work nobody wants to do. Respect. πŸ’…


πŸ’€ HOW DO I DEFINE A DESTRUCTOR?

You define destructors by using the destructor statement in the type definition. Simple as that.

So like, if we're maintaining a list of pedigreed dogs, we're gonna wanna remove dogs from it when they're destroyed. Otherwise the list just grows forever and that's a mess. πŸ™ˆ

snit::type dog {
    option -akc 0

    typevariable akcList {}

    constructor {args} {
        $self configurelist $args

        if {$options(-akc)} {
            lappend akcList $self
        }
    }

    destructor {
        set ndx [lsearch $akcList $self]

        if {$ndx != -1} {
            set akcList [lreplace $akcList $ndx $ndx]
        }
    }

    typemethod akclist {} {
        return $akcList
    }
}

In and out, clean and tidy. 🌈


🌺 ARE THERE ANY LIMITATIONS ON DESTRUCTOR ARGUMENTS?

Yes bestie β€” a destructor has no explicit arguments. None. Zero. Nada. It just vibes. 🧚


⭐ WHAT IMPLICIT ARGUMENTS ARE PASSED TO THE DESTRUCTOR?

The destructor gets the same implicit arguments that get passed to INSTANCE METHODS: type, selfns, win, and self. All four, every time.


πŸ“ MUST COMPONENTS BE DESTROYED EXPLICITLY?

Yes and no. 😭 The classic answer.

Here's the breakdown:

  • Any Tk widgets created by a snit::widget or snit::widgetadaptor will be destroyed automatically by Tk when the megawidget is destroyed β€” that's just normal Tk behavior, since destroying a parent widget destroys the whole tree. 🌳
  • Components of normal snit::types, on the other hand? Never destroyed automatically. Ever. And same goes for non-widget components of Snit megawidgets. So if your object creates them in its constructor, it should generally destroy them in its destructor. That's on you, bestie.

πŸ¦„ IS THERE ANY SPECIAL ABOUT WRITING A DESTRUCTOR?

Yes, and this one's important, so listen up. πŸ‘€

If an object's constructor throws an error, the object's destructor will still be called to clean up. Which means the object might not be completely constructed when the destructor runs. And that can cause the destructor to throw its own error β€” and the result is usually misleading, confusing, and unhelpful. Like, the worst possible error message. πŸ’€

So consequently, it's super important to write your destructor so that it's fail-safe.

For example: a dog might create a tail component, and that component needs to be destroyed. But suppose there's an error while processing the creation options β€” the destructor gets called, and there's no tail to destroy at all. The simplest solution is generally to catch and ignore any errors while destroying components. Just wrap it. 🫢

snit::type dog {
    component tail

    constructor {args} {
        $self configurelist $args

        set tail [tail %AUTO%]
    }

    destructor {
        catch {$tail destroy}
    }
}

See that catch? That's the whole safety net. Do this. Your future self will thank you. πŸ’–

🧩 COMPONENTS 🧩


πŸ’– WHAT IS A COMPONENT? πŸ’–

okay so bestie, picture this: an object is out here living its life, and it's also creating and managing a whole little squad of other objects. a Snit megawidget? yeah, it's constantly birthing Tk widgets like it's a widget daycare. those objects are part of the main object β€” it's literally composed of them β€” so we call them components of the object. πŸ’…

but Snit also has a more precise meaning for COMPONENT, and it's the one that actually matters. the components of a Snit object are those objects to which methods or options can be delegated. that's it, that's the definition. (go look at DELEGATION for the whole tea on delegation.) β˜•


πŸ› οΈ HOW DO I DECLARE A COMPONENT? πŸ› οΈ

first things first: figure out what role this component plays inside your object, and give that role a name. then you declare it with the role name plus the component statement. and here's the cute part β€” the component statement declares an instance variable that holds the component's command name once it gets created. πŸŽ€

so like, imagine your dog object makes a tail object (for maximum wagging, obviously):

snit::type dog {
    component mytail

    constructor {args} {
        # Create and save the component's command
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }

    method wag {} {
        $mytail wag
    }
}

see how it genuinely does not care what the tail object's real name is? the dog refers to it by its component name. iconique. ✨

this example shows one way to delegate the wag method to the mytail component β€” but head to DELEGATION for an easier glow-up. 🌈


🏷️ HOW IS A COMPONENT NAMED? 🏷️

a component has two names, and honestly? main character energy for having two. 🌟

  • the first name is the name of the component variable β€” that's the role the component object plays inside the Snit object. this is the component name proper, and it's what you use to refer to the component inside Snit code.
  • the second name is the name of the actual component object your Snit object's constructor created. this one is always a Tcl command name, and we call it the component's object name.

in the example from the previous question, the component name is mytail, and the mytail component's object name got picked automatically by Snit because %AUTO% was used when the component object was created. no cap. 🫢


🚧 ARE THERE ANY LIMITATIONS ON COMPONENT NAMES? 🚧

yep, there are. buckle up:

  • snit::widget and snit::widgetadaptor objects have a special component called the hull component. so the name hull should be used for no other purpose. periodt.
  • otherwise, since component names are literally instance variable names, they have to follow the rules for INSTANCE VARIABLES. that's the whole vibe check. πŸ’€

πŸ‘‘ WHAT IS AN OWNED COMPONENT? πŸ‘‘

an owned component is a component whose object command's lifetime is controlled by the snit::type or snit::widget. basically, your object holds the leash. πŸ•

and like, as we said, a component is an object your object can delegate methods or options to. under that definition, your object will usually create its component objects β€” but not necessarily. consider this: a dog object has a tail component, but the tail also knows it's part of the dog. mutual. πŸ’ž

snit::type dog {
    component mytail

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }

    destructor {
        catch {$mytail destroy}
    }

    delegate method wagtail to mytail as wag

    method bark {} {
        return "$self barked."
    }
}

 snit::type tail {
     component mydog
     option -partof -readonly yes

     constructor {args} {
         $self configurelist $args
         set mydog $options(-partof)
     }

     method wag {} {
         return "Wag, wag."
     }

     method pull {} {
         $mydog bark
     }
 }

so if you ask a dog to wag its tail, it tells its tail to wag. and if you pull the dog's tail, the tail tells the dog to bark. iconic bidirectional bestie behaviour. 🐾

in this scenario, the tail is a component of the dog, and the dog is a component of the tail β€” but the dog owns the tail, not the other way around. hierarchy, but make it mutual. πŸ‘‘


βš™οΈ WHAT DOES THE INSTALL COMMAND DO? βš™οΈ

the install command creates an owned component using a command you specify, and assigns the result to the component's instance variable. like so:

snit::type dog {
    component mytail

    constructor {args} {
        # set mytail [tail %AUTO% -partof $self]
        install mytail using tail %AUTO% -partof $self
        $self configurelist $args
    }
}

inside a snit::type's code, that install command is equivalent to the commented-out set mytail command. simple. πŸ’š

but β€” inside a snit::widget's or snit::widgetadaptor's code, install does extra credit work: it also queries THE TK OPTION DATABASE and initializes the new component's options accordingly. so for consistency, it's a good idea to get in the habit of using install for all owned components. future you says thank you. 🫢✨


πŸ•°οΈ MUST OWNED COMPONENTS BE CREATED IN THE CONSTRUCTOR? πŸ•°οΈ

no, not necessarily. and actually? there's no reason an object can't destroy and recreate a component multiple times over its own lifetime. glow-up, glow-down, glow-up again. πŸ”πŸ’…


⚠️ ARE THERE ANY LIMITATIONS ON COMPONENT OBJECT NAMES? ⚠️

yes. several. let's go:

  • component objects which are Tk widgets or megawidgets must have valid Tk window names.
  • component objects which are not widgets or megawidgets must have fully-qualified command names β€” i.e., names that include the full namespace of the command. good news: Snit always creates objects with fully qualified names anyway. πŸŽ€

next up: the object names of components owned by your object must be unique. no problemo for widget components, since widget names are always unique β€” but consider this code:

snit::type tail { ... }

snit::type dog {
    delegate method wag to mytail

    constructor {} {
        install mytail using tail mytail
    }
}

this code uses the component name, mytail, as the component object name. and bestie, that is not good. here's why: Snit instance code executes in the Snit type's namespace. in this case, the mytail component gets created in the ::dog:: namespace, so it ends up with the name ::dog::mytail.

now imagine you make two dogs. 😭 both dogs try to create a tail called ::dog::mytail. the first one succeeds, the second one fails, because Snit won't let you create an object if its name is already a command. oop.

here are two ways to dodge that situation:

  1. first, if the component type is a snit::type you can specify %AUTO% as its name, and you're guaranteed a unique name. this is the safest thing to do:
    install mytail using tail %AUTO%
  1. if the component type isn't a snit::type you can create the component in the object's instance namespace:
    install mytail using tail ${selfns}::mytail

just make sure you pick a unique name within the instance namespace. πŸ‘‘


πŸ—‘οΈ MUST I DESTROY THE COMPONENTS I OWN? πŸ—‘οΈ

that depends, bestie. here's the breakdown:

  • when a parent widget is destroyed, all child widgets get destroyed automatically. so if your object is a snit::widget or snit::widgetadaptor, you don't need to destroy any components that are widgets, because they'll generally be children or descendants of your megawidget. free cleanup. ✨
  • if your object is an instance of snit::type, though? none of its owned components will be destroyed automatically. and non-widget components of a snit::widget won't be destroyed automatically either. all such owned components must be destroyed explicitly, or they won't be destroyed at all. πŸ‘»
leaking components is not the vibe. destroy them or they haunt you forever. πŸ’€

🌈 CAN I EXPOSE A COMPONENT'S OBJECT COMMAND AS PART OF MY INTERFACE? 🌈

yes! and there are two ways to do it:

  • the most appropriate way is usually to use DELEGATION. delegation lets you pass the options and methods you specify along to particular components. this effectively hides the components from the users of your type and ensures good encapsulation. chef's kiss. πŸ‘©β€πŸ³πŸ’‹
  • however, there are times when it's appropriate β€” not to mention simpler β€” just to make the entire component part of your type's public interface.

🎁 HOW DO I EXPOSE A COMPONENT'S OBJECT COMMAND? 🎁

when you declare the component, specify the component statement's -public option. the value of this option is the name of a method which will be delegated to your component's object command. πŸͺ„

for example, suppose you've written a combobox megawidget which owns a listbox widget, and you want to make the listbox's entire interface public. you can do it like this:

snit::widget combobox {
     component listbox -public listbox

     constructor {args} {
         install listbox using listbox $win.listbox ....
     }
}

combobox .mycombo
.mycombo listbox configure -width 30

your combobox widget, .mycombo, now has a listbox method which has all of the same subcommands as the listbox widget itself. so the code above sets the listbox component's width to 30. slay. πŸ’…

usually you'll let the method name be the same as the component name β€” but you can name it anything you like. your component, your rules. πŸŽ€


✨ TYPE COMPONENTS ✨

πŸ’– WHAT IS A TYPE COMPONENT? πŸ’–

ok so bestie, a type component is a component that belongs to the type itself instead of to one specific instance of the type. it's giving "shared property, not personal property" πŸ’…

the relationship between components and type components is exactly the same as the relationship between INSTANCE VARIABLES and TYPE VARIABLES. and yes β€” both INSTANCE METHODS and TYPE METHODS can be delegated to type components. the range? unmatched. πŸ‘‘

once you understand COMPONENTS and DELEGATION, type components are just more of the same. same vibe, different scope. periodt.


πŸŽ€ HOW DO I DECLARE A TYPE COMPONENT? πŸŽ€

you declare one with the typecomponent statement. it takes the same options as the component statement does β€” that's -inherit and -public β€” and it defines a type variable to hold the type component's object command. slay.

say your model has, like, many dogs but only one veterinarian. the vet is clearly main character shared-resource energy, so you'd make the veterinarian a type component:

snit::type veterinarian { ... }

snit::type dog {
    typecomponent vet

    # ...
}

🌸 HOW DO I INSTALL A TYPE COMPONENT? 🌸

just use the set command to assign the component's object command to the type component. that's it. no drama. 🫢

because types (even snit::widget types) are not widgets, and don't have options anyway, the extra features of the install command are simply not needed here.

you'll usually install type components in the type constructor, like this:

snit::type veterinarian { ... }

snit::type dog {
    typecomponent vet

    typeconstructor {
        set vet [veterinarian %AUTO%]
    }
}

πŸ¦‹ ARE THERE ANY LIMITATIONS ON TYPE COMPONENT NAMES? πŸ¦‹

yes bestie β€” the same as on INSTANCE VARIABLES, TYPE VARIABLES, and normal COMPONENTS. no special treatment, no exceptions, no cap. πŸ’€


πŸ’… DELEGATION πŸ’…

⭐ WHAT IS DELEGATION? ⭐

delegation, simply put, is when you pass a task you've been given to one of your assistants. (you do have assistants, don't you? πŸ™ˆ) and snit objects can do the exact same thing.

the following example shows one way in which the dog object can delegate its wag method and its -taillength option to its tail component:

snit::type dog {
    variable mytail

    option -taillength -configuremethod SetTailOption -cgetmethod GetTailOption

    method SetTailOption {option value} {
         $mytail configure $option $value
    }

    method GetTailOption {option} {
         $mytail cget $option
    }

    method wag {} {
        $mytail wag
    }

    constructor {args} {
        install mytail using tail %AUTO% -partof $self
        $self configurelist $args
    }

}

this is the hard way to do it, but it demonstrates what delegation is all about. see the following answers for the easy way. πŸ’–

note that the constructor calls the configurelist method after it creates its tail; otherwise, if -taillength appeared in the list of args we'd get an error. so like, order matters, bestie. 😭

πŸ”₯ HOW CAN I DELEGATE A METHOD TO A COMPONENT OBJECT? πŸ”₯

delegation happens so often that snit makes it easy. any method can be delegated to any component or type component by placing a single delegate statement in the type definition. (see COMPONENTS and TYPE COMPONENTS for more info about component names.)

for example, here's a much better way to delegate the dog object's wag method:

% snit::type dog {
    delegate method wag to mytail

    constructor {} {
        install mytail using tail %AUTO%
    }
}
::dog
% snit::type tail {
    method wag {} { return "Wag, wag, wag."}
}
::tail
% dog spot
::spot
% spot wag
Wag, wag, wag.

this code has the same effect as the code shown under the previous question: when a dog's wag method is called, the call and its arguments are passed along automatically to the tail object. it's giving effortless ✨

note that when a component is mentioned in a delegate statement, the component's instance variable is defined implicitly. however, it's still good practice to declare it explicitly using the component statement.
note also that you can define a method name using the method statement, or you can define it using delegate; you can't do both. pick a lane, babe. πŸ’…

🌈 CAN I DELEGATE TO A METHOD WITH A DIFFERENT NAME? 🌈

suppose you wanted to delegate the dog's wagtail method to the tail's wag method. after all, you wag the tail, not the dog. it's easily done:

snit::type dog {
    delegate method wagtail to mytail as wag

    constructor {args} {
        install mytail using tail %AUTO% -partof $self
        $self configurelist $args
    }
}

πŸ“ CAN I DELEGATE TO A METHOD WITH ADDITIONAL ARGUMENTS? πŸ“

suppose the tail's wag method takes as an argument the number of times the tail should be wagged. you want to delegate the dog's wagtail method to the tail's wag method, specifying that the tail should be wagged exactly three times. this is easily done, too:

snit::type dog {
    delegate method wagtail to mytail as {wag 3}
    # ...
}

snit::type tail {
    method wag {count} {
        return [string repeat "Wag " $count]
    }
    # ...
}

🧚 CAN I DELEGATE A METHOD TO SOMETHING OTHER THAN AN OBJECT? 🧚

normal method delegation assumes that you're delegating a method (a subcommand of an object command) to a method of another object (a subcommand of a different object command). but not all Tcl objects follow Tk conventions, and not everything you'd to which you'd like to delegate a method is necessary an object. consequently, snit makes it easy to delegate a method to pretty much anything you like using the delegate statement's using clause.

suppose your dog simulation stores dogs in a database, each dog as a single record. the database API you're using provides a number of commands to manage records; each takes the record ID (a string you choose) as its first argument. for example, saverec saves a record. if you let the record ID be the name of the dog object, you can delegate the dog's save method to the saverec command as follows:

snit::type dog {
    delegate method save using {saverec %s}
}

the %s is replaced with the instance name when the save method is called; any additional arguments are the appended to the resulting command.

the using clause understands a number of other %-conversions; in addition to the instance name, you can substitute in the method name (%m), the type name (%t), the instance namespace (%n), the Tk window name (%w), and, if a component or typecomponent name was given in the delegate statement, the component's object command (%c). iconic substitution menu honestly πŸ’œ


πŸ‘‘ HOW CAN I DELEGATE A METHOD TO A TYPE COMPONENT OBJECT? πŸ‘‘

just exactly as you would to a component object. the delegate method statement accepts both component and type component names in its to clause. same energy, no notes. ✨


πŸ’– HOW CAN I DELEGATE A TYPE METHOD TO A TYPE COMPONENT OBJECT? πŸ’–

use the delegate typemethod statement. it works like delegate method, with these differences: first, it defines a type method instead of an instance method; second, the using clause ignores the %s, %n, and %w %-conversions.

naturally, you can't delegate a type method to an instance component… snit wouldn't know which instance should receive it. like, which one?? πŸ™ˆ


πŸŽ€ HOW CAN I DELEGATE AN OPTION TO A COMPONENT OBJECT? πŸŽ€

the first question in this section (see DELEGATION) shows one way to delegate an option to a component; but this pattern occurs often enough that snit makes it easy. for example, every tail object has a -length option; we want to allow the creator of a dog object to set the tail's length. we can do this:

% snit::type dog {
    delegate option -length to mytail

    constructor {args} {
        install mytail using tail %AUTO% -partof $self
        $self configurelist $args
    }
}
::dog
% snit::type tail {
    option -partof
    option -length 5
}
::tail
% dog spot -length 7
::spot
% spot cget -length
7

this produces nearly the same result as the -configuremethod and -cgetmethod shown under the first question in this section: whenever a dog object's -length option is set or retrieved, the underlying tail object's option is set or retrieved in turn.

note that you can define an option name using the option statement, or you can define it using delegate; you can't do both. (same rule as methods, bestie β€” pick one!) πŸ’…

πŸ¦‹ CAN I DELEGATE TO AN OPTION WITH A DIFFERENT NAME? πŸ¦‹

in the previous answer we delegated the dog's -length option down to its tail. this is, of course, wrong. the dog has a length, and the tail has a length, and they are different. what we'd really like to do is give the dog a -taillength option, but delegate it to the tail's -length option:

snit::type dog {
    delegate option -taillength to mytail as -length

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }
}

🌟 HOW CAN I DELEGATE ANY UNRECOGNIZED METHOD OR OPTION TO A COMPONENT OBJECT? 🌟

it may happen that a snit object gets most of its behavior from one of its components. this often happens with snit::widgetadaptors, for example, where we wish to slightly the modify the behavior of an existing widget. to carry on with our dog example, however, suppose that we have a snit::type called animal that implements a variety of animal behaviors β€” moving, eating, sleeping, and so forth. we want our dog objects to inherit these same behaviors, while adding dog-like behaviors of its own. here's how we can give a dog methods and options of its own while delegating all other methods and options to its animal component:

snit::type dog {
    delegate option * to animal
    delegate method * to animal

    option -akc 0

    constructor {args} {
        install animal using animal %AUTO% -name $self
        $self configurelist $args
    }

    method wag {} {
        return "$self wags its tail"
    }
}

that's it. a dog is now an animal that has a -akc option and can wag its tail. ate and left no crumbs πŸ•βœ¨

note that we don't need to specify the full list of method names or option names that animal will receive. it gets anything dog doesn't recognize β€” and if it doesn't recognize it either, it will simply throw an error, just as it should. πŸ’€

You can also delegate all unknown type methods to a type component using delegate typemethod *. Slay.


HOW CAN I DELEGATE ALL BUT CERTAIN METHODS OR OPTIONS TO A COMPONENT?

Okay so remember how we said every dog is an animal by delegating all the unknown methods and options to the animal component? Cute. But what if the animal type has some methods or options that we're not trying to inherit? Bestie, we need to suppress those.

One way: explicitly delegate every single option and method by hand and give up the cozy convenience of delegate method * and delegate option *. Doable, but exhausting.

But if you only want to suppress <em>a few</em> things, there's a way easier vibe:

snit::type dog {
    delegate option * to animal except -numlegs
    delegate method * to animal except {fly climb}

    # ...

    constructor {args} {
        install animal using animal %AUTO% -name $self -numlegs 4
        $self configurelist $args
    }

    # ...
}

Dogs have four legs, periodt, so we set that explicitly when we create the animal component, and we explicitly exclude -numlegs from the delegated options. And dogs literally cannot fly or climb, so we exclude those animal methods too, just like that. πŸ’…


CAN A HIERARCHICAL METHOD BE DELEGATED?

Yes bestie! Just put multiple words in the delegated method's name:

snit::type tail {
    method wag {} {return "Wag, wag"}
    method droop {} {return "Droop, droop"}
}

snit::type dog {
    delegate method {tail wag} to mytail
    delegate method {tail droop} to mytail

    # ...

    constructor {args} {
        install mytail using tail %AUTO%
        $self configurelist $args
    }

    # ...
}

And unrecognized hierarchical methods can get delegated too β€” the code below sends all subcommands of the tail method over to the mytail component:

snit::type dog {
    delegate method {tail *} to mytail

    # ...
}

✨ WIDGETS ✨

WHAT IS A SNIT::WIDGET?

A snit::widget is the Snit take on what Tcl programmers usually call a megawidget: a widget-ish object that's usually made of one or more Tk widgets, all hanging out inside a Tk frame. πŸŽ€

A snit::widget is also a special kind of snit::type. Basically everything in this FAQ that's about snit::types also applies to snit::widgets. Same energy, different fit.


HOW DO I DEFINE A SNIT::WIDGET?

You define snit::widgets with the snit::widget command, exactly like snit::types get defined by the snit::type command. πŸ¦‹

The body of the definition can hold all the same kinds of statements, plus a couple more that we'll get to below.


HOW DO SNIT::WIDGETS DIFFER FROM SNIT::TYPES?

  • The name of an instance of a snit::type can be any valid Tcl command name, in any namespace. The name of an instance of a snit::widget has to be a valid Tk widget name, and its parent widget has to already exist.
  • An instance of a snit::type can be destroyed by calling its destroy method. Instances of a snit::widget have no destroy method β€” use the Tk destroy command instead.
  • Every instance of a snit::widget has one predefined component called its hull component. The hull is usually a Tk frame or toplevel widget; any other widgets created as part of the snit::widget will usually live inside the hull.
  • snit::widgets can have their options receive default values from THE TK OPTION DATABASE. πŸ‘‘

WHAT IS A HULL COMPONENT?

Snit can't create a Tk widget object β€” only Tk can do that. So every instance of a snit::widget has to be wrapped around a genuine Tk widget, and that Tk widget is what we call the hull component. Snit basically piggybacks all the behavior you define (methods, options, and so on) on top of the hull component so the whole thing behaves like a standard Tk widget. 🫢

For snit::widgets, the hull component must be a Tk widget that defines the -class option.

snit::widgetadaptors differ from snit::widgets mainly in that any kind of widget can be used as the hull component; see WIDGET ADAPTORS.


HOW CAN I SET THE HULL TYPE FOR A SNIT::WIDGET?

A snit::widget's hull component will usually be a Tk frame widget; but honestly it can be any Tk widget that defines the -class option. You can pick the hull type you want explicitly by dropping the hulltype command into the widget definition:

snit::widget mytoplevel {
    hulltype toplevel

    # ...
}

If no hulltype command shows up, the hull is a frame.

By default, Snit recognizes these hull types: the Tk widgets frame, labelframe, toplevel, and the Tile widgets ttk::frame, ttk::labelframe, and ttk::toplevel. If you want some other kind of widget as the hull type, you can lappend the widget command to the variable snit::hulltypes β€” always provided the widget defines the -class option. For example, say Tk gets a new widget type called a prettyframe:

lappend snit::hulltypes prettyframe

snit::widget mywidget {
    hulltype prettyframe

    # ...
}

HOW SHOULD I NAME WIDGETS WHICH ARE COMPONENTS OF A SNIT::WIDGET?

Every widget β€” genuine Tk widget or Snit megawidget β€” has to have a valid Tk window name. When a snit::widget is first created, its instance name, self, is a Tk window name; but if the snit::widget gets used as the hull component by a snit::widgetadaptor, its instance name will get changed to something else. So for this reason, every snit::widget method, constructor, destructor, and so forth gets passed another implicit argument, win, which is the window name of the megawidget. Any children should be named using win as the root. 🌸

So like, imagine you're writing a toolbar widget β€” a frame made of a bunch of buttons sitting side-by-side. It might look a little something like this:

snit::widget toolbar {
    delegate option * to hull

    constructor {args} {
        button $win.open -text Open -command [mymethod open]
        button $win.save -text Save -command [mymethod save]

        # ....

        $self configurelist $args

    }
}

See also the question on renaming objects, toward the top of this file. iykyk πŸ‘€


πŸ¦‹ WIDGET ADAPTORS πŸ¦‹

WHAT IS A SNIT::WIDGETADAPTOR?

A snit::widgetadaptor is a kind of snit::widget. But whereas a snit::widget's hull gets created automatically and is always a Tk frame, a snit::widgetadaptor can be based on any Tk widget β€” or on any Snit megawidget, or even (with luck) on megawidgets defined using some other package. πŸ”₯

It's called a widget adaptor because it lets you take an existing widget and customize its behavior.


HOW DO I DEFINE A SNIT::WIDGETADAPTOR?

Use the snit::widgetadaptor command. The definition for a snit::widgetadaptor looks just like the one for a snit::type or snit::widget, except the constructor <em>must</em> create and install the hull component.

For example, this code makes a read-only text widget by the simple trick of turning its insert and delete methods into no-ops. Then we define new methods, ins and del, which get delegated to the hull component as insert and delete. So we've adapted the text widget and given it new behavior while it's still fundamentally a text widget. Iconic honestly.

::snit::widgetadaptor rotext {

    constructor {args} {
        # Create the text widget; turn off its insert cursor
        installhull using text -insertwidth 0

        # Apply any options passed at creation time.
        $self configurelist $args
    }

    # Disable the text widget's insert and delete methods, to
    # make this readonly.
    method insert {args} {}
    method delete {args} {}

    # Enable ins and del as synonyms, so the program can insert and
    # delete.
    delegate method ins to hull as insert
    delegate method del to hull as delete

    # Pass all other methods and options to the real text widget, so
    # that the remaining behavior is as expected.
    delegate method * to hull
    delegate option * to hull
}

The most important part is in the constructor. snit::widget creates the hull for you, but snit::widgetadaptor cannot β€” it has no idea what kind of widget you want. So the first thing the constructor does is create the hull component (a Tk text widget in this case), and then installs it with the installhull command.

Note: There is no instance command until you create one by installing a hull component. Any attempt to pass methods to $self prior to calling installhull will fail. 😭

CAN I ADAPT A WIDGET CREATED ELSEWHERE IN THE PROGRAM?

Yes. πŸ’–

Sometimes it's just convenient to adapt a pre-existing widget instead of creating your own. For example, the Bwidget PagesManager widget manages a set of frame widgets, only one of which is visible at a time. The application picks which frame is visible. All of those frames are created by the PagesManager itself, using its add method. And it's convenient to adapt those frames to do what we want them to do.

In a case like this, the Tk widget will already exist by the time the snit::widgetadaptor is created. Snit gives you an alternate form of the installhull command for exactly this:

snit::widgetadaptor pageadaptor {
    constructor {args} {
        # The widget already exists; just install it.
        installhull $win

        # ...
    }
}

CAN I ADAPT ANOTHER MEGAWIDGET?

Maybe. πŸ‘€ If the other megawidget is a snit::widget or snit::widgetadaptor, then yes. If it isn't, then, again... maybe. You'll just have to try it and see. You're most likely to run into trouble with widget destruction β€” you have to make sure your megawidget code receives the <Destroy> event <em>before</em> the megawidget you're adapting does. That's the tricky bit, fr fr.


πŸ’œ THE TK OPTION DATABASE πŸ’œ

WHAT IS THE TK OPTION DATABASE?

The Tk option database is a database of default option values maintained by Tk itself; every Tk application has one. The whole concept of the option database comes from something called the X Windows resource database β€” but the option database is available in every Tk implementation, including the ones that don't use the X Windows system at all (e.g., Microsoft Windows). 🌈

Full details about the Tk option database are honestly beyond the scope of this doc πŸ’– β€” but if you want the real tea, both Practical Programming in Tcl and Tk by Welch, Jones, and Hobbs, and Effective Tcl/Tk Programming by Harrison and McClennan., have genuinely good intros to it. Go read those, bestie.

Snit is built so that most of the time it just does the right thing with the option database β€” provided the widget developer also does the right thing by Snit 🫢. This whole section goes deep into what Snit actually requires, so here's the short version first, for reference:

  • If the widget's default widget class isn't what you want, set it explicitly with the widgetclass statement in the widget definition. ✨
  • When you're defining or delegating options, specify the resource and class names explicitly when it matters.
  • Use the installhull using command to create and install the hull for snit::widgetadaptors.
  • Use the install command to create and install all components which are widgets.
  • Use the install command to create and install components that aren't widgets too, if you'd like them to receive option values from the option database.

The interaction of Tk widgets with the option database is a complex thing; the interaction of Snit with the option database is even more so, and it absolutely repays attention to detail πŸ‘‘.


DO SNIT::TYPES USE THE TK OPTION DATABASE?

Nope, they do not πŸ’€ β€” querying the option database needs a Tk window name, and snit::types don't have one.

If you create an instance of a snit::type as a component of a snit::widget or snit::widgetadaptor, though? And if any options are delegated to that component? And if you use install to create and install it? Then the megawidget will query the option database on the snit::type's behalf. That might or might not be what you actually want, so take care πŸ™ˆ.


WHAT IS MY SNIT::WIDGET'S WIDGET CLASS?

Every Tk widget has a "widget class" β€” a name that gets used when adding option settings to the database. For Tk widgets, the widget class is the same as the widget command name, just with an initial capital. For example, the widget class of the Tk button widget is Button πŸ’….

Same energy for snit::widget: its widget class defaults to the unqualified type name with the first letter capitalized. So for example, the widget class of

snit::widget ::mylibrary::scrolledText { ... }

is ScrolledText ✨.

You can also set the widget class explicitly with the widgetclass statement inside the snit::widget definition:

snit::widget ::mylibrary::scrolledText {
    widgetclass Text

    # ...
}

That definition says a scrolledText megawidget has the same widget class as an ordinary text widget. Whether that's a good idea is... a vibe check you gotta do yourself 🫠 β€” it depends on how the rest of the megawidget is defined and how its options are delegated.


WHAT IS MY SNIT::WIDGETADAPTOR'S WIDGET CLASS?

The widget class of a snit::widgetadaptor is just the widget class of its hull widget. Snit has no control over this, periodt.

Heads up: the widget class can only be changed for frame and toplevel widgets β€” which is exactly why those are the valid hull types for snit::widgets.

Try to use snit::widgetadaptors only for making small modifications to another widget's behavior 🌸. If you do that, it usually won't make sense to change the widget's widget class anyway.


WHAT ARE OPTION RESOURCE AND CLASS NAMES?

Every Tk widget option has three names: the option name, the resource name, and the class name. The option name starts with a hyphen and is all lowercase; it's what you use when creating widgets, and with the configure and cget commands.

The resource and class names are used to initialize option default values by querying the option database πŸ“š. The resource name is usually just the option name minus the hyphen, but it may contain uppercase letters at word boundaries. The class name is usually just the resource name with an initial capital β€” but not always πŸ‘€. For example, here are the option, resource, and class names for several Tk text widget options:

-background         background         Background
-borderwidth        borderWidth        BorderWidth
-insertborderwidth  insertBorderWidth  BorderWidth
-padx               padX               Pad

See how sometimes you can infer the resource and class names from the option name β€” but not always? Yeah. That's the whole point 😭.


WHAT ARE THE RESOURCE AND CLASS NAMES FOR MY MEGAWIDGET'S OPTIONS?

For options implicitly delegated to a component using delegate option *, the resource and class names will be exactly those defined by the component. The configure method hands back these names, along with the option's default and current values:

% snit::widget mytext {
    delegate option * to text

    constructor {args} {
        install text using text .text
        # ...
    }

    # ...
}
::mytext
% mytext .text
.text
% .text configure -padx
-padx padX Pad 1 1
%

For all other options β€” whether locally defined or explicitly delegated β€” the resource and class names can be defined explicitly, or they can just be left to their default values πŸ’–.

By default, the resource name is just the option name minus the hyphen, and the class name is just the option name with an initial capital letter. For example, say we explicitly delegate -padx:

% snit::widget mytext {
    option -myvalue 5

    delegate option -padx to text
    delegate option * to text

    constructor {args} {
        install text using text .text
        # ...
    }

    # ...
}
::mytext
% mytext .text
.text
% .text configure -myvalue
-myvalue myvalue Myvalue 5 5
% .text configure -padx
-padx padx Padx 1 1
%

Here the resource and class names got chosen using the default rules. Often those rules are enough β€” but for -padx we'd most likely prefer the option's resource and class names match the built-in Tk widgets'. And that's easy peasy ✨:

% snit::widget mytext {
    delegate option {-padx padX Pad} to text

    # ...
}
::mytext
% mytext .text
.text
% .text configure -padx
-padx padX Pad 1 1
%

HOW DOES SNIT INITIALIZE MY MEGAWIDGET'S LOCALLY-DEFINED OPTIONS?

The option database gets queried for each of the megawidget's locally-defined options, using the option's resource and class name πŸ‘‘. If the result isn't "", then it replaces the default value given in the widget definition. Either way, the caller can still override the default. For example:

option add *Mywidget.texture pebbled

snit::widget mywidget {
    option -texture smooth
    # ...
}

mywidget .mywidget -texture greasy

Here, -texture would normally default to "smooth", but because of that entry added to the option database, it defaults to "pebbled" instead πŸͺ¨. However β€” the caller explicitly overrode the default, so the new widget ends up "greasy" 🍟.


HOW DOES SNIT INITIALIZE DELEGATED OPTIONS?

It depends πŸ’… β€” specifically, on whether the options are delegated to the hull, or to some other component.


HOW DOES SNIT INITIALIZE OPTIONS DELEGATED TO THE HULL?

A snit::widget's hull is a widget, and since its class has been set, it's expected to query the option database for itself. The only exception is options that are delegated to it with a different name. Consider this code:

option add *Mywidget.borderWidth 5
option add *Mywidget.relief sunken
option add *Mywidget.hullbackground red
option add *Mywidget.background green

snit::widget mywidget {
    delegate option -borderwidth to hull
    delegate option -hullbackground to hull as -background
    delegate option * to hull
    # ...
}

mywidget .mywidget

set A [.mywidget cget -relief]
set B [.mywidget cget -hullbackground]
set C [.mywidget cget -background]
set D [.mywidget cget -borderwidth]

So the question is... what are the values of variables A, B, C and D? πŸ‘€

The value of A is "sunken". The hull is a Tk frame that's been given the widget class Mywidget; it automatically queries the option database and picks up this value. And since the -relief option is implicitly delegated to the hull, Snit takes no action at all.

The value of B is "red". The hull will automatically pick up the value "green" for its -background option, just like it picked up the -relief value. But Snit knows that -hullbackground is mapped to the hull's -background option β€” so it queries the option database for -hullbackground, gets "red", and updates the hull accordingly πŸŽ€.

The value of C is also "red", because -background is implicitly delegated to the hull; so retrieving it is the same as retrieving -hullbackground. Note that this case is unusual πŸ‘€ β€” the -background option should probably have been excluded using the delegate statement's except clause, or (more likely) delegated to some other component.

The value of D is "5" β€” but not for the reason you think πŸ’€. See, as it's defined above, the resource name for -borderwidth defaults to borderwidth, whereas the option database entry is borderWidth, matching the standard Tk naming for this option. Just like with -relief, the hull picks up its own -borderwidth option before Snit does anything. And because the option is delegated under its own name, Snit assumes the correct thing has happened and doesn't worry about it any further. To avoid confusion, the -borderwidth option should've been delegated like this:

delegate option {-borderwidth borderWidth BorderWidth} to hull

For snit::widgetadaptors, the case is somewhat altered πŸ”„. Widget adaptors retain the widget class of their hull, and the hull is not created automatically by Snit. Instead, the snit::widgetadaptor must call installhull in its constructor. The normal way to do this is:

snit::widgetadaptor mywidget {
    # ...
    constructor {args} {
        # ...
        installhull using text -foreground white
        # ...
    }
    # ...
}

In this case, the installhull command will create the hull using a command like this:

set hull [text $win -foreground white]

The hull is a text widget, so its widget class is Text. Just like with snit::widget hulls, Snit assumes it's gonna pick up all of its normal option values automatically, no help from Snit needed πŸ’…. Options delegated from a different name get initialized from the option database the exact same way as described above.

In earlier versions of Snit, snit::widgetadaptors were expected to call installhull like this:

installhull [text $win -foreground white]

This form still works β€” but bestie, Snit will not query the option database as described above. 🫠

HOW DOES SNIT INITIALIZE OPTIONS DELEGATED TO OTHER COMPONENTS? 🧐

For hull components, Snit assumes Tk is gonna do most of the work automatically. Non-hull components are a lil more complicated, because they get matched against the option database twice. 😭

A component widget remains a widget still, so it gets initialized from the option database in the usual way. A text widget remains a text widget whether it's a component of a megawidget or not, and will be created as such. Periodt.

But then, the option database is queried for all options delegated to the component, and the component is initialized accordingly β€” provided that the install command is used to create it. ✨

Before option database support was added to Snit, the usual way to create a component was to just create it in the constructor and assign its command name to the component variable:

snit::widget mywidget {
    delegate option -background to myComp

    constructor {args} {
        set myComp [text $win.text -foreground black]
    }
}

The drawback of this method is that Snit has no opportunity to initialize the component properly. So, the following approach is now used:

snit::widget mywidget {
    delegate option -background to myComp

    constructor {args} {
        install myComp using text $win.text -foreground black
    }
}

The install command does the following:

  • Builds a list of the options explicitly included in the install command β€” in this case, -foreground.
  • Queries the option database for all options delegated explicitly to the named component.
  • Creates the component using the specified command, after inserting into it a list of options and values read from the option database. Thus, the explicitly included options (like -foreground) will override anything read from the option database.
  • If the widget definition implicitly delegated options to the component using delegate option *, then Snit calls the newly created component's configure method to receive a list of all of the component's options. From this Snit builds a list of options implicitly delegated to the component which were not explicitly included in the install command. For all such options, Snit queries the option database and configures the component accordingly.

You don't really need to know all of this β€” just use install to install your components, and Snit will try to do the right thing. 🫢

WHAT HAPPENS IF I INSTALL A NON-WIDGET AS A COMPONENT OF WIDGET? πŸ’€

A snit::type never queries the option database. However, a snit::widget can have non-widget components. And if options are delegated to those components, and if the install command is used to install those components, then they will be initialized from the option database just as widget components are. πŸ‘‘

But when used within a megawidget, install assumes that the created component uses a reasonably standard widget-like creation syntax. If it doesn't, don't use install. 🚫


πŸŽ€ ENSEMBLE COMMANDS πŸŽ€

WHAT IS AN ENSEMBLE COMMAND?

An ensemble command is a command with subcommands. Snit objects are all ensemble commands; however, the term more usually refers to commands like the standard Tcl commands string, file, and clock. In a sense, these are singleton objects β€” there's only one instance of them. 🌟

HOW CAN I CREATE AN ENSEMBLE COMMAND USING SNIT?

There are two ways β€” as a snit::type, or as an instance of a snit::type. Two paths, pick your vibe. 🌈

HOW CAN I CREATE AN ENSEMBLE COMMAND USING AN INSTANCE OF A SNIT::TYPE?

Define a type whose INSTANCE METHODS are the subcommands of your ensemble command. Then, create an instance of the type with the desired name.

For example, the following code uses DELEGATION to create a work-alike for the standard string command:

snit::type ::mynamespace::mystringtype {
    delegate method * to stringhandler

    constructor {} {
        set stringhandler string
    }
}

::mynamespace::mystringtype mystring

We create the type in a namespace, so that the type command is hidden; then we create a single instance with the desired name β€” mystring, in this case.

This method has two drawbacks. First, it leaves the type command floating about. More seriously, your shiny new ensemble command will have info and destroy subcommands that you probably have no use for. But read on. πŸ‘€

HOW CAN I CREATE AN ENSEMBLE COMMAND USING A SNIT::TYPE?

Define a type whose TYPE METHODS are the subcommands of your ensemble command.

For example, the following code uses DELEGATION to create a work-alike for the standard string command:

snit::type mystring {
    delegate typemethod * to stringhandler

    typeconstructor {
        set stringhandler string
    }
}

Now the type command itself is your ensemble command. πŸ’…

This method has only one drawback, and though it's major, it's also surmountable. Your new ensemble command will have create, info and destroy subcommands you don't want. And worse yet, since the create method can be implicit, users of your command will accidentally be creating instances of your mystring type if they should mispell one of the subcommands. The command will succeed β€” the first time β€” but won't do what's wanted. This is very bad. 😭

The work around is to set some PRAGMAS, as shown here:

snit::type mystring {
    pragma -hastypeinfo    no
    pragma -hastypedestroy no
    pragma -hasinstances   no

    delegate typemethod * to stringhandler

    typeconstructor {
        set stringhandler string
    }
}

Here we've used the pragma statement to tell Snit that we don't want the info typemethod or the destroy typemethod, and that our type has no instances; this eliminates the create typemethod and all related code. As a result, our ensemble command will be well-behaved, with no unexpected subcommands. Iconic. ✨


πŸ¦‹ PRAGMAS πŸ¦‹

WHAT IS A PRAGMA?

A pragma is an option you can set in your type definitions that affects how the type is defined and how it works once it is defined. It's giving ~settings for your settings~. πŸŽ€

HOW DO I SET A PRAGMA?

Use the pragma statement. Each pragma is an option with a value; each time you use the pragma statement you can set one or more of them.

HOW CAN I GET RID OF THE info TYPE METHOD?

Set the -hastypeinfo pragma to no:

snit::type dog {
    pragma -hastypeinfo no
    # ...
}

Snit will refrain from defining the info type method. Gone. Bye. πŸ‘‹

HOW CAN I GET RID OF THE destroy TYPE METHOD?

Set the -hastypedestroy pragma to no:

snit::type dog {
    pragma -hastypedestroy no
    # ...
}

Snit will refrain from defining the destroy type method. ✨

HOW CAN I GET RID OF THE create TYPE METHOD?

Set the -hasinstances pragma to no:

snit::type dog {
    pragma -hasinstances no
    # ...
}

Snit will refrain from defining the create type method; if you call the type command with an unknown method name, you'll get an error instead of a new instance of the type.

This is useful if you wish to use a snit::type to define an ensemble command rather than a type with instances. 🫢

Pragmas -hastypemethods and -hasinstances cannot both be false (or there'd be nothing left). 😭

HOW CAN I GET RID OF TYPE METHODS ALTOGETHER?

Normal Tk widget type commands don't have subcommands; all they do is create widgets β€” in Snit terms, the type command calls the create type method directly. To get the same behavior from Snit, set the -hastypemethods pragma to no:

snit::type dog {
    pragma -hastypemethods no
    #...
}

# Creates ::spot
dog spot

# Tries to create an instance called ::create
dog create spot
Pragmas -hastypemethods and -hasinstances cannot both be false (or there'd be nothing left). 😭

WHY CAN'T I CREATE AN OBJECT THAT REPLACES AN OLD OBJECT WITH THE SAME NAME?

Up until Snit 0.95, you could use any name for an instance of a snit::type, even if the name was already in use by some other object or command. You could do the following, for example:

snit::type dog { ... }

dog proc

You now have a new dog named "proc", which is probably not something that you really wanted to do. πŸ™ˆ As a result, Snit now throws an error if your chosen instance name names an existing command. To restore the old behavior, set the -canreplace pragma to yes:

snit::type dog {
    pragma -canreplace yes
    # ...
}

HOW CAN I MAKE MY SIMPLE TYPE RUN FASTER? 🏎️

In Snit 1.x, you can set the -simpledispatch pragma to yes.

Snit 1.x method dispatch is both flexible and fast, but the flexibility comes with a price. If your type doesn't require the flexibility, the -simpledispatch pragma allows you to substitute a simpler dispatch mechanism that runs quite a bit faster. The limitations are these:

  • Methods cannot be delegated.
  • uplevel and upvar do not work as expected: the caller's scope is two levels up rather than one.
  • The option-handling methods (cget, configure, and configurelist) are very slightly slower.

In Snit 2.2, the -simpledispatch macro is obsolete, and ignored; all Snit 2.2 method dispatch is faster than Snit 1.x's -simpledispatch. πŸ”₯

🌸 MACROS 🌸

✨ WHAT IS A MACRO? ✨

Okay so bestie, a Snit macro is literally nothing more than a Tcl proc that's defined in the Tcl interpreter used to compile Snit type definitions. That's it. That's the whole thing. πŸ’…


✨ WHAT ARE MACROS GOOD FOR? ✨

Two iconic jobs, no cap:

  • You can use Snit macros to define new type definition syntax.
  • You can use them to support conditional compilation.

✨ HOW DO I DO CONDITIONAL COMPILATION? ✨

So say you want your type to use a fast C extension if it's available; otherwise you'll fallback to a slower Tcl implementation. You want one set of methods in the first case and another set in the second case. But like… how does your type definition even know whether the fast C extension is available or not? πŸ€”

It's easily done, bestie. Outside of any type definition, define a macro that returns 1 if the extension is available, and 0 otherwise:

if {$gotFastExtension} {
    snit::macro fastcode {} {return 1}
} else {
    snit::macro fastcode {} {return 0}
}

Then use your macro in your type definition:

snit::type dog {

    if {[fastcode]} {
        # Fast methods
        method bark {} {...}
        method wagtail {} {...}
    } else {
        # Slow methods
        method bark {} {...}
        method wagtail {} {...}
    }
}

See how that just works? Obsessed. πŸ’–


✨ HOW DO I DEFINE NEW TYPE DEFINITION SYNTAX? ✨

Use a macro, obviously. πŸ’… For example, your snit::widget's -background option should be propagated to a number of component widgets. You could implement that like this:

snit::widget mywidget {
    option -background -default white -configuremethod PropagateBackground

    method PropagateBackground {option value} {
        $comp1 configure $option $value
        $comp2 configure $option $value
        $comp3 configure $option $value
    }
}

For one option, this is fine. But if you've got a number of options, it becomes tedious and error prone. 😭 So package it as a macro:

snit::macro propagate {option "to" components} {
    option $option -configuremethod Propagate$option

    set body "\n"

    foreach comp $components {
        append body "\$$comp configure $option \$value\n"
    }

    method Propagate$option {option value} $body
}

Then you can use it like this:

snit::widget mywidget {
    option -background default -white
    option -foreground default -black

    propagate -background to {comp1 comp2 comp3}
    propagate -foreground to {comp1 comp2 comp3}
}

Ate. No crumbs. ✨


✨ ARE THERE ARE RESTRICTIONS ON MACRO NAMES? ✨

Yes bestie, there are. 🫢

You can't redefine any standard Tcl commands or Snit type definition statements. You can use any other command name, though β€” including the name of a previously defined macro.

If you're using Snit macros in your application, go ahead and name them in the global namespace, as shown above. But if you're using them to define types or widgets for use by others, you should define your macros in the same namespace as your types or widgets. That way, they won't conflict with other people's macros. So considerate. πŸ‘‘

If my fancy snit::widget is called ::mylib::mywidget, for example, then I should define my propagate macro as ::mylib::propagate:

snit::macro mylib::propagate {option "to" components} { ... }

snit::widget ::mylib::mywidget {
    option -background default -white
    option -foreground default -black

    mylib::propagate -background to {comp1 comp2 comp3}
    mylib::propagate -foreground to {comp1 comp2 comp3}
}

😭 BUGS, IDEAS, FEEDBACK 😭

Okay so real talk: this document, and the package it describes, will undoubtedly contain bugs and other problems. Undoubtedly. It's giving "we know, we know". πŸ™ˆ

Please report such in the category snit of the Tcllib Trackers at http://core.tcl.tk/tcllib/reportlist. Please also report any ideas for enhancements you may have for either package and/or documentation. We wanna hear it, fr fr. πŸ’œ

When proposing code changes, please provide unified diffs, i.e the output of diff -u.

Also note further that attachments are strongly preferred over inlined patches. Attachments can be made by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar. πŸŽ€

⭐ KEYWORDS ⭐

BWidget, C++, Incr Tcl, adaptors, class, mega widget, object, object oriented, widget, widget adaptors


🌈 CATEGORY 🌈

Programming tools


Copyright (c) 2003-2006, by William H. Duquette


And that's a wrap on the whole page, bestie! πŸŽ‰ This was tcllib version 2.2 of snitfaq(3tcl), and you made it all the way to the end β€” main character energy, honestly. Now go forth and build something iconic with Snit. Slay, periodt. πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from