Author: Harald Oehlmann <[email protected]>
Author: Jan Nijtmans <[email protected]>
Author: Sergey G. Brester <[email protected]>
State: Draft
Type: Project
Vote: Pending
Created: 13-03-2024
Tcl-Version: 9.1
Tcl-Branch: tip-689
Vote-Summary:
Votes-For:
Votes-Against:
Votes-Present:
Abstract
TIP 181 introduced namespace unknown to call the unknown handler of the caller namespace. This TIP proposes to call the unknown handler of the namespace, where the command could not be resolved.
Rationale
A namespace assembles all commands of a package. The purpose of a procedure registered by namespace unknown is to to dynamically handle procs in a namespace which do not exist on initial setup. One application is auto loading on first use but other dynamic functions are possible. Currently, this only works, if called from the own namespace. If called from another namespace, the unknown handler of the other namespace is called.
This makes this feature unusable, as it is designed to be called from the outside.
Example:
namespace eval ::t1 { namespace unknown ::u1 }
proc ::u1 args {puts "u1 $args"}
namespace eval ::t2 { namespace unknown ::u2 }
proc ::u2 args {puts "u2 $args"}
% namespace eval ::t1 {::t1::test}
u1 ::t1::test
% namespace eval ::t1 {::t2::test}
u1 ::t2::test
As a demonstration, the TIP implementation also supplies a new ::tcl::clock
unknown handler, replacing the old tclIndex
method for the clock
command.
Specification
namespace unknown should call the unknown function of the namespace where the proc was not found.
This changes the upper example to:
% namespace eval ::t1 {::t2::test}
u2 ::t2::test
Details
namespace unknown will be invoked independently to the current namespace of current frame. In opposite to TIP 181 even for this call:
::A::B::C::D::cmd
if unknown handler is set for any of that namespaces.The precedence of handler invocation: deepest NS with unknown-handler always wins (regardless from where exactly it was executed), thereby absolute namspace paths over relative paths. For the case of relative command name, firstly the current namespace and all parents will be inspected, hereafter if no one handler found, the affected namespaces relative global namespace, at end the global ::unknown handler.
For instance, for this call:namespace eval ::A::B { C::D::cmd }
the order of search for namespace unknown handler will be:
::A::B::C::D ::A::B::C ::A::B ::A ::C::D ::C ::
The handler always get an originally supplied command name, which corresponds the command relative the caller frame (and therefore like by global ::unknown, relative to
[uplevel {namespace current}]
).% namespace eval ::A::B::C { cmd } = cmd % namespace eval ::A::B { C::cmd } = C::cmd % namespace eval ::A { B::C::cmd } = B::C::cmd % ::A::B::C::cmd = ::A::B::C::cmd
To obtain real normalized command name inside the handler, one could use something like this (e. g. with auto_qualify like global ::unknown does):
# fully-qualified command name: lindex [auto_qualify $cmd [uplevel {namespace current}]] 0 # fully-qualified command name (without auto_qualify): regsub -all {(::){2}} [uplevel {namespace current}]::$cmd {::} # relative command name: regsub {^(::){1,2}foo::bar(::){1,2}} [uplevel {namespace current}]::$cmd {}
Suggestion (not a part of this TIP, just as nice to have):
For the last (to obtain normalized relative name) one could extend command namespace tail like this:% namespace tail namespace tail string ?relative? % namespace tail ::foo::bar::xxx::yyy ::foo::bar xxx::yyy
Like in original implementation of TIP 181, only the first found unknown handler will be invoked. If logic of code expects to call unknown of near parent namespace instead, the handler must do that inside itself, for example using tailcall (to unfold recursion).
There is a small compatibility issue possibly, because of the precedence matter.
For instance:
in case of 2 nested namespaces with handlers (::A::B
and::A::B::C
), for the call:namespace eval ::A::B { C::cmd }
it'd invoke handler of
::A::B::C
now, where previously it'd rather invoke handler of::A::B
(since another was not implemented yet).
Although a negative impact is hardly believable, because previously it was rather unexpected behaviour.
Counterpoint
The rationale above provides no real rationale. It merely illustrates the behaviour of the namespace unknown handler without explaining what problem it might pose. The current behaviour is useful. TIP 181 explains that the namespace unknown handler is intended for use by scripts that run in the calling namespace to react to a missing procedure so that whatever is implemented in that calling namespace can reconfigure itself accordingly. This TIP would repurpose the namespace unknown handler, and this original functionality would then be lost.
A separate mechanism already exists for providing a dynamic interface to a namespace: The unknown handler for a [namespace ensemble]. this example illustrates how just a few lines of new code provide the desired dynamic interface for ::tcl::clock.
The bigger problem with this tip is the specification: "namespace unknown should call the unknown function of the namespace where the proc was not found." Given that a namespace path may specify a number of namespaces in which to look, there is not necessarily a single "namespace where the proc was not found". In addition to not specifying how this proposal would interact with a namespace path, this TIP incorrectly assumes that the lookup path for a procedure follows the parent namespaces of a nested namespace, which makes the whole TIP a non-starter.
History
This TIP started as a bug ticket, not realizing that there was already a TIP describing the current behavior.
Counterpoint provided by Poor Yorick.
Implementation
Implementation is in TCL branch "tip-689".
Copyright
This document has been placed in the public domain.