Live data from Hacker News

Subroutine calls in the ancient world, before computers had stacks or heaps

devblogs.microsoft.com

201–210 of 241 posts

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#201

I was looking through the ROM listing on a really old diskette controller for an S-100 computer, and I saw all those jumps... and didn't understand what was going on. Then a friend told me there wasn't any guarantee of RAM in any given address, so they used the BX register (if I recall correctly) for the return address.

Similar with PC BIOSes from before x86 CPUs got internal cache. The DRAM wasn't usable until it was properly set up (refresh, and possibly timing parameters on later chipsets).

One trick I've seen in BIOS code is basically return-oriented programming[1]: before a "call", the stack pointer is set to some location in ROM containing one or more return addresses. The advantage over putting the return address itself in some register is that this way, it can use subroutines ending in a normal RET instruction, that might also be called later when there is an actual stack.

[1] https://en.wikipedia.org/wiki/Return-oriented_programming

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#202

Earlier quoted context omitted.

There is an argument that all recursive proofs can be made iterative due to isomorphism. You are not lazy enough to be a good programmer yet. ;-)

Yeah, but that is error prone and more complex. A compiler can make those same transformations. I'd argue that the properly lazy programmer is the one using recursion. To get even lazier, one should move into relational algebra.

Meh, or just choose a documented data structure that supports your problem scope. If it takes longer than 1 coffee, than someone is usually approaching things the wrong way...

Have to think "minimum effort" here... ;-)

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#203
post #165

Earlier quoted context omitted.

Well, it's still recursion whether you're using the call stack or are using an explicit stack structure. You're still breaking the problem down into smaller subproblems inductively. I feel that people focus on the wrong things when talking about recursion, focusing on the aspect of having a function calling itself instead of the idea of having the problem solved by way of breaking it down into smaller problems.

it's still recursion whether you're using the call stack or are using an explicit stack structure Recursion means defining something in terms of itself, so no, using a stack isn't recursion. The call stack of lots of different function calls in a normal program isn't called recursion either. the idea of having the problem solved by way of breaking it down into smaller problems. That's not recursion, that's organizati…

> Recursion means defining something in terms of itself, so no, using a stack isn't recursion.

How can you differentiate between them? How do you account for these two things being isomorphic? Any algorithm that can be defined by calling itself can also be expressed by way of an explicit stack, just as any algorithm defined iteratively can be implemented via recursion (usually tail recursion). Using an explicit stack and the call stack is formally equivalent.

> That's not recursion, that's organization, modularity and all sorts of other descriptions. Where did you get these ideas?

From the same source as you. Namely the thing you quoted below:

>> Recursion occurs when the definition of a concept or process depends on a simpler or previous version of itself

I'd say that a "simpler or previous version of itself" would also be smaller since you're trying to get down to the base case.

Like if you're doing stuff with a binary tree, you usually have to consider the left and right subtrees, and whether they're empty or not, and if they're not empty, maybe do something like push a new entry onto a stack so that the left and right subtrees also get processed, and of course with them being empty being the base case.

The structure is inductively defined, which is why recursion is also the natural way to approach it. This of course being in contrast to dealing with codata and corecursion which deals with infinite structures.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#204

Earlier quoted context omitted.

Are you saying recursion creates "self sustaining computing blocks" ? What does that mean and how does it do it? that will thread themselves as see fit.* What does this mean? In a way it's more encapsulating than languages preaching encapsulation. Recursion does this? Are you talking about not depending on a stack structure in this specific instance or something else?

I'm trying to make my ideas clearer but I'm not sure it will be :) > Are you talking about not depending on a stack structure in this specific instance or something else? - using recursion involves functions as basic block of logic, that represent sub-parts of a domain - often creates finite set of self dependent functions - all you do is call and pass other functions, that will call each others > In a way it's more…

using recursion involves functions as basic block of logic, that represent sub-parts of a domain

That's what functions do.

often creates finite set of self dependent functions

This doesn't have anything to do with recursion.

all you do is call and pass other functions, that will call each others

You this doesn't have anything to do with recursion.

If you consider `map` or `fold` these create opaque functional processes that operate very automatically.

I think you mean, they iterate for you. They don't use recursion.

On the other hand in OO you needed (until very recently) to create iterators and empty structure to modify step by step.

I really don't understand all this. You know most iteration is done with a for loop right?

PEG parsing where the monadic flavor replaces an externally managed stack

This seems like you're trying to write a satire of someone soaked in haskell. Everyone is else is just writing for loops and moving on.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#205

