Tcl 为什么不是';t“中内置的异常处理”;dict get";指挥部?

Tcl 为什么不是';t“中内置的异常处理”;dict get";指挥部?,tcl,Tcl,当字典首次实现并添加到Tcl时,为什么dict get命令的实现方式允许在尝试检索字典中不存在的键的值时发生错误 这要求您在每次使用catch语句时将该命令包装在catch语句中,以确保其完全安全。在我看来,像这样一个经常使用的命令总是会内置某种异常处理。我想这就是为什么我们提供了dict exists命令 您可能希望dict get返回键元素不存在的空字符串。但是,如果任何键本身的实际值是空字符串,那么使用类似于它们的实现将导致问题 % set demo {id {} name Dinesh}

当字典首次实现并添加到Tcl时,为什么dict get命令的实现方式允许在尝试检索字典中不存在的键的值时发生错误


这要求您在每次使用catch语句时将该命令包装在catch语句中,以确保其完全安全。在我看来,像这样一个经常使用的命令总是会内置某种异常处理。

我想这就是为什么我们提供了
dict exists
命令

您可能希望
dict get
返回键元素不存在的空字符串。但是,如果任何键本身的实际值是空字符串,那么使用类似于它们的实现将导致问题

% set demo {id {} name Dinesh}
id {} name Dinesh
% dict get $demo id
% dict get $demo age
key  "age" not known in dictionary
% 

如果要跳过捕获,请使用dict exists

dict命令作为一个。这意味着您可以很容易地自己扩展它来实现这一点。我喜欢调用这个
dict get?
,如果键不存在,它将返回一个空值。我们可以按如下方式添加此新子命令:

proc ::tcl::dict::get? {dict key} {
    if {[dict exists $dict $key]} {
        return [dict get $dict $key]
    }
   return
}
namespace ensemble configure dict \
    -map [linsert [namespace ensemble configure dict -map] end get? ::tcl::dict::get?]
如您所见,这将
dict exists
调用与
dict get
调用简单地包装起来,但由于集成更新,它将作为
dict
命令的内置部分呈现。在使用中,它看起来像这样:

if {[dict get? $meta x-check-query] eq "yes"} {
    ... do stuff ...
}
if {[set x [foo]] ne {BAD_RETURN_VALUE}} {
    # use the resource
} else {
    # deal with failure
}
try {
    foo
} on ok x {
    # use the resource
} on error {} {
    # deal with failure
}

(这可以在Tcl测试套件代码中看到。)

这是Tcl(以及其他一些语言)中常见的设计选择。当像
dict get
(或者更常见的是,
open
)这样的命令失败时,程序必须处理它,这意味着必须以某种方式向它发出故障警报

最常见的选项是使用失败的命令

  • 返回一个域外值(如有该值的语言中的
    null
    ),或

  • 提出一个例外

  • (例如,
    lsearch
    命令如果成功则返回索引值,如果失败则返回-1(第一个选项)。
    dict get
    命令如果成功则返回值,如果失败则引发异常(第二个选项)。)

    对于
    dict get
    命令,第一个选项实际上不可行,因为没有域外值。任何Tcl值都可能存储在字典中,因此您无法查看dict get的结果并知道它找不到值。空字符串通常在Tcl中用作伪空值,但空字符串很可能是字典中的实际值

    因此,
    dict get
    在失败时引发异常。没那么糟。异常具有许多简洁的属性,例如直接将控制权交给最近的封闭处理程序,而不管它需要释放多少堆栈级别

    (实际上不可能在命令内部处理所有异常:处理程序必须知道如何处理错误,
    dict get
    无法知道。)

    无论如何,可能失败的命令都需要进行某种检查。如果使用
    foo
    命令获取可能不可用的资源,并且没有合理的默认值,则调用它的代码必须如下所示:

    if {[dict get? $meta x-check-query] eq "yes"} {
        ... do stuff ...
    }
    
    if {[set x [foo]] ne {BAD_RETURN_VALUE}} {
        # use the resource
    } else {
        # deal with failure
    }
    
    try {
        foo
    } on ok x {
        # use the resource
    } on error {} {
        # deal with failure
    }
    
    或者像这样:

    if {[dict get? $meta x-check-query] eq "yes"} {
        ... do stuff ...
    }
    
    if {[set x [foo]] ne {BAD_RETURN_VALUE}} {
        # use the resource
    } else {
        # deal with failure
    }
    
    try {
        foo
    } on ok x {
        # use the resource
    } on error {} {
        # deal with failure
    }
    
    或者类似这样(如果存在预测if
    foo
    是否成功的谓词函数):

    在每一种情况下都会有同样多的麻烦。由于域外值在Tcl中很少见,并且错误处理功能非常广泛,因此通常倾向于使用谓词或异常策略

    patthoyts已经展示了一种将错误抑制getter函数添加到
    dict
    集合的方法。另一个相对轻量级的调用是

    set foo [try {dict get $bar xyzzy} on error {} {}]
    
    如果调用成功,则返回
    dict get
    调用的结果,如果调用不成功,则返回空字符串,并压扁引发的任何错误

    set foo [try {dict get $bar xyzzy} on error {} {return 42}]
    
    此调用设置失败时使用的默认返回值

    如果调用仍然很麻烦,可以将其转换为命令:

    proc dictget args {
        set default {}
        if {[lindex $args 0] eq {-default}} {
            set args [lassign $args - default]
        }
        try {
            dict get {*}$args
        } on error {} {
            set default
        }
    }
    
    这方面的概要如下:

    dictget-默认值?字典值?钥匙


    文档:,,,

    我使用“dict exists”,但这仍然是一个额外的步骤。我想得越多,就越觉得值检索是一个两步过程(除了在您自己的进程中包装'dict get'并调用它)…任何作为错误返回的内容都无法与有效的字典值区分开来。