Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why do we need nested procedures in tcl

Hi i have been working with tcl scripting for almost a year and now understand almost basics of it completely. But today i just came across nested procedures which is kind of strange as i did not get the use of it.

Anyways, i read about nested proc here but did not get the clear idea as of why do we need it.

The article says that since proc's are global in a namespace so to create a local proc you make nested proc's.

    proc parent {} {
        proc child {} {
            puts "Inside child proc";
        }
        ...   
    }

Now one usage i can think of is like

    proc parent {} {
        proc child {intVal} {
            puts "intVal is $intVal";
        }
        puts "[::child 10]";
        ... #some processing
        puts "[::child 20]";
        ... #some processing
        puts "[::child 30]";
        ... #some processing
        puts "[::child 40]";
        ... #some processing
        puts "[::child 50]";
        ... #some processing
    }

So now the child proc is local to the parent proc and could be used only inside parent proc. And also as i understand it is useful when you want to do same processing at multiple places inside that parent proc.

Now my confusion is that Is this the only use of nested proc or is there anything else that i did not understand???. I mean the nested proc just seems like a kind of private proc.

So please shed some light on it and help me understand the use of nested proc's.

like image 982
Puneet Mittal Avatar asked Feb 16 '23 04:02

Puneet Mittal


2 Answers

Tcl doesn't have nested procedures. You can call proc inside a procedure definition, but that's just creating a normal procedure (the namespace used for resolution of the name of the procedure to create will be the current namespace of the caller, as reported by namespace current).

Why would you put proc inside proc? Well, the real reason for doing so is when you want to have the outer command act as a factory, to create the command when it is called. Sometimes the name of the command to create will be supplied by the caller, and sometimes it will be internally generated (in the latter case, it is normal to return the name of the created command). The other case that comes up is where the outer command is some sort of proxy for the (real) inner one, allowing the postponing of the creation of the real command because it is expensive in some fashion; if that's the case, the inner procedure will tend to actually be created with the same name as the outer one, and it will replace it (though not the executing stack frame; Tcl's careful about that because that would be crazy otherwise).

In the case where you really need an “inner procedure” it's actually better to use a lambda term that you can apply instead. That's because it is a genuine value that can be stored in a local variable and which will automatically go away when the outer procedure terminates (or if you explicitly unset the variable or replace its contents). This inner code won't have access to the outer code's variables except via upvar; if you want to return the value while still binding variables, you should use a command prefix and include a bit of extra trickery to bind the variables as pre-supplied arguments:

proc multipliers {from to} {
    set result {}
    for {set i $from} {$i <= $to} {incr i} {
        lappend result [list apply {{i n} {
            return [expr {$i * $n}]
        }} $i]
    }
    return $result
}

set mults [multipliers 1 5]
foreach m $mults {
    puts [{*}$m 2.5]
}
# Prints (one per line): 2.5  5.0  7.5  10.0  12.5

Using an inner proc to simulate apply

Note that the apply command can actually be simulated by an inner procedure. This was a technique used in Tcl 8.4 and before:

# Omitting error handling...
proc apply {lambdaTerm args} {
    foreach {arguments body namespace} $lambdaTerm break
    set cmd ${namespace}::___applyLambad
    proc $cmd $arguments $body
    set result [uplevel 1 [linsert 0 $args $cmd]]; # 8.4 syntax for safe expansion!
    rename $cmd ""
    return $result
}

This was somewhat error-prone and very slow as it would recompile on each invocation; we don't do that any more!

like image 94
Donal Fellows Avatar answered Feb 18 '23 18:02

Donal Fellows


Tcl does not have nested procs. From the proc man page:

Normally, name is unqualified (does not include the names of any containing namespaces), and the new procedure is created in the current namespace.

(emphasis mine)

To demonstrate:

% namespace eval foo {
    proc parent {} {
        proc child {} {
            puts "the child"
        }
        puts "the parent, which holds [child]"
    }
}
% foo::parent
the child
the parent, which holds 
% foo::child
the child

We can still call the "inner" proc directly -- it's not local to the enclosing proc.

One item you missed in the discussion in that wiki page is that to make a proc truly local only to the enclosing proc, one must delete it at the end of the enclosing proc:

% namespace eval foo {
    proc parent {} {
        proc child {} {
            puts "the child"
        }
        puts "the parent, which holds [child]"
        # now, destroy the inner proc
        rename child ""
    }
}
% foo::parent
the child
the parent, which holds 
% foo::child
invalid command name "foo::child"

As to the use of a local proc, I'd agree with you that it's beneficial to encapsulate repetive tasks that are only useful in the current proc. I wouldn't get too hung up on that though: clear documentation or code conventions will do just as well.

like image 43
glenn jackman Avatar answered Feb 18 '23 17:02

glenn jackman