Hacker News new | past | comments | ask | show | jobs | submit login

The world has been changing the shapes of our hands since they were fins.

The problem I have with most language's function call syntax (except for point-free stack based languages like FORTH and PostScript) is that you can have multiple fingers on your "in" hand, but only one finger on your "out" hand. C#'s in/out/ref modifiers and Lisp's multiple-value-bind are hacks.

https://en.wikipedia.org/wiki/Tacit_programming

FORTH's /MOD ( numerator denominator -- remainder quotient ) naturally takes two integer inputs and returns two integer outputs, and it doesn't use need any special clumsy syntax to express that.




> The problem I have with most language's function call syntax (except for point-free stack based languages like FORTH and PostScript) is that you can have multiple fingers on your "in" hand, but only one finger on your "out" hand.

I dunno, in many modern languages that aren't point-free stack-based languages you can (and the difference between these is one of perspective more than concrete substance, arguably) either have multiple “fingers” on either hand or only one on each, but the one value each touches can be arbitrarily structured and destructured.


That same logic can be used to argue that functions should only support one input argument, too. If you can have multiple inputs, then what's wrong with multiple outputs? And if multiple outputs are so bad, then why have multiple inputs?

There's a big difference between simply and efficiently returning multiple values on the stack without generating intermediate garbage and memory references, and packing multiple values up into a single tuple, polymorphic array, structure, or class, and then destructuring it later, or passing input parameters that are indirect pointers to temporary output locations in linear memory.

Using indirect pointers for output parameters can also cause bugs and performance optimization problems with aliasing.

https://en.wikipedia.org/wiki/Pointer_aliasing

To elaborate what I said: C#'s in/out/ref and Lisp's multiple-value-bind syntax are clumsy, inelegant, and inefficient hacks.

And languages like Java that don't have pointers can't even do that, and just have to proliferate pointless container classes and generate garbage intermediate objects. Quick: How do you implement swap(a, b) in Java? (Without using an IntermediatingObjectSwapperDependencyInjector- ProxyThunkingShadowEnumeratorComponentBeanAdaptor- DecoratorReferenceRepositoryServiceProviderFactoryFactory!)

http://www.javapractices.com/topic/TopicAction.do?Id=37

https://stackoverflow.com/questions/1403921/output-parameter...

https://stackoverflow.com/questions/3624525/how-to-write-a-b...

With WebAssembly, for example, returning multiple values has a significant effect on performance and code size, because it's much more costly to return multiple values indirectly through linear memory than on the stack. (The stack is in a separate address space that you can't point to like linear memory.)

That's why there's an active multiple value return proposal for WebAssembly, which is implemented in Chromium release 80:

https://www.chromestatus.com/feature/5192420329259008

https://hacks.mozilla.org/2019/11/multi-value-all-the-wasm/

>But Why Should I Care?

>Code Size

>There are a few scenarios where compilers are forced to jump through hoops when producing multiple stack values for core Wasm. Workarounds include introducing temporary local variables, and using local.get and local.set instructions, because the arity restrictions on blocks mean that the values cannot be left on the stack.

>Consider a scenario where we are computing two stack values: the pointer to a string in linear memory, and its length. Furthermore, imagine we are choosing between two different strings (which therefore have different pointer-and-length pairs) based on some condition. But whichever string we choose, we’re going to process the string in the same fashion, so we just want to push the pointer-and-length pair for our chosen string onto the stack, and control flow can join afterwards. [...]

>This encoding is also compact: only sixteen bytes!

>When we’re targeting core Wasm, and multi-value isn’t available, we’re forced to pursue alternative, more convoluted forms. We can smuggle the stack values out of each if and else arm via temporary local values: [...]

>This encoding requires 30 bytes, an overhead of fourteen bytes more than the ideal multi-value version. And if we were computing three values instead of two, there would be even more overhead, and the same is true for four values, etc… The additional overhead is proportional to how many values we’re producing in the if and else arms. [...]

>Returning Small Structs More Efficiently

>Returning multiple values from functions will allow us to more efficiently return small structures like Rust’s Results. Without multi-value returns, these relatively small structs that still don’t fit in a single Wasm value type get placed in linear memory temporarily. With multi-value returns, the values don’t escape to linear memory, and instead stay on the stack. This can be more efficient, since Wasm stack values are generally more amenable to optimization than loads and stores from linear memory.


> That same logic can be used to argue that functions should only support one input argument, too.

Some languages do (more if you consider things that support what looks like multiple arguments but which the complete set of arguments that can be passed to a function corresponds directly to a single data structure in the language.)

> There's a big difference between simply and efficiently returning multiple values on the stack without generating intermediate garbage and memory references, and packing multiple values up into a single tuple

Fundamentally, there's not, since you have to represent both the number of items and each item either way. It's true that there are more and less efficient means of performing the task, but the information required is identical, so it is quite possible for any method capable of doing what looks like one to a user of the language to implement what looks like the other from the same perspective.

The obvious implementation when conceptualized each way given other elements of a language or it's implementation design may be different, but that's not an inherent difference, and implementing efficiencies in the implementation has no necessary reflection in language-level features (and supporting any particular language-level feature isn't a guarantee of efficient implementation.)


> only one finger on your "out" hand

I think Python's way isn't amazing, but it's not too bad either:

    d, r = divmod(100, 3)
It's a bit uglier in C++, but you could make this work:

    int d, r;
    refs(d, r) = divmod(100, 3);
I'm certain there's either a Boost or STL library for this. (I wrote my own for C++98 at one point)

It's clear you like stack based languages, and I certainly admit they can be concise.


Yes, I think how Forth and PostScript does that is good, compared to the other ways (there are other good things about Forth, too). And then, in assembly language, it may depend what instruction set is used and how call frames are organized; Glulx allows only one return value, and same with Z-machine code.


There's a proposal to support multiple value returns in WebAssembly!

Multi-Value All the Wasm (hacks.mozilla.org)

https://news.ycombinator.com/item?id=21596965

https://hacks.mozilla.org/2019/11/multi-value-all-the-wasm/




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: