代码之家  ›  专栏  ›  技术社区  ›  glenn jackman

在“array”集合中创建新命令时出现问题

tcl
  •  1
  • glenn jackman  · 技术社区  · 7 年前

    我想创建一个方便的命令 array values arrayName 作为“数组名称”命令的“反面”。

    创建一个简单的过程非常简单:

    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 名称空间:

    • 首先是一些家庭作业:

      1. array 名称空间集合?对

        % namespace ensemble exists array
        1
        
      2. 名称空间是什么?

        % namespace ensemble configure array -namespace
        ::tcl::array
        
      3. 子命令是什么?

        % 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
    


    % namespace ensemble config array -map [list values ::array_values {*}[namespace ensemble config array -map]]
    % array values a
    bar qux
    

    但是我的车怎么了 ::tcl::array::values 程序?

    3 回复  |  直到 7 年前
        1
  •  3
  •   Schelte Bron    7 年前

    set values {} 命令在::tcl::array命名空间中执行,因此它运行::tcl::array::set命令。换句话说,它相当于 array set values {} . 因此,它使值成为一个没有成员的数组。然后 lappend values $value

    解决方法应该是使用 ::set values {}

    或者,您可以使用以下方法完全避免该问题:

    proc array_values {arrayName} {
        upvar 1 $arrayName ary
        return [lmap {name value} [get ary] {string cat $value}]
    }
    
        2
  •  1
  •   mrcalvin    7 年前

    ::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中意味着什么)。

        3
  •  0
  •   glenn jackman    7 年前

    对于好奇的人来说,这就是我的结局:

    $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
        # https://stackoverflow.com/q/53379995/7552
        #
        # 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
    }
    

    和$HOME/.tclshc

    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