Acknowledged

"return" documentation is confusing and incorrect

At the top of the Functions page, it says:

You can specify the return value with a return statement, but if your function doesn’t have a return statement it will return the last value on the stack.

This is confusing because as far as I can see, this is the only place the documentation refers to "values on the stack", so its unclear what it actually means.

Lower down it repeats this with different wording:

All functions return values in Monkey C. You can explicitly set the return value by using the return keyword:

<example>

The expression is optional. Functions without a return statement automatically return the last value operated on.

The introduces a couple of new sources of confusion.

First it says "The expression is optional"; but then it goes on to tell you what happens if you leave the statement out altogether (rather than explaining what it does if you leave out the optional expression).

But then what does "the last value operated on" actually mean? Especially as side-effect free expressions aren't allowed at the top level in monkeyc. It sounds a bit clearer than "the last value on the stack", but it still isn't obvious what it would mean in various scenarios.

It turns out that Perl actually has a similar rule for function return values, but it allows you to say things like "$x + 2;" as a statement. So ending a function with an expression is the same as returning that expression. But monkeyc doesn't let you do that (except for function calls). But perl really does use the last evaluated expression as the return value (if you don't give an explicit return statement), so I tried:

function foo() {
  var x = 0;
  if (x != 0) {
    System.println("foo");
  }
}

In perl, the last expression evaluated would be "x != 0" so it would return false for the corresponding function written in perl... but no - in monkeyc it returns null. In fact, no matter what code I put in foo (without including a return statement!), it always seems to return null.

So the documentation appears to be wrong on that one.

But what about if you do have a return statement, but don't provide a return expression?

Well again, I found that it pretty much always returns null. With one exception: if you put a "return;" inside a switch statement (anywhere), it returns the value that was switched on.

so eg

function baz(x) {
  switch (x) {
    default:
      return;
  }
}

Always returns x. Changing it to

function baz(x) {
  switch (x) {
    default:
      break;
  }
  return;
}

goes back to returning null (!)

And it still returns null if you remove the return, and just drop off the end of the function

So I'm not quite sure how to resolve this. Maybe the simplest would be to say that a function with no return statement returns null, and that a function that returns via a return statement with no expression, returns an unspecified value? Or that both cases return unspecified values. At any rate, references to the "last value on the stack" and "last value operated on" should probably be removed (or explained).

  • And yet another exception (pun almost intended)... if you put "return;" in a catch block, it returns the exception that the catch block caught.

  • One additional exception to "return;" returning null:

    If you put "return;" inside a "finally" block, and use System.println to print the result, it says "method". But it's not a Lang.Method, because "m has :invoke" is false (and trying to call invoke on it fails).

    But if you're careful, you can call it using normal call syntax, and things get weird. For some reason I can't edit the original post, so I'll add the details here:

    function foo(x) {
      try {
        if (x == 0) {
          throw new Lang.Exception();
        }
      } catch (ex) {
      } finally {
        return;
      }
      // this should be unreachable... and it *is* if you call
      // foo normally.
      System.println("Wtf: " + x);
    }
    
    var m;
    
    function test() {
      System.println(foo(0)); // prints "method"
      System.println(foo(10));// prints "method"
      m = foo(42);
      m(24); // prints "Wtf: 24"
      m();   // prints "Wtf: null"
      m(9);  // prints "Wtf: 9"
    }
    

    So the "method" that gets returned from the finally block is callable, and seems to set foo's parameter, but jumps to immediately after the finally call. Note that this doesn't work if m is a local, because "m()" is implicitly treated as "self.m()" by the compiler.