If my memory is correct "no stack" was the way I was writing BASIC programs on my ZX81. 1 GOTO 30 10 LET C = A + B 20 RETURN 30 LET A = 1 40 LET B = 2 50 GOSUB 10 60 LET A = C 70 LET B = 3 80 GOSUB 10 90 PRINT C RUN 6 I was doing the job of the compiler in the article. Line numbers are memory addresses and the hidden variables are not hidden to me, because I'm the compiler. The only thing the interpreter made for me…

GOSUB definitely popped into my mind too. Nice to see the other reply that goes into a bit more detail and the part about showing the "stack" in screen-memory.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#206
post #203

Earlier quoted context omitted.

it's still recursion whether you're using the call stack or are using an explicit stack structure Recursion means defining something in terms of itself, so no, using a stack isn't recursion. The call stack of lots of different function calls in a normal program isn't called recursion either. the idea of having the problem solved by way of breaking it down into smaller problems. That's not recursion, that's organizati…

> Recursion means defining something in terms of itself, so no, using a stack isn't recursion. How can you differentiate between them? How do you account for these two things being isomorphic? Any algorithm that can be defined by calling itself can also be expressed by way of an explicit stack, just as any algorithm defined iteratively can be implemented via recursion (usually tail recursion). Using an explicit stack…

How can you differentiate between them?

One is a concept that means that something is defined in terms of itself, which why I linked you the definition. The other is a data structure where the first item in is the last item out. One is an abstract concept that isn't limited to computer, the other is an ordering.

Why do you think these two completely different things have anything to do with each other? You just keep saying they are the same for some reason. Repeating a claim is not evidence.

How do you account for these two things being isomorphic?

They aren't.

Any algorithm that can be defined by calling itself can also be expressed by way of an explicit stack, just as any algorithm defined iteratively can be implemented via recursion (usually tail recursion).

Being able to do something in a different way with a different tool doesn't make all ways and tools the same. I can hit a nail with a block of wood, that doesn't make the wood a hammer. You can use it as a hammer, but if you ask someone what it is they won't say it's a hammer.

Using an explicit stack and the call stack is formally equivalent.

My point above is that pragmatically they have a big difference, which is that it is easier to debug a static array stack because you can see the whole thing instead of having to walk through a call stack to figure out where the iteration went. It's also probably faster and simpler, but the point is the clarity and the ability to debug.

The structure is inductively defined

The structure is defined by the data. There is nothing "inductive" about it.

which is why recursion is also the natural way to approach it.

Again, what you are really seeing here is that the iteration of a tree matches the first in last out structure of a stack. Recursion just gives you a stack using the call stack, nothing more.

This of course being in contrast to dealing with codata and corecursion which deals with infinite structures.

This has nothing to do with what we are talking about.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#207

Earlier quoted context omitted.

I'm trying to make my ideas clearer but I'm not sure it will be :) > Are you talking about not depending on a stack structure in this specific instance or something else? - using recursion involves functions as basic block of logic, that represent sub-parts of a domain - often creates finite set of self dependent functions - all you do is call and pass other functions, that will call each others > In a way it's more…

using recursion involves functions as basic block of logic, that represent sub-parts of a domain That's what functions do. often creates finite set of self dependent functions This doesn't have anything to do with recursion. all you do is call and pass other functions, that will call each others You this doesn't have anything to do with recursion. If you consider `map` or `fold` these create opaque functional process…

> This doesn't have anything to do with recursion.

recursion is compressing the domain so small it eats itself, crafting a small set of function is mirroring this, kinda like grammars

> You this doesn't have anything to do with recursion.

> I think you mean, they iterate for you. They don't use recursion.

afaik map and fold were defined recursively

     ...
     foldl f z (x:xs) = foldl f (f z x) xs
