从 Filter 方法中调用的方法能否调用 Filter 本身?

Can methods invoked from within Filter methods invoke the Filter itself?

Tcl 的面向对象系统允许使用 Filter 方法,这些方法可用于拦截对 class 实例的方法调用。

此功能可用于复制 Smalltalk 的方法链接风格,这意味着这三个单独的语句

person setName Bob

person setAge 100

person details

可以改为链接在一起并写成

person setName Bob setAge 100 details

下面的代码通过定义一个 Filter 方法来执行以下操作来实现此功能:

  1. 给定请求的方法名称,查找它的实现和它需要的参数数量。
  2. 在目标方法之间拆分过滤器捕获的参数——只发送它需要的(在步骤1中确定)——然后将剩余的参数再次转发给实例以触发方法调度/Filter 方法功能。

本质上,此逻辑是将上面的单语句方法链示例脱糖为三语句版本。

oo::class create Person {
    variable Name Age

    method MethodChaining {args} {
        # Get the target method's name and its associated class.
        lassign [self target] className methodName
        # puts "$className :: $methodName"

        # Retrieve the target method's definition,
        # specifically its call signature / what parameters it takes.
        lassign [info class definition $className $methodName] methodArgs


        if {[llength $args] == 0} {
            # If the filtering method was invoked with no arguments,
            # there is nothing to forward to the target method.
            return [next]
        } elseif {$methodArgs eq "args"} {
            # If the target method has specified the "args" parameter
            # name, we forward all of the filtering methods arguments.
            
            #puts "Target method takes unlimited args."
            return [next {*}$args]
        } else {
            # Split the filter's arguments up to satisfy
            # the target method's required number of arguments
            # and then forward the rest to the object instance
            # to trigger method dispatch.
            set numMethodArgs [llength $methodArgs]
            set targetArgs [lrange $args 0 $numMethodArgs-1]
            set argsToForward [lrange $args $numMethodArgs end]
            
            # puts "targetArgs: $targetArgs"
            # puts "argsToForward: $argsToForward"
            next $targetArgs
            
            if {[llength $argsToForward] != 0} {
                my {*}$argsToForward            
            }
            
            
        }
        
    }

    method multi {args} {
        puts "Hello $args"
    }

    method setName {name} {
        puts "Name set to $name"
        set Name $name
        
    }

    method setAge {age} {
        puts "Age set to $age"
        set Age $age
        
    }

    method details {} {
        puts "$Name is $Age years old"
    }

    filter MethodChaining
}

我的实施似乎有效,但产生了一些令人惊讶的结果。

当下面的被调用时

person setName Bob setAge 100 details

输出如下:

Name set to Bob
wrong # args: should be "my setAge age"

我们看到“Name set to Bob”这一事实意味着过滤器实现正常工作并且消息正在按预期拆分:正在使用“Bob”的单个参数调用 setName。然而,有关发送到 setAge 的错误 # of args 的警告是出乎意料的——正如我们刚刚看到的,过滤器实现在处理接收超过指定数量参数的方法调用时没有问题。出现此消息的事实表明 my setAge 100 details 的内部调用未调用过滤器本身。如果是,setAge 不应该抱怨附加参数。

与初始调用的主要区别

person setName Bob setAge 100 details

的内部调用
my setAge 100 details

第一个来自过滤器外部,而第二个来自过滤器内部

我曾怀疑可能是 my 的使用,但在其位置使用 [self object] 以同样的方式失败。

同样,我也尝试通过 uplevel 调用后续的“转发”消息,但再次遇到关于参数数量错误的相同错误。

最后回答我的问题:中调用的方法在中过滤方法遵循与普通方法相同的调度程序,还是绕过过滤阶段?

你是对的(但我必须去阅读来源仔细来确认这一点)。在过滤器中,过滤器是禁用的,因为如果没有过滤器,当您尝试访问当前对象的各个方面时,您会在重入时发生各种疯狂的事情。

让您正在做的事情发挥作用的方法是使用 tailcall,具体来说:

tailcall my {*}$argsToForward
# Or maybe, so you can only chain public methods:
#   tailcall [self] {*}$argsToForward

通过这一更改,您将获得(cut-n-paste 来自交互式会话):

% person setName Bob setAge 100 details
Name set to Bob
Age set to 100
Bob is 100 years old

tailcall 意味着这对多个过滤器不太适用...但是堆叠多个过滤器(尤其是像你这样的!)的整个想法让我很头疼。


顺便说一句,恭喜你找到了我以前从未考虑过的过滤器的新用途(对我来说)。