-
-
Save kriskowal/1691779 to your computer and use it in GitHub Desktop.
function foo() { | |
bar({|x| | |
return 10; | |
}); | |
} | |
function bar(block) { | |
block(); | |
block(); // we should never get here. how? | |
} | |
foo(); |
10 foo(); // stack: [] | |
2 bar({|x| return 10}) // stack: [foo] | |
7 block() // stack: [foo, bar] | |
3 return 10 // stack: [foo, bar, block] | |
… |
@dherman Perhaps the “exception way of thinking” should be more literally true. A thrown value, perhaps a ReturnValue
exception like that codified by your generators improvement proposal, implicitly caught by foo
, identified by its object identity as being associated with itself, and converted into a literal return value.
If my function is going to be interrupted, I expect to be able to observe that in a
catch
orfinally
clause and clean up resources.
Well first of all, you can use a finally
block to clean up resources. That wouldn't change.
As for catch
, the difference is that nobody but lexically enclosed code (i.e., you) can fabricate the ability to cause that local exit unless you explicitly pass them a block that you wrote yourself. That is, unlike exceptions, nobody has the ability to cause you to return
unless you hand them a capability to return
in the form of a block that closes over return
.
Perhaps the “exception way of thinking” should be more literally true. A thrown value, perhaps a
ReturnValue
exception like that codified by your generators improvement proposal, implicitly caught byfoo
, identified by its object identity as being associated with itself, and converted into a literal return value.
This approach would be dynamic scoping, which has two problems. 1) Any function you call would implicitly have the ability to force you to return; 2) It breaks abstractions; if you pass a block lambda that does a return
to another higher-order function, it will accidentally return from that function instead of the function it appeared to be lexically bound to.
@dherman Right. The finally
clause might be enough.
I did not mean to imply that you could fabricate a ReturnValue
exception and throw it, causing a return from the first parent scope. I mean that a return
in a block lambda would construct a ReturnValue
exception with a read-only value
(at least), add it to a side-table.
var sideTable = new WeakMap();
// return value desugars
var exception = new ReturnValue(value);
sideTable.set(exception, foo);
throw exception;
// foo desugars
function foo() {
try {
} catch (x) {
if (sideTable.get(x) === foo) {
return x.value;
} else {
throw x;
}
}
}
Then foo
would implicitly catch the exception and convert a ReturnValue.value
into a return value only if the exception in the side-table maps to foo
.
This would make it possible for an intermediary to observe the exception in a catch block, rethrow it, or throw a different exception. It would not give the ability to return an arbitrary value from an arbitrary parent scope.
@dherman I think that finally
will have to suffice. It would be an information/capability leak for an intermediate stack frame to be able to observe the return value in a parent lexical scope. MarkM would kill me for suggesting it.
@dherman The least terrifying of these, for me, is to think of it like an exception. I was initially confounded because it seems like it would be necessary for the block to interrupt the flow of the higher order function, but with exceptions, there are existing accommodations for these cases. If my function is going to be interrupted, I expect to be able to observe that in a
catch
orfinally
clause and clean up resources.