11
$\begingroup$

I'm working on a stack based lang that has "wraps", essentially lambdas, that by default run with an isolated stack (though some commands can push things to a wrap's stack, effectively partially applying it)

Is this common design, or do lambdas usually share the same stack as the code that calls them?

Would there be a point in adding syntax for a wrap that captures (duplicates into its own stack) automatically some of its surroundings, and how could it look like?

For reference, wraps look like (commands...), so like the first 10 even numbers are 1 10 range (2 *) map, but I think this question extends to all languages that try to combine stack-based and functional concepts.

I'm essentially asking for suggestions on syntax for duplicating part of the outside stack inside a lambda's own stack, as well as the meta-question of if that is even needed or if 7 (+) flip bind is enough.

$\endgroup$
1
  • 3
    $\begingroup$ Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. $\endgroup$ May 17, 2023 at 5:41

2 Answers 2

6
$\begingroup$

This answer comes from an experience of using stack-based golfing languages like 05AB1E and Vyxal - a programming language I've made.

Both isolated and outer stack usage have their advantages and disadvantages, but I'll give my experience with both:

Using an isolated stack

Generally speaking, isolated stacks are more useful - it's most analogous to how functional programming works (you get the value you're mapping/filtering/whatvering over as a neat reference) and makes sure that the outer stack doesn't get inadvertently modified. It also means that there's no unexpected behaviour if an operation uses up all references of the initial item - it can simply error out (or do some other behaviour like re-push the current iteration item).

Using the outer stack

Sometimes, there are situations in which you want to succinctly refer to values in the outer stack (whether they be inputs you've pushed or some other values you've calculated). However, this approach requires more explicit stack management, as well as caution to make sure the outer stack does not have too many values taken off.

Recommendation

Have wraps/lambdas work on an isolated stack by default. However, do give the option for wraps to work on the outer stack.

For example:

1 10 range (2 *) map

Would use an isolated stack while

1 10 range >(2 *) map

Would use the outer stack. The >( would mark that the outer stack values are being piped into the wrap.

$\endgroup$
7
  • $\begingroup$ For another example, you might want to check out Vyxal 3's literate mode, which has similar aspects to what's been described here. The syntax for a lambda to use the outer stack is lambda stack! | code end and an isolated stack lambda is lambda code end or {code} $\endgroup$
    – lyxal
    May 17, 2023 at 5:54
  • $\begingroup$ would an outer stack wrap be linked to the stack where it was first pushed to, or just read whatever is around? I currently have two ways to call wraps, call, that allows to read whatever is around when popping from an empty inner stack, and call! (what's used internally in, say, map) which disallows leaking implementation details by failing when popping an empty inner stack. How would these combine with these options? Would 7 1 10 range >(pop io.println 0) map not leak implementation details, instead of the maybe expected printing 7 ten times and then pushing a list of 10 zeroes? $\endgroup$
    – RubenVerg
    May 17, 2023 at 6:09
  • $\begingroup$ Well in the example code you give, assuming your language errors on popping from an empty stack, only one 7 would be printed. The function would read from the stack(s) it's called in $\endgroup$
    – lyxal
    May 17, 2023 at 6:11
  • $\begingroup$ If it was something like 7 1 10 range >(dup io.println 0) map it would print 7 ten times and return a list of 0s. That's generally what I would expect from a stack language/functional programming $\endgroup$
    – lyxal
    May 17, 2023 at 6:14
  • $\begingroup$ right, so >() should copy the outer stack somewhere else inside the function? I wonder how nested >()s would play with each other. What would >(pop) 7 bind do? Should it even be allowed? $\endgroup$
    – RubenVerg
    May 17, 2023 at 6:22
5
$\begingroup$

The Factor approach

Factor doesn't give functions an isolated stack, but it still has enough control to prevent functions from clobbering (accidentally or maliciously) the stack. A function must declare the number of input and output arguments it takes (and, if any of those are themselves functions, it must also declare their arity).

:add1 ( x -- y )
  1 + ;

Basically, Factor has a really weak static type system where the only types are "any value" and "function from X to Y" where X and Y are lists of (possibly higher-order function) types.

In this way, if you want to politely peek at a value from the outer stack (such as inside of a list map if you know what's immediately beneath you on the stack), you can do so, but you can't destroy it or push additional data on, since the arity of each function is still checked.

Factor also provides curry, which sounds like your bind and comes in handy when you're doing more complex stack manipulation, but often you can get by without it.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .