5 ms·
I believe the original Algol-68 didn't have full closures. For example, the following (taken from the revised report) wouldn't work: proc f = (real x) proc (
by bakul 8y ago
I believe the original Algol-68 didn't have full closures. For example, the following (taken from the revised report) wouldn't work:
proc f = (real x) proc (real) real: (real y) real: x + y;
proc (real) real g := f (3);
As by the time g is created, x of f has disappeared from the stack.
- Taniwha 8y agoWhat it does have is the algol 60 idea of nested scopes For example if proc A contains a variable X and declares a subroutine B within it, then B can access X, even if you take a pointer to B and call it from elsewhere (effectively that pointer to B also carries info about which instance of A it came from .... These days we've largely forgotten about this idea of how variables should be accessed and inherited between subroutines - hardware designers used to design support for these language features (display registers in Burroughs mainframes) - to be fair as a feature it honestly doesn't add a lot of practical functionality considering what it costs
- int_19h 8y agoWe even have the legacy of that in x86 - the ENTER opcode, the second argument for which is "nesting level of the function": "If the nesting level is 0, the processor pushes the frame pointer from the BP/EBP/RBP register onto the stack, copies the current stack pointer from the SP/ESP/RSP register into the BP/EBP/RBP register, and loads the SP/ESP/RSP register with the current stack-pointer value minus the value in the size operand. For nesting levels of 1 or greater, the processor pushes additional frame pointers on the stack before adjusting the stack pointer. These additional frame pointers provide the called procedure with access points to other nested frames on the stack. See “Procedure Calls for Block-Structured Languages” in Chapter 6 of the Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1, for more information about the actions of the ENTER instruction." (Although, curiously, Free Pascal - which also implements this feature - doesn't use ENTER in assembly, last I checked.)
- bakul 8y agoThe issue is that if the local variables of A, such as X are allocated on the stack, once you return from A, the stack frame will disappear. So the example you cite can only work as long as A is active (not returned from). If A itself returns a pointer to B, at least the locals of A captured by B must be allocated on the heap and GCed once all references to a particular B disappear. Note that a number of modern languages provide full closures including Go, Haskell, JavaScript, Python, Scheme, Swift.
- Taniwha 8y agoexactly .... which kind of makes those proc pointers not as useful because you need to carefully manage lifetimes/etc yourself and failures are, well, not defined
- bakul 8y agoIt's the compiler's job to do so, not a programmers! The programmer can do whatever makes sense. For example, in Scheme (define (make-counter) (let ((n 0)) (lambda () (set! n (+ n 1)) n))) is guaranteed to work in any context where make-counter is called. Same in Go; though the syntax is a bit unwieldy due to having to specify types: func makeCounter() (func () int)) { n := 0 return func () int { n++; return n } } Google's Go compiler is smart enough to allocate only the captured variable (n in this case) for such returned functions on the heap. There should be no failures. IIRC some algol-68 compiler did remove the restriction and allowed such full closures.
- Taniwha 8y agoI think that's a language centric point of view - more one would say "it's the language's job to do so" and then write a compiler for the language. Algol60 and 68 were created at a time when people were still trying to figure out how computer languages ought to work (in a sense we still are) and it's likely the idea that procedure local storage should (or could) out live the procedure being called wasn't an obvious choice (remember that algol68 Nests are not just locals of a particular subroutine but also locals of every subroutine in its declaration [not calling] path as well as globals at level 0) Algol68 (and Algol60) gave us lots of things we take for granted today but there are also features that have fallen out of fashion (I'm old enough to have seen language features like objects and functional programming come and go, several times ... it's all fashion .... and I'm cynical enough to know that there's no correct way to do any of this stuff)