-
-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make an RFC for multiple values #78
Comments
Can we have tuples instead? |
Yeah, that's the idea. Multiple values, as I understand, is only needed for performance (https://www.cs.indiana.edu/~dyb/pubs/mrvs.pdf). But if we are going to have some kind of static analysis (see #57), it might be possible to make returning a tuple as efficient as multiple values. |
How does Haskell deal with returning tuples? |
My personal taste is that multiple values are theoretically beautiful and elegantly expose the ability of an advanced compiler to use a custom calling convention, but are extremely annoying to actually use in almost all circumstances in Scheme & Racket, for the reasons you mention. |
I think Racket 2 would be simpler to use if it didn't require anyone to know about multiple-value return. It could still exist if it's needed for performance, but it can be relegated to a specialty thing. Thinking about it in more of a positive brainstorming way, as in "Is there anything better that we can do?" I have several ideas. To start with, procedures could have the same output convention as their input convention:
To make this easier to work with, "multiple values (with possible keyword arguments)" (which we might dub a "values-and-keywords collection") could be the primary first-class value type of the language. Variables would abstract over that concept the same way expressions do:
With this infrastructure in place, some quality-of-life conveniences would suggest themselves:
I have no idea what effect any of this would have on performance, nor how severe everyone's headaches would be as they tried to maintain each other's clever nested values-and-keywords data structures. :-p So I don't necessarily support it, but it's a fun thought experiment. |
Check out this code from over nine years (don't actually know when I wrote it because I had this code outside of git back then): https://github.com/jeapostrophe/exp/blob/master/values.ss (plus the racket update: https://github.com/jeapostrophe/exp/blob/master/values.rkt ) --- It implements a bunch of these ideas, although not as consistently as you suggest. I don't really support this idea either, but it is useful to write down and think about. |
Whoa, nice! :) |
I think there's another cost to supporting multiple values: it encourages people to group values together in a semantics-free way, instead of creating a |
Whoa, let's slow down a little. Do we need multiple return values? Not to the extent that Racket wouldn't be Turing-complete if we removed them, but they are so convenient I consider them indispensable. I'll outline just one way multiple return values make my life easier, but it's a big one. Racket conceptualizes function invocation as the application of a procedure to an argument list. Since lists can have any number of elements, this perspective leads naturally to multivariate functions—such as thunks or functions with rest-args—and multivariate functions compose neatly with multiple return values. There are useful symmetries between Take the totem from my RacketCon talk slides, which looks something like this in Racket:
This pattern crops up everywhere when I'm using a compositional style. Algebraic Racket is designed around these functions, to support function composition as a deliberate programming style. My code is so much cleaner now, I'd need a good reason to go back. Here's a real example, in the event monad:
A direct translation to Racket would look like this:
Every line examines, generates, or brings together multiple arguments and multiple return values. Several lines do more than one of these things. Every More precisely,
For an expression this simple,
Depending on the context, either of these might be "better" for some definition of "good." Algebraic Racket makes this sort of multi-valued wizardry mundane, so I'd usually try a few and pick the simplest one. Directly translating the last two examples, I get:
And if we include the values monad, I have a few more options:
|
I'm glad you chimed in, @dedbox! It's good to hear from someone seriously using multiple-value return for point-free composition. Based on your refactorings of this example, it looks like it could even be written That said, in a language without multiple-value return, the design of |
I concur with @rocketnia. In particular, every time you use Here's one way to translate code with
(also, if we wish, To lift from "bare" value to multiple values, simply apply Assuming that
The only thing that we will lose is the "implicit one value" ability. E.g., we won't be able to write |
Nice. It works!
I don't follow. How does Racket know
Not a die-hard fan of point-free style, but I'm happy to use it when it gives good results.
I am pathologically pragmatic, so my techniques will adapt to whatever reality we find ourselves in. There are always trade-offs, so I'll delay speculating until I have a more concrete understanding of what you're proposing.
No, not really. Not in any meaningful sense.
Definitely not. It's usually because I want to map, filter, fold, or apply something across an argument list, as opposed to just one of its elements. It is an artifact of multiple arguments, not multiple return values. Would you feel different if we replaced And besides, capturing multiple return values is as easy as
I don't understand where you're going with this. The most prominent feature of multiple return values is that you can largely ignore them when you don't need them. If you're proposing every function have exactly one argument, you can already do that without any hardcore macro-fu. Go make a #lang univariate/racket/base for this perspective and tell me how it goes.
Not without some implicit notion of laziness, or am I missing something? If |
What I'm saying is that in a language without multiple-value return, whoever authored If it returned something more meaningful than a list (such as a struct, as @jackfirth is talking about), we could pass it to a more meaningful function than |
But this doesn't eliminate the burden of discerning multiple arguments from a lone argument that happens to be a list. It merely shifts the burden away from the platform and toward the programmer in the form of ad hoc calling conventions and extra ``glue'' syntax. One way to build a reference interpreter is to start with a single-argument lambda calculus with pairs, then use it to bootstrap into multiple arguments and more structured data. The tutorial series included with Algebraic Racket documents this technique. It even covers a syntax extension that exploits the ambiguity between list arguments and argument lists to provide first class pattern-based macros with the bare minimum parentheses. All that is to say I have designed and implemented, on top of Racket, many languages with the sort of syntax you're proposing, and it isn't all roses. The devil is always in the details.
While I'm generally sympathetic to Jack's desire to name all the things, I'm not buying the argument that structs with named fields are inherently more meaningful than lists. I mean, you can already do this today—with calling conventions and extra ``glue'' syntax. There's nothing stopping you from implementing a whole parallel universe where every function accepts and returns, say, a single hash. I studied a spectrum of variations on this theme throughout my CS Masters program. In practice, I think you'll find not everything needs a name, and too many names can be just as disorienting as too few. |
Here's an optimization in Haskell:
|
General principals question. If the underlying runtime supports multiple values, or any language feature for that matter, should a language built on top of it have a construct that allows one to make use of that functionality? If the answer to this question is no, then don't we still have to require that all compliant runtimes support that feature anyway due to of the requirement for interoperatilbity with existing Racket? |
I don't think so, no. A language may do so, but I don't see any reason a language should or must.
Yes. But for the most part, Racket2 RFCs aren't about the runtime. They're about the surface APIs. At least, that's been my assumption so far. |
Do we need multiple values? If so, what's the best way to make it integrate well with the language? Right now, it's not.
As an example, if I have a function
f
that returns two values, and I want only the first returned value to get passed to functiong
, the best I can do is:Is there anything better that we can do?
The text was updated successfully, but these errors were encountered: