在 tcl 中打破超过 1 级的 foreach 嵌套

break more than 1 level of foreach nesting in tcl

是否可以从两层嵌套中在 1 个命令中退出?也就是说,假设我有这个代码:

foreach l { 1 2 3 4 } {
   foreach k { 3 4 5 6 } {
      if { $k > 4 } {
         break2
      } else {
         puts "$k $l"
   }
}

我希望看到的输出是:

1 3
1 4

问题是,如何编写 break2(如果可能的话)?
除了将其包装在 proc 中之外,我不知道任何语言都有这样的 "feature",并且使用 return 停止进程,这比正确的语言结构更像是一种技巧
谢谢。

在 Tcl ≥ 8.5 中是可能的:

foreach l { 1 2 3 4 } {
    foreach k { 3 4 5 6 } {
        if {$k > 4} {
            return -code break -level 2
        } else {
            puts "$k $l"
        }
    }
}

return -code break -level 2 的工作方式类似于 "make the enclosing command two levels up the stack return as if it has called break"。

return command manual page.

直接做是不行的; break 机器除了最近的循环上下文之外没有任何东西可以追踪到任何东西。

处理此问题的最简单方法是在 8.6 中使用 try 和自定义异常代码(即 5 以上的任何值)。

foreach a {b c} {
    puts "a=$a; to show that this is not stopping the outermost loop"
    try {
        foreach l { 1 2 3 4 } {
            foreach k { 3 4 5 6 } {
                if { $k > 4 } {
                    # Generating a custom exception code is a bit messy
                    return -level 0 -code 5
                }
                puts "$k $l"
            }
        }
    } on 5 {} {
        # Do nothing here; we've broken out
    }
}

运行 给出了这个输出:

a=b; to show that this is not stopping the outermost loop
3 1
4 1
a=c; to show that this is not stopping the outermost loop
3 1
4 1

但是这样做很麻烦;最好的方法通常是重构您的代码,以便您通常可以 return 结束循环。使用 apply 可能会使这更容易:

foreach a {b c} {
    puts "a=$a; to show that this is not stopping the outermost loop"
    apply {{} {
        foreach l { 1 2 3 4 } {
            foreach k { 3 4 5 6 } {
                if { $k > 4 } {
                    return
                }
                puts "$k $l"
            }
        }
    }}
}

使用 apply 的缺点是它是一个不同的变量上下文并且有相当多的开销(因为所有堆栈帧管理)。不过,如果您小心的话,可以使用 upvar 来解决变量上下文的问题。