1

The following script gives an error:

proc __val x {set x}
proc mul {x y } {
    return [expr $x*$y]
}
proc factorial {x } { return if [expr $x==0] {__val 1} {__val [mul $x [factorial [expr $x-1]]]} }
set x 4
mul $x [factorial [expr $x-1]]
missing operand at _@_
in expression "4*_@_"

Why?

I've read this similar question but the problem there seems different. If it's the same problem interpret this question as "why does this script also have that problem?".

Timmmm
  • 88,195
  • 71
  • 364
  • 509

1 Answers1

2

Firstly, brace your expressions. Really. It means that Tcl can generate bytecode for the expression instead of needing to prepare everything every time.

proc __val x {set x}
proc mul {x y } {
    return [expr {$x*$y}]
}
proc factorial {x } { return if [expr {$x==0}] {__val 1} {__val [mul $x [factorial [expr {$x-1}]]]} }
set x 4
mul $x [factorial [expr {$x-1}]]

With that, we get the slightly less confusing error can't use empty string as operand of "*" and the trace will tell you that this is coming from the (expression inside the) top level call to mul. Which means that the result of factorial is the empty string. What else could be going wrong there?

Well, there is abuse of return. Yes, return accepts many arguments that are formed into the result dictionary (that's where error traces are stored, for example) but what you are doing is highly unlikely. Let's fix that almost syntactic error (and add some newlines for clarity).

proc __val x {set x}
proc mul {x y } {
    return [expr {$x*$y}]
}
proc factorial {x } {
    return [if {$x==0} {__val 1} {
       __val [mul $x [factorial [expr {$x-1}]]]
    }]
}
set x 4
mul $x [factorial [expr {$x-1}]]

Behold, it now works. But we can shorten it.

# Magic namespace contains function definitions
proc tcl::mathfunc::factorial x {expr {
    $x == 0 : 1 ? $x * factorial($x - 1)
}}

# I ought to brace this... but I can't be bothered as it is a literal
expr factorial(4)
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • What you call "abuse of return" should be handled by (newer?) Tcl in a more predictable manner. Even after all that years using Tcl, I find myself always disturbed by `return` just packing anything into the result dictionary, depending on the arity of the arguments provided. Other commands explicitly check for valid flags/ options being passed, return does not. dashed keys are merely syntactic sugar. Are there valid reasons for `return` behaving this way, other than backward compat? – mrcalvin Dec 30 '22 at 10:59
  • I don't know. I would have at least checked that the keys looked right (a check for keys beginning with a `-` would catch most problems), but I didn't design that bit of Tcl. The reason any number of words is accepted is that the return value is optional (defaulting to the empty string, and that is old behaviour). – Donal Fellows Jan 02 '23 at 08:02