Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

tcl

Tcl's object-oriented system allows for the use of Filter methods that can be used to intercept method invocations on a class's instances.

This functionality can be used to replicate Smalltalk's style of method chaining, which means these three individual statements

person setName Bob

person setAge 100

person details

Could instead be chained together and written as

person setName Bob setAge 100 details

The code below implements this functionality by defining a Filter method to do the following:

  1. Given the requested method name look up its implementation and the number of parameters it requires.
  2. Split the parameters captured by the filter between the target method -- sending it only what it requires (determined in step 1) -- and then forwarding the remaining parameters to the instance again to trigger the method dispatch / Filter method functionality.

In essence, this logic is de-sugaring the single-statement method chain example above into the three statement version.

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
}

My implementation appears to be working, but with some surprising results.

When the below is invoked

person setName Bob setAge 100 details

The following is output:

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

The fact that we see "Name set to Bob" means that the filter implementation is working correctly and that the messages are being split up as expected: setName is being called with a single argument of "Bob". However, the warning about wrong # of args sent to setAge is unexpected -- as we've just seen, the filter implementation has no problem dealing with method calls that are receiving more than the specified number of arguments. The fact that this message appears indicates that the internal call of my setAge 100 details is not invoking the Filter itself. If it were, setAge should not complain about the additional parameter.

The main difference between the initial call of

person setName Bob setAge 100 details

and the internal call of

my setAge 100 details

is that the first originates from outside the Filter while the second originates from inside the Filter.

I had suspected that perhaps it was the use of my, but using [self object] in its place failed in the same manner.

Likewise, I also attempted to invoke subsequent "forwarded" messages via uplevel, but again, got the same error about wrong number of arguments.

To finally get to my question: do methods invoked from within Filter methods follow the same dispatch procedures as normal methods, or do they bypass the Filtering phase?

like image 722
dmux Avatar asked Oct 29 '25 07:10

dmux


1 Answers

You are correct (but I had to go and read the source carefully to confirm this). In a filter, filters are disabled, as without that you get all sorts of crazy things happening with reentrancy when you try to access all sorts of aspects of the current object.

The way to make what you're doing work is to use tailcall, specifically:

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

With that one change, you get (cut-n-paste from an interactive session):

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

The tailcall means that this won't work too well with multiple filters... but the whole idea of stacking multiple filters (especially ones like yours!) just makes my brain ache.


Congratulations, by the way, on finding a new (to me) use for filters that I'd never considered before.

like image 115
Donal Fellows Avatar answered Nov 01 '25 12:11

Donal Fellows



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!