向Tcl过程添加关键字参数
Adding keyword parameters to Tcl procs

原始链接: https://world-playground-deceit.net/blog/2025/04/adding-keyword-parameters-to-tcl-procs.html

作者痛惜某些编程语言缺乏关键字/命名参数以及内置功能的不可访问性。他们展示了一个Tcl解决方案`proc*`,它为Tcl的`proc`命令添加了可选的、命名的、且顺序无关的参数,模仿类UNIX的选项处理方式。这涉及到参数解析、强制参数与可选参数的处理以及动态代码生成。 作者强调了由于缺乏选择性字符串替换,Tcl元编程的复杂性。他们求助于一个基于字符串的模板系统,该系统通过自定义的`quasiquote`命令实现,严重依赖正则表达式和字符串操作。生成的代码被描述为令人反感且破坏了Emacs的Tcl模式缩进,突出了与Common Lisp (CL)等语言相比,Tcl元编程的痛点。该解决方案有效地扩展了Tcl的功能,但却依赖于一种脆弱且复杂的方法来生成代码。

Hacker News 最新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 向Tcl过程添加关键字参数 (world-playground-deceit.net) 8 分,作者 BoingBoomTschak,34 分钟前 | 隐藏 | 过去 | 收藏 | 讨论 加入我们,参加 6 月 16-17 日在旧金山举办的 AI 初创公司学校! 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系我们 搜索:
相关文章
  • Tcl 教程 2025-03-17
  • (评论) 2025-03-17
  • (评论) 2024-09-15
  • (评论) 2025-03-17
  • (评论) 2024-07-30

  • 原文

    Two things that really annoy me with some programming languages: the lack of keyword (optional, always named and order agnostic) parameters and when the language has a builtin feature not made available to the user.

    Tcl hits the nail here, because a lot of its standard commands have UNIX-like options but it doesn't make any of this available in either proc or apply.

    But since proc can be wrapped "easily" (more later), a few hours gave me this:

    source .../util.tcl
    namespace path {::util ::tcl::mathop ::tcl::mathfunc}
    
    proc* p {-flag {-opt foo} x args} {
        puts [lmap v {flag opt x args} {string cat "$v=[set $v]"}]
    }
    p 1 a;                p -opt bar -flag 1 a; p -flag;              
    proc  p1 {x} {}
    proc* p2 {-flag {-opt foo} x} {}
    timerate {p1 1}; timerate {p2 1}; 

    Pretty nice? As you can see in the third example, I even implemented that Tcl behaviour where mandatory positional parameter count is used to disambiguate them from options (which is how you can do puts $string without having to safeguard it with a -- marker).

    Addendum about the aforementioned "easily" to show how the sausage is made; definitely not for the faint of heart.

    proc proc* {name args body} {
        if {[llength $args] == 0 || ![string match "-*" [lindex $args 0]]} {
            proc $name $args $body
            return
        }
        while {[llength $args] && [string match "-*" [lindex $args 0]]} {
            set args [lassign $args[set args {}] arg]
            switch [llength $arg] {
                1 {                 lappend optargs [set var [string range $arg 1 end]]
                    lappend optinit 0
                    lappend optswitch $arg [list set $var 1]
                }
                2 {                 lassign $arg opt init
                    lappend optargs [set var [string range $opt 1 end]]
                    lappend optinit $init
                    lappend optswitch $opt "set [list $var] \[lindex \$args \[incr i]]"
                }
                default {error "$arg: unknown option format"}
            }
        }
        lappend optswitch default {error "$arg: unknown option"}
            set mcount 0
        foreach arg $args {
            if {[llength $arg] != 1 || $arg eq "args"} break
            incr mcount
        }
        tailcall proc $name args [quasiquote {
            lassign `$optinit `@$optargs
            for {set i 0; set end [expr {[llength $args] - `$mcount}]} {$i < $end} {incr i} {
                set arg [lindex $args $i]
                if {![string match "-*" $arg] || $arg eq "--"} break
                switch -- $arg `$optswitch
            }
            apply `[list [list {*}$optargs {*}$args] $body]` \
                `@[join [lmap o $optargs {string cat \$$o}]]` {*}[lrange $args $i end]
        }]
    }
    

    Not too bad, you might say. And I agree, some very basic preprocessing and code generation at play. No, the problem resides in that innocuous quasiquote command used to do the final generation.

    You see, Tcl has a pretty poor metaprogramming story as it lacks a way to selectively subst within strings, so what everybody does is… roll their own string based templating system! In fact, I don't think a hacker can wield Tcl for some time without making something like it (cf the wiki).

    In my case, here's the revolting but working result (ignore the ? and move helpers):

    proc quasiquote {script} {
            set tmp [string map {\\ \\\\ $ \\$ \[ \\\[} $script]
        set tmp [string map {`@\\$ $} [move tmp]]
        set tmp [regsub -all {`\\\$([[:alnum:]_:]+|\{[^\}]+\})} [move tmp] {[list $\1]}]
                for {set start 0} {[regexp -indices -start $start {`@?\\\[} $tmp ropener]} {} {
            lassign $ropener rstart ropener_end
            set rend [+ [string first {]`} $tmp [+ $ropener_end 1]] 1]
            set splice [== [- $ropener_end $rstart] 3]
            set repl [string map {\\\\ \\ \\$ $ \\\[ \[} \
                              [string range $tmp $ropener_end [- $rend 1]]]
            set tmp [string replace [move tmp] $rstart $rend [? {$splice} {$repl} {\[list $repl\]}]]
            set start [+ $rstart [string length $repl] 1]
        }
        tailcall subst $tmp
    }
    

    So horrible it breaks Emacs' (arguably fragile) tcl-mode indentation. I think there's only one valid reaction to the words "metaprogramming via regexp", especially coming from CL which inspired this:

    联系我们 contact @ memedata.com