在 `array` 集合中创建新命令时出现问题
Problem creating new command in `array` ensemble
我想创建一个方便的命令 array values arrayName
作为 "flip side" 到 "array names" 命令。
创建一个简单的过程很简单:
proc array_values {arrayName} {
upvar 1 $arrayName ary
set values {}
foreach {name value} [array get ary] {lappend values $value}
return $values
}
array set a {foo bar baz qux}
puts [array_values a] ;# => bar qux
但是,我在 ::tcl::array
命名空间中创建命令时遇到困难:
先做点功课:
是 array
命名空间集合吗?是的
% namespace ensemble exists array
1
命名空间是什么?
% namespace ensemble configure array -namespace
::tcl::array
子命令是什么?
% namespace ensemble configure array -subcommands
% namespace ensemble configure array -map
anymore ::tcl::array::anymore donesearch ::tcl::array::donesearch exists ::tcl::array::exists get ::tcl::array::get names ::tcl::array::names nextelement ::tcl::array::nextelement set ::tcl::array::set size ::tcl::array::size startsearch ::tcl::array::startsearch statistics ::tcl::array::statistics unset ::tcl::array::unset
好的,一切都很好。让我们将 array_values
proc 添加到命名空间
% namespace eval ::tcl::array {
proc values {arrayName} {
upvar 1 $arrayName ary
set values {}
foreach {name value} [array get ary] {lappend values $value}
return $values
}
}
% array set a {foo bar baz qux}
% puts [::tcl::array::values a]
can't set "values": variable is array
这个错误是从哪里来的?我尝试将 proc 中的 "values" 变量重命名为其他名称,但它仍然发出 "variable is array" 错误。
注意:我可以将第一个过程添加到合奏中:
% namespace ensemble config array -map [list values ::array_values {*}[namespace ensemble config array -map]]
% array values a
bar qux
但是我的 ::tcl::array::values
过程出了什么问题?
您的 set values {}
命令在 ::tcl::array 命名空间中执行,因此它运行 ::tcl::array::set 命令。换句话说,它相当于 array set values {}
。所以它使 values 成为一个没有成员的数组。然后 lappend values $value
命令失败,因为此时值是一个数组。
解决方案应该是使用::set values {}
或者您可以使用以下方法完全避免此问题:
proc array_values {arrayName} {
upvar 1 $arrayName ary
return [lmap {name value} [get ary] {string cat $value}]
}
我想补充一点,鉴于可能存在冲突的集成命令是一个移动的目标,修补集成很可能随处发生,我已经看到核心开发人员在 ::tcl::array::*
命名空间:
proc arrayValues {arrayName} {
upvar 1 $arrayName ary
set values {}
foreach {name value} [array get ary] {lappend values $value}
return $values
}
# implant "arrayValues" into [array] ensemble as "values"
namespace ensemble configure ::array -map \
[dict replace [namespace ensemble configure ::array -map] \
values [namespace which arrayValues]]
这样一来,您就不必担心意外的解析冲突(首先在 Tcl 中意味着什么)。
出于好奇,这就是我最终得到的结果:
$HOME/tcl/lib/monkeypatches/monkeypatches.tcl
# a set of useful additions to built-in ensembles
package provide monkeypatches 0.1
namespace eval ::monkeypatches {
# https://wiki.tcl-lang.org/page/wrapping+commands
proc append_subcommand {cmd subcmd procname} {
set map [namespace ensemble configure $cmd -map]
dict set map $subcmd [namespace which $procname]
namespace ensemble configure $cmd -map $map
}
# array foreach
# to be subsumed by https://core.tcl.tk/tips/doc/trunk/tip/421.md
#
# example:
# array set A {foo bar baz qux}
# array foreach {key val} A {puts "name=$key, value=$val"}
#
proc array_foreach {vars arrayName body} {
if {[llength $vars] != 2} {
error {array foreach: "vars" must be a 2 element list}
}
lassign $vars keyVar valueVar
# Using the complicated `upvar 1 $arrayName $arrayName` so that any
# error messages propagate up with the user's array name
upvar 1 $arrayName $arrayName \
$keyVar key \
$valueVar value
set sid [array startsearch $arrayName]
# If the array is modified while a search is ongoing, the searchID will
# be invalidated: wrap the commands that use $sid in a try block.
try {
while {[array anymore $arrayName $sid]} {
set key [array nextelement $arrayName $sid]
set value [set "${arrayName}($key)"]
uplevel 1 $body
}
array donesearch $arrayName $sid
} trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
return -options $e "detected attempt to modify the array while iterating"
}
return
}
append_subcommand ::array foreach array_foreach
# array values arrayName
#
#
# example:
# array set x {foo bar baz qux}
# array get x ;# => foo bar baz qux
# array names x ;# => foo baz
# array values x ;# => bar qux
#
proc array_values {arrayName} {
upvar 1 $arrayName ary
set values [list]
array foreach {name value} ary {lappend values $value}
return $values
}
append_subcommand ::array values array_values
# info formalargs procName
# https://core.tcl.tk/tips/doc/trunk/tip/65.md
#
# example:
# proc test {one {two 2} {three {3 4 5}} args} {return}
# info args test ;# => one two three args
# info formalargs test ;# => one {two 2} {three {3 4 5}} args
#
proc info_formalargs {procname} {
# [info args] throws an error if $procname is not a procedure.
return [lmap arg [info args $procname] {
set has_d [info default $procname $arg value]
if {$has_d} then {list $arg $value} else {set arg}
}]
}
append_subcommand ::info formalargs info_formalargs
}
与其关联pkgIndex.tcl
和$HOME/.tclshrc
set lib_dir [file join $env(HOME) tcl lib]
if {$lib_dir ni $auto_path} {lappend auto_path $lib_dir}
unset lib_dir
package require monkeypatches
我想创建一个方便的命令 array values arrayName
作为 "flip side" 到 "array names" 命令。
创建一个简单的过程很简单:
proc array_values {arrayName} {
upvar 1 $arrayName ary
set values {}
foreach {name value} [array get ary] {lappend values $value}
return $values
}
array set a {foo bar baz qux}
puts [array_values a] ;# => bar qux
但是,我在 ::tcl::array
命名空间中创建命令时遇到困难:
先做点功课:
是
array
命名空间集合吗?是的% namespace ensemble exists array 1
命名空间是什么?
% namespace ensemble configure array -namespace ::tcl::array
子命令是什么?
% namespace ensemble configure array -subcommands % namespace ensemble configure array -map anymore ::tcl::array::anymore donesearch ::tcl::array::donesearch exists ::tcl::array::exists get ::tcl::array::get names ::tcl::array::names nextelement ::tcl::array::nextelement set ::tcl::array::set size ::tcl::array::size startsearch ::tcl::array::startsearch statistics ::tcl::array::statistics unset ::tcl::array::unset
好的,一切都很好。让我们将 array_values
proc 添加到命名空间
% namespace eval ::tcl::array {
proc values {arrayName} {
upvar 1 $arrayName ary
set values {}
foreach {name value} [array get ary] {lappend values $value}
return $values
}
}
% array set a {foo bar baz qux}
% puts [::tcl::array::values a]
can't set "values": variable is array
这个错误是从哪里来的?我尝试将 proc 中的 "values" 变量重命名为其他名称,但它仍然发出 "variable is array" 错误。
注意:我可以将第一个过程添加到合奏中:
% namespace ensemble config array -map [list values ::array_values {*}[namespace ensemble config array -map]]
% array values a
bar qux
但是我的 ::tcl::array::values
过程出了什么问题?
您的 set values {}
命令在 ::tcl::array 命名空间中执行,因此它运行 ::tcl::array::set 命令。换句话说,它相当于 array set values {}
。所以它使 values 成为一个没有成员的数组。然后 lappend values $value
命令失败,因为此时值是一个数组。
解决方案应该是使用::set values {}
或者您可以使用以下方法完全避免此问题:
proc array_values {arrayName} {
upvar 1 $arrayName ary
return [lmap {name value} [get ary] {string cat $value}]
}
我想补充一点,鉴于可能存在冲突的集成命令是一个移动的目标,修补集成很可能随处发生,我已经看到核心开发人员在 ::tcl::array::*
命名空间:
proc arrayValues {arrayName} {
upvar 1 $arrayName ary
set values {}
foreach {name value} [array get ary] {lappend values $value}
return $values
}
# implant "arrayValues" into [array] ensemble as "values"
namespace ensemble configure ::array -map \
[dict replace [namespace ensemble configure ::array -map] \
values [namespace which arrayValues]]
这样一来,您就不必担心意外的解析冲突(首先在 Tcl 中意味着什么)。
出于好奇,这就是我最终得到的结果:
$HOME/tcl/lib/monkeypatches/monkeypatches.tcl
# a set of useful additions to built-in ensembles
package provide monkeypatches 0.1
namespace eval ::monkeypatches {
# https://wiki.tcl-lang.org/page/wrapping+commands
proc append_subcommand {cmd subcmd procname} {
set map [namespace ensemble configure $cmd -map]
dict set map $subcmd [namespace which $procname]
namespace ensemble configure $cmd -map $map
}
# array foreach
# to be subsumed by https://core.tcl.tk/tips/doc/trunk/tip/421.md
#
# example:
# array set A {foo bar baz qux}
# array foreach {key val} A {puts "name=$key, value=$val"}
#
proc array_foreach {vars arrayName body} {
if {[llength $vars] != 2} {
error {array foreach: "vars" must be a 2 element list}
}
lassign $vars keyVar valueVar
# Using the complicated `upvar 1 $arrayName $arrayName` so that any
# error messages propagate up with the user's array name
upvar 1 $arrayName $arrayName \
$keyVar key \
$valueVar value
set sid [array startsearch $arrayName]
# If the array is modified while a search is ongoing, the searchID will
# be invalidated: wrap the commands that use $sid in a try block.
try {
while {[array anymore $arrayName $sid]} {
set key [array nextelement $arrayName $sid]
set value [set "${arrayName}($key)"]
uplevel 1 $body
}
array donesearch $arrayName $sid
} trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
return -options $e "detected attempt to modify the array while iterating"
}
return
}
append_subcommand ::array foreach array_foreach
# array values arrayName
#
#
# example:
# array set x {foo bar baz qux}
# array get x ;# => foo bar baz qux
# array names x ;# => foo baz
# array values x ;# => bar qux
#
proc array_values {arrayName} {
upvar 1 $arrayName ary
set values [list]
array foreach {name value} ary {lappend values $value}
return $values
}
append_subcommand ::array values array_values
# info formalargs procName
# https://core.tcl.tk/tips/doc/trunk/tip/65.md
#
# example:
# proc test {one {two 2} {three {3 4 5}} args} {return}
# info args test ;# => one two three args
# info formalargs test ;# => one {two 2} {three {3 4 5}} args
#
proc info_formalargs {procname} {
# [info args] throws an error if $procname is not a procedure.
return [lmap arg [info args $procname] {
set has_d [info default $procname $arg value]
if {$has_d} then {list $arg $value} else {set arg}
}]
}
append_subcommand ::info formalargs info_formalargs
}
与其关联pkgIndex.tcl
和$HOME/.tclshrc
set lib_dir [file join $env(HOME) tcl lib]
if {$lib_dir ni $auto_path} {lappend auto_path $lib_dir}
unset lib_dir
package require monkeypatches