albeit accumulative recursion (maybe that's what you mean by iterating)

> I really don't understand all this. You know most iteration is done with a for loop right?

that was what i was pointing at, iterators are not encapsulated enough

> This seems like you're trying to write a satire of someone soaked in haskell. Everyone is else is just writing for loops and moving on.

I'd appreciate if you didn't make it personal. Also, you forgot ocaml, lisp, prolog.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#209
post #203

Earlier quoted context omitted.

> Recursion means defining something in terms of itself, so no, using a stack isn't recursion. How can you differentiate between them? How do you account for these two things being isomorphic? Any algorithm that can be defined by calling itself can also be expressed by way of an explicit stack, just as any algorithm defined iteratively can be implemented via recursion (usually tail recursion). Using an explicit stack…

How can you differentiate between them? One is a concept that means that something is defined in terms of itself, which why I linked you the definition. The other is a data structure where the first item in is the last item out. One is an abstract concept that isn't limited to computer, the other is an ordering. Why do you think these two completely different things have anything to do with each other? You just keep…

> One is a concept that means that something is defined in terms of itself, which why I linked you the definition. The other is a data structure where the first item in is the last item out. One is an abstract concept that isn't limited to computer, the other is an ordering.

Well first of all, both are abstract concepts. In particular, in computer science stacks are considered to be an abstract data type with a push and a pop operation, which both act on the "top" element of the stack.[0] And I'm sure that you already knew this.

The fun part here is, of course, that stacks are also a type that can be defined via structural induction. I.e. that you either have an empty stack, or a stack where the top has an item and then below is another stack which contains the rest of the elements of a stack. So something like this:

  data Stack a = Empty | NonEmpty a (Stack a)
Of course, another way to refer to this kind of a structurally induced data type is to call it recursive. Now we're getting somewhere!

There's of course nothing magical about call stacks versus explicitly instantiated stacks. They're both particular manifestations of the abstract data type. Of course, you can easily argue that there's a difference since call stacks of course tend to get special support in hardware, but as per the original article, that was of course not always the case.

And this is not to mention that you can store "call frames" even with an explicit stack and then pop things off of the stack, until it's empty, in a loop. This is for example how usually recursive algorithms such as depth-first search are implemented in a more iterative manner. And this couldn't be recursion because... it's not written as a function calling itself? Do I understand this right? Because to me that feels like a quite arbitrary way to define what it means to "define in terms of itself".

> They aren't.

Well, I'm not going to ask for a full formal proof, but I would appreciate for some expansion of this point.

> Being able to do something in a different way with a different tool doesn't make all ways and tools the same. I can hit a nail with a block of wood, that doesn't make the wood a hammer. You can use it as a hammer, but if you ask someone what it is they won't say it's a hammer.

Well at least to me, it seems that the argument being put forward here is more that you can't use a block of wood to hammer a nail with, because a block of wood is not a hammer, and that you need to explicitly use a hammer to be able to hammer stuff.

> My point above is that pragmatically they have a big difference, which is that it is easier to debug a static array stack because you can see the whole thing instead of having to walk through a call stack to figure out where the iteration went. It's also probably faster and simpler, but the point is the clarity and the ability to debug.

Oh for sure, it's easier to inspect a stack if it's backed by an array. Of course one could also argue that this should be solvable by improving debugging tools, and making them better at showing call frames, but that's neither here nor there.

> The structure is defined by the data. There is nothing "inductive" about it.

Incorrect.[1] Trees are explicitly defined as inductive/recursive data types. And this isn't just the case for binary trees, but trees where you can have arbitrary amounts of children are defined like this, usually by invoking the concept of a "forest" which is a collection of trees.

> Again, what you are really seeing here is that the iteration of a tree matches the first in last out structure of a stack.

Well no, you could also go through a tree in a breadth-first traversal, in which case you wouldn't want a stack, but a queue, which is explicitly a FIFO. But yes, due to the structure of a tree, and it being a recursive data structure, of course you'd usually use recursion to iterate through it. And for that, you want a stack of some sort.

> Recursion just gives you a stack using the call stack, nothing more.

I don't disagree with this. If anything, it just serves my point. There's nothing special about using the call stack for this. Whether using the call stack or an explicit stack, you're still going through the tree in this case in way that takes advantage of its recursive nature of being defined as nodes with other trees as the nodes' children.

We could come up with an alternative name for this, if calling this general idea "recursion" feels odd, since you don't need to necessarily implement it as a function calling itself, but that doesn't fundamentally change the actual thing being done.

> This has nothing to do with what we are talking about.

Heh, fair enough. I just thought how it's interesting that both induction and corecursion deal with data by defining stuff from a base case and then expanding on that, but of course corecursion is based on coinduction, which of course goes the other way around, going from larger objects to smaller ones, which on the other hand is how recursion is usually thought of.

But yeah, not super relevant.

----

[0]: https://en.wikipedia.org/wiki/Stack_(abstract_data_type)>

[1]: https://www.cs.princeton.edu/courses/archive/fall21/cos326/l...>

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#210
> How did you call a function if you didn’t have a stack for the return address or local variables? Here’s how it worked. First, the compiler defined a secret global variable for each inbound function parameter, plus another secret global variable for each function to hold the return address. It also defined a secret global variable for each of the function’s local variables.

I always assumed that functions did something like this behind the scenes. At the lowest level, all of memory is just a very large, globally-accessible array of numbers. Functions are just JMPs with fancy syntax sugar.

...is that completely wrong? Do modern computers actually have some type of hardware support for functions?

Post reply on HN