TIP 572: Implement a mechanism to specify the procedure/lambda/method argument call types (Evaluation strategy)

Login
Author:         Rami Khaldi <[email protected]>
State:          Draft
Type:           Project
Tcl-Version:    9.1

Abstract

The aim of this TIP is to implement a mechanism to specify the procedure call types (call by name vs. value).

Rationale

Tcl users used to use the upvar commands to pass the arguments by names. The goal of this document is to provide a mechanism how to define the command call strategies prior to calling the target command. This includes not only the procedures defined by proc command but the apply and methods as well. so that there will be no need to use upvar command anymore to be able to pass the arguments by names.

The important thing in this feature is to preserve the backward compatibility and not to change the procedure definition signature. The calling strategy does not need to be fixed for the whole lifecycle of the command, i.e. we can change it several times prior to calling the target command.

I addition to we will see the performance improvement results compared to the old approach.

Specification

A new C based command has been implemented:

callstrat <command> <arg1> <arg2> ... <argn>

The <command> specifies the target command we want to change its calling strategy.

The arg<i> specifies which call strategy we want the procedure to be called by.

arg<i> := * | &.

* := Call by value (Default).

& := Call by name.

Actually anything different than & will be interpreted as call by value. Only the first character of the strategy will be evaluated i.e (*, **, *&, *&& will be evaluated as call by value and &, &&, &* will be evaluated as call by name).

Use cases

Target command types

Procedure

set a 1; 
set b [list 2 3]; 
set c [dict create 4 5]; 
set d "example"; 
set e 40; 
set f(X) 100; 
set f(Y) 200;

proc manipulateData {a b c d e f} {
    incr a
    set b [join $b -]
    set c WasDict
    set d [string toupper $d]
    incr e -19
    set f(Z) 300
}

# Specify the call strategy for the command: manipulateData (Only the fifth argument will be passed by value. The other ones will be passed by name)
callstrat manipulateData & & & & * &

#Call the target procedure
manipulateData a b c d $e f

# See the side-effects for arguments passed by name

# a -> 2
# b -> 2-3 
# c -> WasDict
# d -> EXAMPLE
# e -> 40 (not manipulated, as it has been passed by value)
# f ->  X -> 100, Y -> 200, Z -> 300 (Extend array passed by name)

Method

set a 1
set b 2
set c 3

oo::class create myClass; 
oo::define myClass { 
    method myMethod {a b c} { 
        incr a; incr b; incr c 
    }
}

set obj [myClass new]

#Specify call strategy for the next method to be called
callstrat $obj & * &

#Call the target method
$obj myMethod a $b c

# See the side-effects

# a -> 2
# b -> 2 
# c -> 4

Lambda

set a 10
set b 20
set c 30

#Specify call strategy
callstrat apply & * &

#Call the target anonymous procedure (lambda)
apply {{x y z} {incr x; incr y; incr z}} a $b c

# See the side-effects

# a -> 11
# b -> 20
# c -> 31

Recursion

Classic Recursion

set a 1
set b 2
set c 3

proc myRecProc {a b c} {
    if {$a + $b + $c > 10} {
        return
    }

    incr a; incr b; incr c;

    #Pass by name: a and c. Pass by value: b
    myRecProc a $b c
}

#Specify call strategy
callstrat myRecProc & * &

#Call the target procedure
myRecProc a $b c

# See the side-effects

# a -> 3
# b -> 2
# c -> 5

Tailcall Recursion

set a 1
set b 2
set c 3

proc myRecProc {a b c} {
    if {$a + $b + $c > 10} {
        return
    }

    incr a; incr b; incr c;

    #Pass by names: a & c. Pass by value: b
    tailcall myRecProc a $b c
}

#Specify call strategy
callstrat myRecProc & * &

#Call the target procedure
myRecProc a $b c

# See the side-effects

# a -> 3
# b -> 2
# c -> 5

Multiple levels

set a 1
set b 2
set c 3
set d 4

#procs manipulating variables
proc manipulateData1 {a b c d} {
    append a +; append b +; append c +; append d +;
    manipulateData2 a b c d
}
proc manipulateData2 {a b c d} {
    append a -; append b -; append c -; append d -;
    manipulateData3 a b c d
}
proc manipulateData3 {a b c d} {
    append a .; append b .; append c .; append d .;
}

#Specify call strategy
callstrat manipulateData1 & * & &
callstrat manipulateData2 & & * &
callstrat manipulateData3 & & & *

#Call the target procedure
manipulateData1 a b c d

# See the side-effects

# a -> 1+-.
# b -> 2
# c -> 3+
# d -> 4+-

Call Strategy Life-Cycle

TODO: create update delete

Performance Analysis

The TclX profiler has been used.

profile on

set iter 10000000

set a1 0
set a2 0

proc myProc1 {a} {
	upvar $a l
	incr l
}

proc myProc2 {a} {
	incr a
}
callstrat myProc2 &

for {set i 0} { $i < $iter} {incr i} {
	myProc1 a1
	myProc2 a2
}

puts $a1
puts $a2

profile off res;
profrep res cpu profdata.txt;

Results

------------------------ -------------- ----------- ----------
Procedure Call Stack Calls Real Time CPU Time
------------------------ -------------- ----------- ----------
1 300772 299964
myProc1 10000000 91320 91000
myProc2 10000000 50597 51260
history 19 0 0
tcl::HistAdd 19 0 0

Implementation

The code is licensed under the BSD license.

tip-572.

Testing

Documentation

Copyright

This document has been placed in the public domain.