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::cmdTo 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::yyyLike 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::Band::A::B::C), for the call:namespace eval ::A::B { C::cmd }it'd invoke handler of
::A::B::Cnow, 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
(provided by Poor Yorick)
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.
Counterpoint to counterpoint
(provided by Sergey G. Brester aka sebres)
without explaining what problem it might pose
No. Everything is explained point by point and it also illustrate the sole problem in P.5 in details.
The current behaviour is useful.
The current behaviour remains and can be used as it used before, it will be finally extended so the unknown handler becomes also callable outside of the namespace. The attempt to justify the previous behavior with purely hypothetical problems shows a misunderstanding either of the TIP itself or of the command resolve of Tcl basically. And the problem looks contrived to me and it is really hypothetical, because it only presens for the commands having another namespace in the name, e. g. with some child namespace, which shall also have namespace unknown handler. And it is rather a precidence question, what could be solved also using some option.
This TIP would repurpose the namespace unknown handler, and this original functionality would then be lost.
Neither it repurposes it, nor any functionality of original TIP 181 will be lost.
The new TIP only extends original TIP based on rules valid for resolve of commands
in the namespaces. The only exception is described in P.5 above and it is rather pure
artificial example, because it'd behave as before if namespace ::A::B::C would not get
unknown handler at all, so it will still invoke the unknown handler of ::A::B.
However if this is so important (despite I cannot imagine any real example where it
may be needed), one could extend namespace unknown with some option that would
avoid full command name resolution and thus invoke another handler. But from this point
of view (or rather the common sense) it shall be then rather an option of namespace itself,
that would "bypass" the resolve inside the namespace or the namespace could get another
command resolver that would assign the command ::A::B::C::cmd to namespace ::A::B
instead of namespace ::A::B::C. This would be more logical than strange attempt to "separate"
the namespace unknown from command resolver standard behaviour.
But again I don't know any use case where it may be necessary.
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
Why? I mean why it is logical in your opinion that the command ::A::B::C::cmd is resolved
to be belonging to the namespace ::A::B::C, but the namespace unknown will be invoked
in current namespace, no matter what is it? What is the logic behind it? And which particular
use case shall be covered by this behaviour?
History
This TIP started as a bug ticket, not realizing that there was already a TIP describing the current behavior.
Implementation
Implementation is in TCL branch "tip-689".
Copyright
This document has been placed in the public domain.
