If your language has block expressions, why not use block expressions for all grouping! be free, parentheses! `{ x + 10 } * y`. parens can be unambiguously tuples, just like they always wanted.
@dotstdy I showed this to @ianh and he managed to temporarily convince me (without necessarily holding this opinion himself) that function calls and tuples should use `[...]` instead of `(...)`, and then `(...)` can be used for blocks, after which your same idea applies. Were Lisp M-expressions right all along?

@zwarich @ianh instead of using `[..]` for calls you can use the Lua trick of having functions accept an anonymous structure. `do_stuff { 1, 2, 3 }`

Thus `(..)` is grouping / block, `{..}` is a struct / tuple, and `[..]` is an array.

now we're cooking

@dotstdy @ianh Has any recent language adopted uncurried single arg style where product types are used for multiple args rather than implicit currying? I know that Swift originally started in this direction, but I forget the actual reason why they backed away from this. Can you refresh my memory, @joe ?

@zwarich @dotstdy @ianh @joe Related, but why isn't first class multiple return values a thing more often?

For example, the C++26 standard library has senders/receivers which essentially work via continuations, so you can write async functions that take N inputs and M outputs natively without resorting to tuples. You can even send outputs of different types without having to box them into a sum type (because they just statically dispatch to different overloads).

@foonathan @dotstdy @ianh @joe My guess has always been that first-class multiple return values are overlooked because tuples serve their use case fairly well. When I think of use cases for them, my mind always goes to alternate ABIs like returning bools in status flags, etc.
@zwarich @foonathan @dotstdy @ianh I’m finding that ownership and lifetime dependent values really stretch the equivocation between tuples and multiple values. a tuple of borrows can’t always be used as a borrowed tuple, for instance

@joe @foonathan @dotstdy @ianh I think most languages that would allow you to return borrows would let you also store them as struct fields, etc. and just add a borrowed pointer type, in which case you could just make a tuple of borrows. I guess you could take a purist "parameter modes" approach and define borrowed struct fields via parameter modes on the struct's constructor? However, I think it might be tricky to make this work well with generics.

Another place where a similar distinction comes up is in-place construction of return values. Rust doesn't have this, but I assume that some successor language will want this to support internal self-reference, e.g. for efficient containers with inline capacity

@zwarich @foonathan @dotstdy @ianh yeah return value emplacement was the other thing i had in mind where a tuple (in its naive unexploded representation) isn't the same thing as multiple values.

even with first-class borrows, the way swift tries to allow for tuples to be magically exploded and imploded by the implementation fights against the very concept of a borrow-of-tuple ever existing, since you really want a contiguous representation for that borrow to refer to

@joe @foonathan @dotstdy @ianh Another funny realization is that if all return values are returned by writing to a passed reference, then you could have functions with no actual return values and only out-params (with the appropriate pointer type that must be written before returning). It's the use of resources like registers (which may be implicitly used by other code in the function) that necessitates presenting a value at the point of return.

You could take this to the next level and actually have out-params that "steal" registers when written to, but at that point you're probably in meme language territory.

@zwarich @foonathan @dotstdy @ianh hylo apparently runs with that out-params-only idea. it doesn't sound totally unworkable

@joe @foonathan @dotstdy @ianh Hylo seems to have ordinary results (https://hylo-lang.org/docs/user/language-tour/functions-and-methods/) while also having `inout` and `set` (must be uninitialized by caller and initialized by callee) params.

I feel like requiring this style everywhere would be fairly unpopular, just based on the observation that expression-based languages have only become more popular in the past 15 years. It is very common to take multiple inputs and produce one result, and this is roughly the same ergonomic advantage of natural deduction over the sequent calculus.

Maybe it would make sense for something that is more like a portable high-level assembly language?

Functions and methods

Hylo
@zwarich @foonathan @dotstdy @ianh one of dave a’s white whales was coming up with a notation to unify the imperative and functional forms of any operation, so you can have the ‘+=‘ given any ‘+’ or vice versa. since inout is isomorphic to a matched parameter-return, and set to a return, it seems like you could say only multiple parameters and returns really exist, but you have notation for binding a location as the place to emplace a result, or in-place-update
@zwarich @foonathan @dotstdy @ianh with clever calling convention rules, maybe you could collocate the parameter registers/memory with same-typed result reg/mem so that functions can naturally in-place-update
@zwarich @foonathan @dotstdy @ianh clay had this, come to think of it. you could write `pointer <-- expr();` and it would use the memory of `pointer` as the return address for the value of `expr()`. and on the flip side, you could declare functions with names for the return slot addresses. it was all YOLO there, but it shouldn't be too hard to make it safe with definite initialization analysis
@joe @foonathan @dotstdy @ianh Did Clay have unmovable values? What would happen if you did this for a function call returning a scalar? One of the big things that killed the momentum of any sort of placement semantics in Rust is that they didn't have real situations where it caused a meaningful semantic difference (although I head long ago that there is unsafe code in usage that depends on the exact sequence of copies made on the function return path).
@zwarich @foonathan @dotstdy @ianh Clay is C++ish in that you can have any wacky combination or lack of rule-of-five operation (though at least move was destructive). Clay has all functions return indirectly, so the placement syntax really does just bind the indirect return argument directly
@joe @foonathan @dotstdy @ianh Is the new version of Clay actually going to be released as open source at some point?
@zwarich @joe @foonathan @dotstdy @ianh There’s a new version of Clay?

@slava @joe @foonathan @dotstdy @ianh https://claylabs.com/clay/

Clay is as efficient as C. It features fast compiles, powerful meta-programming, and excellent support for modular programming. All of our software (at Clay Labs) is written in Clay.

If you're interested in checking out an extremely old version of Clay (circa 2011), see https://github.com/jckarter/clay. Clay has been rewritten several times since then. We plan to release the new version of Clay as open-source in the future.

Clay Programming Language

@zwarich @foonathan @dotstdy @ianh you’ll have to ask @kssreeram about that. last I talked to him, it sounds like he’s waiting till he’s got it perfect
@zwarich @foonathan @dotstdy @ianh troll answer would be to say that it’s at zig lang dot org