Tcl:K 技巧/取消共享对象也用于包含列表?
Tcl: K trick / unsharing objects also for embracing a list?
我想做的是在列表周围添加一层大括号:
set l [list $l]
对于
类型的操作
set someVar [someFunc $someVar]
推荐(, , https://wiki.tcl-lang.org/page/K)使用
set someVar [someFunc $someVar[set someVar {}]]
相反,它可以显着提高性能。
两个问题:
- 这是否也适用于
list
,所以我应该使用它来解决我的具体问题吗?:
set l [list $l[set l {}]]
- 是否有另一种有效的方法来添加额外级别的大括号?
感谢您的帮助!
编辑:更正了 Chris Heithoff 指出的错误
是的,应该可以。 list
只是 K.
推荐中替代 someFunc
的另一个可能的东西
你的列表具体例子不太正确。
set l [list $l[set $l {}]]
..你想给变量名l
分配一个空字符串,而不是$l
的值才真正受益。
改为这样做:
set l [list $l[set l {}]]
这是对为什么您不需要 K 技巧的解释。
Tcl 的值通常是引用计数的复合值,可以保证序列化为字符串;这通常是隐藏的信息,但事情确实如此。事实上,我们的一些字符串在某些时候也有复杂的内部表示,这使得 Tcl 的字符串在大多数时候比 C、C++ 或 Java 中的字符串快很多。
这是一个模型,您必须复制一个值才能对其进行修改。但我们实际上并没有这样做;如果你必须一直复制东西,那就太慢了!相反,我们只在写入 shared 值时获取副本;该值是浅重复数据删除的,更新应用于新副本,然后在应该使用的地方使用它(例如,写回变量)。这是根据需要以递归方式完成的,因此当您在嵌套列表或字典的深处进行修改时,正确的事情就会发生。
什么时候共享价值观?好吧,有几个地方可以保存引用(例如,列表和字典),但这里的关键是:
- 变量。 (呃!这是一个非常明显的!)
- Tcl 执行堆栈。这是 Tcl 保存替换、变量读取和命令的结果的地方,直到它可以分派给它接下来将要调用的命令。 理论上可以使用替代执行机制,但您仍然必须将引用保留相同的时间才能使执行模型正确。
那么,在 K 的经典案例中会发生什么?好吧,你从做 set x [linsert $x 0 a]
开始,这(忽略字节编码)会做:
- 推
set
.
- 推
x
.
- 推送
linsert
- 推送从
x
读取的值。 (注意这一点:它现在有 两个 引用,一个来自变量,一个来自堆栈)
- 推送
0
- 推送
a
- 调用(
linsert
)从堆栈中消耗 4 个值(步骤 3–6)并推送结果。对参数的值引用仅在命令执行后删除 returns,确保参数不会从命令的脚下消失。
- 调用(
set
)消耗堆栈中的 3 个值(步骤 1、2 和 7)。
如果值未共享,linsert
的实现可以对列表进行 in-place 编辑,但如第 4 步所述,值自然是共享的,因此必须删除重复数据,使得这必然是一项昂贵的操作。
经典的K招是这样的:
proc K {x y} {return $x}; # This is the K combinator
set x [linsert [K $x [set x DUMMY]] 0 a]
这有效地执行了 take-from-variable(留下任意值)。您正在使用它的优化版本(这取决于字节码技巧和字符串连接实现中的语义优化)。
但是为什么 set x [list $x]
不需要它呢?那么,在这种情况下,我们不会更新 值, 我们只是将它封装在列表包装器值中,然后更新 变量 。值不是变量。您可以使用 lindex $x 0
获得之前在那里的确切值;使用 tcl::unsupported::representation
亲自确认这一点,其输出包括值实际存储在内存中的位置(以及引用计数和类型信息)。这是一个很好的调试工具,尽管有时会让人感到惊讶!
[编辑]:为了说明我的意思,这里有一个小的互动环节。特别注意 object pointer
;那是 Tcl_Obj
结构的内存地址,实际上是值的真实身份。 (这在 Tcl 中通常被严格忽略;值不应该具有这样的身份,因为这是为命名实体(如变量)保留的概念。随着命名而来的是可以修改事物的想法。)
% set x "a b c"
a b c
% tcl::unsupported::representation $x
value is a pure string with a refcount of 4, object pointer at 0x7ff2eba275b0, string representation "a b c"
% set x [list $x]
{a b c}
% tcl::unsupported::representation $x
value is a list with a refcount of 2, object pointer at 0x7ff2eba26ec0, internal representation 0x7ff2eb02f290:0x0, string representation "{a b c}"
% tcl::unsupported::representation [lindex $x 0]
value is a pure string with a refcount of 4, object pointer at 0x7ff2eba275b0, string representation "a b c"
% set x [linsert $x 0 d]
d {a b c}
% tcl::unsupported::representation $x
value is a list with a refcount of 2, object pointer at 0x7ff2eba2b930, internal representation 0x7ff2eb02d090:0x0, string representation "d {a b c}"
如您所见,原始值仍在列表中;它没有以任何方式改变。
我想做的是在列表周围添加一层大括号:
set l [list $l]
对于
类型的操作set someVar [someFunc $someVar]
推荐(
set someVar [someFunc $someVar[set someVar {}]]
相反,它可以显着提高性能。
两个问题:
- 这是否也适用于
list
,所以我应该使用它来解决我的具体问题吗?:
set l [list $l[set l {}]]
- 是否有另一种有效的方法来添加额外级别的大括号?
感谢您的帮助!
编辑:更正了 Chris Heithoff 指出的错误
是的,应该可以。 list
只是 K.
someFunc
的另一个可能的东西
你的列表具体例子不太正确。
set l [list $l[set $l {}]]
..你想给变量名l
分配一个空字符串,而不是$l
的值才真正受益。
改为这样做:
set l [list $l[set l {}]]
这是对为什么您不需要 K 技巧的解释。
Tcl 的值通常是引用计数的复合值,可以保证序列化为字符串;这通常是隐藏的信息,但事情确实如此。事实上,我们的一些字符串在某些时候也有复杂的内部表示,这使得 Tcl 的字符串在大多数时候比 C、C++ 或 Java 中的字符串快很多。
这是一个模型,您必须复制一个值才能对其进行修改。但我们实际上并没有这样做;如果你必须一直复制东西,那就太慢了!相反,我们只在写入 shared 值时获取副本;该值是浅重复数据删除的,更新应用于新副本,然后在应该使用的地方使用它(例如,写回变量)。这是根据需要以递归方式完成的,因此当您在嵌套列表或字典的深处进行修改时,正确的事情就会发生。
什么时候共享价值观?好吧,有几个地方可以保存引用(例如,列表和字典),但这里的关键是:
- 变量。 (呃!这是一个非常明显的!)
- Tcl 执行堆栈。这是 Tcl 保存替换、变量读取和命令的结果的地方,直到它可以分派给它接下来将要调用的命令。 理论上可以使用替代执行机制,但您仍然必须将引用保留相同的时间才能使执行模型正确。
那么,在 K 的经典案例中会发生什么?好吧,你从做 set x [linsert $x 0 a]
开始,这(忽略字节编码)会做:
- 推
set
. - 推
x
. - 推送
linsert
- 推送从
x
读取的值。 (注意这一点:它现在有 两个 引用,一个来自变量,一个来自堆栈) - 推送
0
- 推送
a
- 调用(
linsert
)从堆栈中消耗 4 个值(步骤 3–6)并推送结果。对参数的值引用仅在命令执行后删除 returns,确保参数不会从命令的脚下消失。 - 调用(
set
)消耗堆栈中的 3 个值(步骤 1、2 和 7)。
如果值未共享,linsert
的实现可以对列表进行 in-place 编辑,但如第 4 步所述,值自然是共享的,因此必须删除重复数据,使得这必然是一项昂贵的操作。
经典的K招是这样的:
proc K {x y} {return $x}; # This is the K combinator
set x [linsert [K $x [set x DUMMY]] 0 a]
这有效地执行了 take-from-variable(留下任意值)。您正在使用它的优化版本(这取决于字节码技巧和字符串连接实现中的语义优化)。
但是为什么 set x [list $x]
不需要它呢?那么,在这种情况下,我们不会更新 值, 我们只是将它封装在列表包装器值中,然后更新 变量 。值不是变量。您可以使用 lindex $x 0
获得之前在那里的确切值;使用 tcl::unsupported::representation
亲自确认这一点,其输出包括值实际存储在内存中的位置(以及引用计数和类型信息)。这是一个很好的调试工具,尽管有时会让人感到惊讶!
[编辑]:为了说明我的意思,这里有一个小的互动环节。特别注意 object pointer
;那是 Tcl_Obj
结构的内存地址,实际上是值的真实身份。 (这在 Tcl 中通常被严格忽略;值不应该具有这样的身份,因为这是为命名实体(如变量)保留的概念。随着命名而来的是可以修改事物的想法。)
% set x "a b c"
a b c
% tcl::unsupported::representation $x
value is a pure string with a refcount of 4, object pointer at 0x7ff2eba275b0, string representation "a b c"
% set x [list $x]
{a b c}
% tcl::unsupported::representation $x
value is a list with a refcount of 2, object pointer at 0x7ff2eba26ec0, internal representation 0x7ff2eb02f290:0x0, string representation "{a b c}"
% tcl::unsupported::representation [lindex $x 0]
value is a pure string with a refcount of 4, object pointer at 0x7ff2eba275b0, string representation "a b c"
% set x [linsert $x 0 d]
d {a b c}
% tcl::unsupported::representation $x
value is a list with a refcount of 2, object pointer at 0x7ff2eba2b930, internal representation 0x7ff2eb02d090:0x0, string representation "d {a b c}"
如您所见,原始值仍在列表中;它没有以任何方式改变。