4

嗨,我已经使用 tcl 脚本编写了将近一年,现在几乎完全了解它的基础知识。但是今天我刚刚遇到了嵌套程序,这有点奇怪,因为我没有使用它。

无论如何,我在这里阅读了有关嵌套 proc的信息,但没有清楚地了解我们为什么需要它。

这篇文章说,由于 proc 在命名空间中是全局的,所以要创建一个本地 proc,你需要创建嵌套的 proc。

    proc parent {} {
        proc child {} {
            puts "Inside child proc";
        }
        ...   
    }

现在我能想到的一种用法是

    proc parent {} {
        proc child {intVal} {
            puts "intVal is $intVal";
        }
        puts "[::child 10]";
        ... #some processing
        puts "[::child 20]";
        ... #some processing
        puts "[::child 30]";
        ... #some processing
        puts "[::child 40]";
        ... #some processing
        puts "[::child 50]";
        ... #some processing
    }

所以现在子进程是父进程本地的,只能在父进程内部使用。而且据我了解,当您想在该父进程内的多个位置进行相同的处理时,它很有用。

现在我的困惑是,这是嵌套 proc 的唯一用途还是还有什么我不明白的???。我的意思是嵌套过程看起来就像是一种私有过程。

因此,请对此有所了解并帮助我了解嵌套过程的使用。

4

2 回答 2

7

Tcl 没有嵌套过程。您可以proc在过程定义中调用,但这只是创建一个普通过程(用于解析要创建的过程名称的命名空间将是调用者的当前命名空间,如 报告的那样namespace current)。

为什么要放在proc里面proc?好吧,这样做的真正原因是当你想让外部命令充当工厂时,在调用它时创建命令。有时要创建的命令的名称将由调用者提供,有时它会在内部生成(在后一种情况下,返回创建的命令的名称是正常的)。出现的另一种情况是外部命令是(真实)内部命令的某种代理,允许推迟创建真实命令,因为它在某种方式上很昂贵;如果是这种情况,内部过程实际上将倾向于使用相同的名称创建作为外部的,它将替换它(尽管不是正在执行的堆栈帧;Tcl 对此很小心,否则会很疯狂)。

在您确实需要“内部过程”的情况下,实际上最好使用可以apply 代替的 lambda 术语。那是因为它是一个真正的值,可以存储在局部变量中,并且当外部过程终止时(或者如果您显式地更改unset变量或替换其内容),它将自动消失。此内部代码将无法访问外部代码的变量,除了 via upvar; 如果你想在绑定变量的同时返回值,你应该使用命令前缀并包含一些额外的技巧来将变量绑定为预先提供的参数:

proc multipliers {from to} {
    set result {}
    for {set i $from} {$i <= $to} {incr i} {
        lappend result [list apply {{i n} {
            return [expr {$i * $n}]
        }} $i]
    }
    return $result
}

set mults [multipliers 1 5]
foreach m $mults {
    puts [{*}$m 2.5]
}
# Prints (one per line): 2.5  5.0  7.5  10.0  12.5

使用内部proc模拟apply

请注意,该apply命令实际上可以由内部过程模拟。这是 Tcl 8.4 及之前使用的技术:

# Omitting error handling...
proc apply {lambdaTerm args} {
    foreach {arguments body namespace} $lambdaTerm break
    set cmd ${namespace}::___applyLambad
    proc $cmd $arguments $body
    set result [uplevel 1 [linsert 0 $args $cmd]]; # 8.4 syntax for safe expansion!
    rename $cmd ""
    return $result
}

这有点容易出错并且非常慢,因为它会在每次调用时重新编译;我们不再这样做了!

于 2013-06-20T19:34:16.847 回答
1

Tcl 没有嵌套的过程。从proc手册页:

通常, name 是不合格的(不包括任何包含名称空间的名称),并且在当前名称空间中创建新过程

(强调我的)

展示:

% namespace eval foo {
    proc parent {} {
        proc child {} {
            puts "the child"
        }
        puts "the parent, which holds [child]"
    }
}
% foo::parent
the child
the parent, which holds 
% foo::child
the child

我们仍然可以直接调用“内部”过程——它不是封闭过程的本地。

您在该 wiki 页面的讨论中遗漏的一项内容是,要使 proc 仅对封闭 proc 真正本地化,必须在封闭 proc 结束时将其删除:

% namespace eval foo {
    proc parent {} {
        proc child {} {
            puts "the child"
        }
        puts "the parent, which holds [child]"
        # now, destroy the inner proc
        rename child ""
    }
}
% foo::parent
the child
the parent, which holds 
% foo::child
invalid command name "foo::child"

至于本地 proc 的使用,我同意您的观点,即封装仅在当前 proc 中有用的重复任务是有益的。不过,我不会太在意这一点:清晰的文档或代码约定也可以。

于 2013-06-20T17:32:07.600 回答