No doubt multiple return values are highly useful, especially in writing pure functional code.
However, multiple-values give rise to some difficulties. For example, the functional identity can no longer be defined as (define (id x) x) if we want it to be the unit of the functional composition. Instead, we have to (define (id . args) (apply values args)) The functional composition becomes more complex, too.
Oftentimes we wish for a function (trace "some-string" val) that merely propagates the "val" to its caller. The utility of such a function is in its side-effect, e.g., printing a message:
(define (trace title arg) (display title) (newline) ; perhaps print the timestamp as well arg) ; propagate the arg
The function can be used as (generate-reply (trace "got request" (get-request)))
This pattern of propagating a value is quite widespread: it occurs in procedures such as "call-with-input-file" (which must close the port before passing the value returned by its body) and forms such as dynamic-wind. The presence of multiple values noticeably complicates the definitions of such procedures:
It is interesting to note that in these situations we often have to specifically reify multiple values as a list.
The function 'values' itself is more complex than it seems. [in the following, we mean the function values applied to more or fewer than 1 argument] Perhaps 'values' the only R5RS function that does not yield a value. The "result" of 'values' is not first-class. BTW, R5RS specifically says that an "unspecified" result is still a first-class value. The only other function whose result is not first-class is 'error' (or 'abort', following SRFI-12).
The similarity between 'values' and 'error' runs deeper. One can say that neither of these functions actually return. That is, they do not invoke their 'natural' continuation. The function error invokes the error continuation, which is always present (often implicitly). The function 'values' invokes a multiple-value continuation, which must be established by a distinguished 'call-with-values' procedure. The implementation of 'values' in R5RS corroborates this interpretation. We can imagine 'values' throwing a special kind of exception, which is trapped in the handler set by 'call-with-values'. The multiple-values "exception" is a peculiar one: it can't percolate so it must be caught right away.
> *ML allows you to pass arguments as first class tuples, but it's more > common to *not* do this, but to instead have each function take a single > argument and return another function that is then applied to the next > argument.
> (define quadratic > (lambda (a) > (lambda (b) > (lambda (c) > (lambda (x) > (+ (* a x x) (* b x) c))))))
> *ML has syntactic sugar so you can just write something like...
Matthias Blume <matth...@shimizu-blume.com> writes: > [...] Personally, I have come to prefer languages that lets in > neither, i.e., strict single-argument single-result functions, with > a first-class tupling mechanisms that give you the expressive power > of multiple arguments/results. Static analysis (e.g., type > checking) can often effectively be used to eliminate the overhead of > creating actual tuples.
I don't want to sound like this is c.l.l, but it looks like this carries zero information -- people who haven't tried it probably don't know what you're talking about here, and people who do must have some good reason to use Scheme.
(Personally, I have *really* tried working with ML, but I have come to realize that no matter how hard I try to do that, a dynamic language is always more fun to use.)
> > *ML allows you to pass arguments as first class tuples, but it's more > > common to *not* do this, but to instead have each function take a single > > argument and return another function that is then applied to the next > > argument.
> This criterion of elegance can be pushed even farther -- why bother > distinguishing arguments from return values? Mathematically, a function > is just a special case of a relation, and in the realm of programming > languages this perspective is taken by Prolog.
Loosely speaking, a function f is a relation between elements of two different sets (domain(f) and image(f)) with the particularity that each element of the first set (x in domain(f)) is related to only_one element of the other set ( f(x) in image(f)).
> But the tradition of functions as things with multiple arguments and > one return value is about 1000 years older than this more abstract > definition (and even more older than the one-argument-function lambda > calculus), and it clearly (imho) captures something really useful > and rich, even today. Multiple-return-value functions don't feel like > functions to me.
Multiple return value is still a function but with a composed set image(f). For instance, f = (lambda (x1 x2 x3) (values (* 2 x1) (/ x2 x3)) is just a function with : - domain(f) = SCM^3 - image(f)=SCM^2 with SCM being the set of scheme values. But for each input in domain(f), it returns one output in image(f). Hence, - if f returns a single composed value for each input => f is a function (the case with the "Multiple-return-value functions") - if f returns a list of values (being composed or not) => f is not a function but a more general relation (f doesn't feel like a function because it isn't). In this case, the returned list can be of different size at each call. This may not be useful at first sight but is heavely used in relational databases.
Anyway, I find the multiplevalue stuff mathematically nice but its syntax is ugly. What would be nice is to be able to write: (define (f x y z) ... (values v w)) (define (g x y) ... (values s)) with (g (f 3 4 5)) or (g 4 6) being to possible calls
George Caswell <sieg_h...@attbi.com> wrote: > I did explain my current (limited) understanding of multiple return > values. About the only reason I could fathom for its existence was in > order to make the representation match the intent. I humbly suggest that, > in order to make your responses useful, you pay attention to what you're > reading.
I did not understand your attempted explanation (which looks to me like part of a sentence), so I did not try to answer that part. Now that I see you seem to enjoy abusing those whose replies do not meet your exacting completeness standards, I regret following up at all. I will try not to make that error again and humbly apologise for offending you.
MJ Ray wrote in message ... >Dorai Sitaram <d...@goldshoe.gte.com> wrote: >> There is already the procedure apply that can do the >> unpacking without let, car, cdr, list-ref, etc.
>I always seem to take a performance hit when using apply. Has anyone got >benchmarks of the two ways round?
There is a paper by Kent Dybvig ("Efficient Implementation of Multiple Return Values in Scheme" (or so)), which compares the performance of different programming styles (values, CPS, lists, etc.). Should be available at readscheme.org.
Brad Lucier reported timings for a benchmark of the SRFI-25 reference implementation where native multiple values[*] showed to be about 5 times faster than multiple values simulated with lists/apply
cheers, felix -- [*] with a preliminary release of Gambit (4.0)
Jeffrey Siegal wrote in message <3CB363BC.241B3...@quiotix.com>... >Barry Margolin wrote: >> As others have mentioned, it allows for some runtime optimization.
>I wouldn't say it "allows" optimization, since optimization is always >allowed. It might make optmization somewhat easier, but even that seems >questionable.
I do not understand. Do you mean optimizing for multiple values is not automatically easier than optimizing for a list-hack? I don't think so (see Dybvig's paper).
>> In fact, when it fits the situation well, it saves some "noise work" with >> let, car, cdr, list-ref etc and allows the real method to shine through. My >> Opinion Only.
>You call the above "saving 'noise work'"?!? You gotta be kidding!
>Plus, I could already do all this without ary let, car, cdr, or such:
"felix" <felixundd...@freenet.de> writes: > MJ Ray wrote in message ... > >Dorai Sitaram <d...@goldshoe.gte.com> wrote: > >> There is already the procedure apply that can do the > >> unpacking without let, car, cdr, list-ref, etc.
> >I always seem to take a performance hit when using apply. Has anyone got > >benchmarks of the two ways round?
> There is a paper by Kent Dybvig ("Efficient Implementation of Multiple > Return Values in Scheme" (or so)), which compares the performance > of different programming styles (values, CPS, lists, etc.). Should > be available at readscheme.org.
> Brad Lucier reported timings for a benchmark of the SRFI-25 reference > implementation where native multiple values[*] showed to be about 5 times > faster than multiple values simulated with lists/apply
Well, that just makes the point very clearly: multiple values are a *performance* hack.
Eli Barzilay <e...@barzilay.org> writes: > Matthias Blume <matth...@shimizu-blume.com> writes:
> > [...] Personally, I have come to prefer languages that lets in > > neither, i.e., strict single-argument single-result functions, with > > a first-class tupling mechanisms that give you the expressive power > > of multiple arguments/results. Static analysis (e.g., type > > checking) can often effectively be used to eliminate the overhead of > > creating actual tuples.
> I don't want to sound like this is c.l.l, but it looks like this > carries zero information -- people who haven't tried it probably don't > know what you're talking about here, and people who do must have some > good reason to use Scheme.
I'd prefer it if you'd speak for yourself only. I am pretty sure that there are people who do know what I am talking about. The above isn't that difficult to understand. (If you think that I simply made a thinly-veiled reference to ML and want the above to be read "I prefer ML", then you are wrong. I intentionally did not mention the concrete instance ML of the broader class of languages that I would prefer as far as their handling of arguments and return values of functions is concerned.)
> (Personally, I have *really* tried working with ML, but I have come to > realize that no matter how hard I try to do that, a dynamic language > is always more fun to use.)
Well, different strokes for different folks. Anyway, this was not at all about static vs. dynamic, so I don't quite see why you need to bring it up.
Matthias Blume <matth...@shimizu-blume.com> writes: > > > [...] Personally, I have come to prefer languages that lets in > > > neither, i.e., strict single-argument single-result functions, > > > with a first-class tupling mechanisms that give you the > > > expressive power of multiple arguments/results. Static analysis > > > (e.g., type checking) can often effectively be used to eliminate > > > the overhead of creating actual tuples.
> I'd prefer it if you'd speak for yourself only.
You did not even mention pattern matching to destruct the touple. (And my guess was that anyone who'll know how static analysis can prevent creating these touples belongs to that crowd that knows this already.)
> (If you think that I simply made a thinly-veiled reference to ML and > want the above to be read "I prefer ML", then you are wrong.
Substitute `ML' with `languages that have only strict single-argument single-result functions' and that use `Static analysis to eliminate overhead'. The ML reference was my theoretical mistake that came out of the practical reality where you posted something about ML in the same neighborhood.
But all this is pointless meta-junk, the only thing I wanted to say is exactly this:
> Well, different strokes for different folks.
... so what's the point of tempting people to reduce the S/N ratio?
> Anyway, this was not at all about static vs. dynamic, so I don't > quite see why you need to bring it up.
(Yeah well, the standard approach would be to say that: if multiple return values are a hack, but without them you have a non-symmetrical situation that pushes you to forbid multiple inputs, leading to unbearable Scheme overhead for almost every function call, which can be fixed by using static type analysis and pattern matching, then my reply is that giving up dynamisms that will follow is a way too high price. etc etc etc.)
Eli Barzilay <e...@barzilay.org> writes: > Matthias Blume <matth...@shimizu-blume.com> writes:
> > > > [...] Personally, I have come to prefer languages that lets in > > > > neither, i.e., strict single-argument single-result functions, > > > > with a first-class tupling mechanisms that give you the > > > > expressive power of multiple arguments/results. Static analysis > > > > (e.g., type checking) can often effectively be used to eliminate > > > > the overhead of creating actual tuples.
> > I'd prefer it if you'd speak for yourself only.
> You did not even mention pattern matching to destruct the touple. > (And my guess was that anyone who'll know how static analysis can > prevent creating these touples belongs to that crowd that knows this > already.)
Strictly speaking, you don't need any pattern matching. In this case it would just be syntactic sugar. For conciseness of notation, it would definitely help, though.
> > (If you think that I simply made a thinly-veiled reference to ML and > > want the above to be read "I prefer ML", then you are wrong.
> Substitute `ML' with `languages that have only strict single-argument > single-result functions' and that use `Static analysis to eliminate > overhead'.
Such languages have a bit of an easier time with first-class multiple values because they *already* do type analysis for other reasons and therefore can piggy-back on that. But the analysis used in, e.g., Scheme, does not have to be a type analysis in the usual sense. Instead, it could be some fairly straightforward CFA, or soft typing, etc. (Actually, those really all boil down to the same thing: CFA.)
> > Well, different strokes for different folks.
> ... so what's the point of tempting people to reduce the S/N ratio?
The point is that call-with-values and values are "features piled on top of features" -- something that does not give you any additional expressive power, in fact, something that is strictly less expressive than *another* language feature that is *already* in the language. The only thing it has going for itself is the fact that naive implementations can optimize it a bit more easily -- hence the word "performance hack". As I have showed in a previous article, there are situations where the hack actually amounts to a pessimisation. In terms of pragmatics, it is definitely a pessimisation because I need to contort all kinds of otherwise clean code to account for the possibility of multiple values.
I do not understand why you think that discussing this is reducing the S/N ratio. CWV and VALUES go very much against the spirit of the design principles that Scheme is so proud of.
> > Anyway, this was not at all about static vs. dynamic, so I don't > > quite see why you need to bring it up.
> (Yeah well, the standard approach would be to say that: if multiple > return values are a hack, but without them you have a non-symmetrical > situation that pushes you to forbid multiple inputs, leading to > unbearable Scheme overhead for almost every function call, which can > be fixed by using static type analysis and pattern matching,
It can be fixed by a clever compiler, doing *some* kind of compile time analysis. Compilers (even -- or should I say, *especially* those for Scheme) do all kinds of other clever static analyses, too. Static typing at the level of the surface language is not required for this (although it helps if you already have it). Pattern matching has absolutely *nothing* to do with performance one way or the other here.
By the way, the "unbearable" overhead does not have to be all that "unbearable" even in an implementation that does not do any sophisticated static analysis:
Suppose we have the following semantics of abstraction and application:
1. Abstraction:
(LAMBDA (x) <body>) -- takes the function's single argument, binds it to X and evaluates <body> (LAMBDA () <body>) -- takes the function's single argument which must (dynamically) be of type "tuple" with zero components (aka "unit"). Evaluate <body>. (LAMBDA (x1 ... xN) <body>) -- takes the function's single argument which must be of type "tuple" with N components. Bind component i to xi for all i \in 1 .. N. Evaluate <body>.
If we assume that there is a form expressing projection for tuples, say (project <tuple> <i>) gives you the <i>th component of <tuple>, then the second form is syntactic sugar for
(LAMBDA (arg) <check type of arg to be 0-tuple> <body>)
and the last form is syntactic sugar for
(LAMBDA (arg) <check type of arg to be N-tuple> (let ((x1 (project arg 1)) ... (xN (project arg N))) <body>))
Notice that techinally all LAMBDA forms evaluate to procedures taking a single argument.
2. Application:
(<function> <arg>) -- evaluates <function> and <arg>, <function> must be a procedure, <arg> can be anything. Call <function> with <arg> as its single argument.
(<function>) -- evaluates <function>, result must be a procedure expecting a "unit" (see above) argument. Call it with a unit value (empty tuple) as its single argument.
(<function> <arg1> ... <argN>) -- evaluates <function> and all <argi>. <function> must be a procedure taking an N-tuple, bundle up <argi> as such an N-tuple, pass it to the procedure as its single argument.
If we assume that there is a form expressing tupling, say (tuple <x1> ... <xN>) for N >= 0, then the second form is syntactic sugar for
(<function> (tuple))
and the last form is syntactic sugar for
(<function> (tuple <arg1> ... <argN>))
If we further assume that (tuple <x>) simply returns <x>, then all three forms can be seens as instances of the same kind of syntactic sugaring.
Obviously, the first two forms of application are as cheap (or very nearly so) as those in any existing Scheme implementation. The potential performance hit comes with the third form. So here is what we do: Instead of creating the tuple in the caller, we pass the values <arg1> ... <argN> in "unpacked" form (e.g., as individual stack slots, or even in registers), just like it is done currently in Scheme. In addition, as any prudent Scheme implementation must do, we also say how big N actually is. (Current Scheme implementations must do the latter, too, in order to be able to detect arity mismatches dynamically.)
Now, on the callee side, we proceed as follows:
In the (LAMBDA (x) <body>) case, we check to see if N = 1. If so, we simply take the value, bind it to x, and continue with <body> In the case of N = 0, we bind x to an empty tuple and evaluate <body>. In the case of N > 0, we now tuple up the arguments, bind the resulting tuple to x, and then continue. Notice that the only expensive case here is the one that would be considered a dynamic type error in existing Schemes.
In the (LAMBDA () <body>) case, we check to see if N = 0. If so, we proceed with <body>. In all other cases we raise an error.
In the (LAMBDA (x1 ... xM) <body>) case, we check to see if: M = N => bind x1 ... xN as usual and proceed with <body> N = 1 => check the type of the actual argument. It must be a tuple of size M. If so, unpack the tuple, bind x1 ... xM, and proceed. If not, raise an error. otherwise => raise error Again, the only expensive case occurs where traditional Schemes would raise an error anyway.
The point of all this is that from the point of the semantics of the surface language, multiple values (for both arguments and results) are good old-fashioned first-class values like everything else in Scheme. And to get good performance on the critical path case where first-class-ness is not semantically required, we do the actual tupling lazily, using more efficient parameter-passing mechanisms wherever possible.
In other words, there really is no excuse for having CALL-WITH-VALUES and VALUES in a language.
> then my reply is that giving up dynamisms that will follow is a way > too high price. etc etc etc.)
Calm down. Nobody asked you to give up your precious dynamisms. :-)
On Tue, 9 Apr 2002, MJ Ray wrote: > George Caswell <sieg_h...@attbi.com> wrote: > > I did explain my current (limited) understanding of multiple return > > values. About the only reason I could fathom for its existence was in > > order to make the representation match the intent. I humbly suggest that, > > in order to make your responses useful, you pay attention to what you're > > reading.
> I did not understand your attempted explanation (which looks to me like part > of a sentence), so I did not try to answer that part. Now that I see you > seem to enjoy abusing those whose replies do not meet your exacting > completeness standards, I regret following up at all. I will try not to > make that error again and humbly apologise for offending you.
Apology accepted.
More seriously: This is just an ongoing pet peeve of mine - someone asks for a question to be answered, and the answer is of the form of "why would you do that?" or "why are you asking that question?" Something pretty much orthogonal to the question being asked. Answers like this are noise. In this case I asked why (values) exists. Your first answer was along the lines of "You can turn values into a list", and IMO did not at all address the question. Your second answer was that you felt it was more semantically clean than using lists. The second answer did address the question, and I appreciate the input.
Anyway, thanks to everybody who helped me out with this.
Matthias Blume <matth...@shimizu-blume.com> writes: > In the (LAMBDA () <body>) case, we check to see if N = 0. If so, > we proceed with <body>. In all other cases we raise an error.
Sorry, got this one wrong. Should have said:
In the (LAMBDA () <body>) case, we check to see if N = 0. If so, we proceed with <body>. If N = 1, we check the type of the single argument, which must be "unit". If so, we proceed with <body>. Otherwise, we raise an error. If N > 1, we raise an error.
Matthias Blume <matth...@shimizu-blume.com> writes: > The point is that call-with-values and values are "features piled on > top of features" -- something that does not give you any additional > expressive power, in fact, something that is strictly less > expressive than *another* language feature that is *already* in the > language. [...] > In terms of pragmatics, it is definitely a pessimisation because I > need to contort all kinds of otherwise clean code to account for the > possibility of multiple values.
At this point I have to admit that I am pretty much leaning towards this opinion. The only thing that I found useful with multiple values is in case you want to define several functions sharing a single (hidden) state without using a redundant binding as in
(define f+g (let ((state ...)) (let ((f ...) (g ...)) (list f g)))) (define f (car f+g)) (define g (cadr f+g))
but this needs some form of `define-values'.
> I do not understand why you think that discussing this is reducing > the S/N ratio. [...]
Well discussing *this* is certainly bringing it up -- it's those "I can do X if I had feature Y in my language", for any property Y that is far enough from Scheme to be impractical or even something to learn from. So your current post is definitely good stuff, compared to the other. (You probably think that this one is just being more verbose...)
> Suppose we have the following semantics of abstraction and application: > 1. Abstraction: > [...] > 2. Application: > [...] > If we further assume that (tuple <x>) simply returns <x>, then > all three forms can be seens as instances of the same kind of > syntactic sugaring. > [...] So here is what we do: Instead of creating the tuple in the > caller, we pass the values <arg1> ... <argN> in "unpacked" form > (e.g., as individual stack slots, or even in registers), just like > it is done currently in Scheme. In addition, as any prudent Scheme > implementation must do, we also say how big N actually is. (Current > Scheme implementations must do the latter, too, in order to be able > to detect arity mismatches dynamically.)
Yes (just need to make the above a bit more complex to allow variable number of arguments), but after you've done all this, you end up with something which looks exactly like the current state of things without multiple values, which you could add to reify these tuples. And maybe then add a form that will be a function application using a user-supplied tuple, as with CL's `multiple-value-call':
(multiple-value-call f (values ...))
Do you see any practical result of your description which makes things different than where they are now?
> Calm down. Nobody asked you to give up your precious dynamisms. :-)
Whew... (Semi-seriously, I think it's good to worry about this, there's no doubt static languages have advantages, so I think it's good to have a reality check every once in a while... The easiness of expressing any argument structure in ML with minimized performance penalty is certainly one of these attractive features. (I even implemented a pattern matcher as a result.))
Disclaimer: it is getting very late for me (in contrast to the local timezone), so I apologize in advance if there's anything obviously stupid in the above... Time to crash now...
Barry Margolin <bar...@genuity.net> writes: > But in fact, we don't require programmers to package up multiple input > values that way. So why should we require them to do so for multiple > output values?
this is the crux of it. it also makes it less clumsy, easier to understand less error-prone etc etc. multiple return values are not a luxury.
oz --- the power of the unaided human mind is highly overrated. -- Donald Norman
In article <a8vpav$mk...@abbenay.CS.Berkeley.EDU>,
Brian Harvey <b...@abbenay.cs.berkeley.edu> wrote: >But the tradition of functions as things with multiple arguments and >one return value is about 1000 years older than this more abstract >definition
Really? Didn't many early programming languages, such as Algol, allow you to specify IN, OUT, and IN-OUT parameters to procedures? Multiple values allows you to get an effect analogous to multiple OUT parameters.
It's true that when procedures are used as functions in expressions, there's a distinguished return value, and it's traditionally been a singleton (so that it can be substituted into the expression). In some languages a function can assign to its OUT parameters while also returning a value, but the Lisp family doesn't support this. So multiple return values were introduced to fill the gap. As I mentioned in another post, Common Lisp recognizes the special status of the primary value when a function call appears in an ordinary expression, by ignoring any extra values.
-- Barry Margolin, bar...@genuity.net Genuity, Woburn, MA *** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups. Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.
> > Brad Lucier reported timings for a benchmark of the SRFI-25 reference > > implementation where native multiple values[*] showed to be about 5 times > > faster than multiple values simulated with lists/apply
> Well, that just makes the point very clearly: multiple values are a *performance* hack.
I should point out that the implementation of multiple values in Gambit is really straightforward. It simply consists in packaging the values in a specially tagged vector (except when there is a single value). I think the factor of 5 performance improvement observed is simply due to the inlining of the allocator. So you could get the same performance with (vector value1 value2 ...) if you use the right declarations for this call to be inlined.
I don't like multiple values, but they are supported in Gambit 4.0 for compatibility with R5RS.
Eli Barzilay <e...@barzilay.org> writes: > Do you see any practical result of your description which makes things > different than where they are now?
Absolutely. For starters, I can write "compose" the way I wanted it:
(define ((compose f g) x) (f (g x)))
This handles all cases, including g or f taking or produces multiple values.
Since multiple values are now first-class values, you can even do things like
(map f (map g <list>))
where g might return multiple values. The intermediate list would nicely collect them as first-class tuples and dispatch the (potentially) multiple-arg f to work on those. Imagine the contortions that you'd need in current Schemes.
Let's try, assuming that multiple args for g are given as sublists of <list>, and that multiple results of f are to be collected as sublists of the final result:
Matthias Blume wrote: >"felix" writes: >> There is a paper by Kent Dybvig ("Efficient Implementation of Multiple >> Return Values in Scheme" (or so)), which compares the performance >> of different programming styles (values, CPS, lists, etc.). Should >> be available at readscheme.org.
>> Brad Lucier reported timings for a benchmark of the SRFI-25 reference >> implementation where native multiple values[*] showed to be about 5 times >> faster than multiple values simulated with lists/apply
>Well, that just makes the point very clearly: multiple values are a >*performance* hack.
What's always puzzled me is that in Ashley & Dybvig's paper, the only way they were able to actually get increased performance is to transform things like
where mv-let (and mv-call) are implemented as primitives. Performing this transformation is complicated by the fact that call-with-values is an ordinary procedure, not syntax.
Further, the code I've seen written to demonstrate the utility of multiple return values almost invariably defines macros to provide Lisp-style (multiple-value-bind) and (multiple-value-call) as syntactic sugar for (call-with-values).
I really wonder why R5RS specified (call-with-values) as the essential primitive instead of (mv-let) and (mv-call), when the former is too ugly to be used directly by programmers and too awkward to be implemented efficiently without first transforming it into the latter. Was there a moratorium on introducing new special forms in effect at the time, or what?
In article <fo1ydnbnx8....@trex10.cs.bell-labs.com>, Matthias Blume
<matth...@shimizu-blume.com> wrote: > Suppose we have the following semantics of abstraction and application:
> 1. Abstraction:
> (LAMBDA (x) <body>) -- takes the function's single argument, binds > it to X and evaluates <body>
Did you mean (lambda x body)? The design as you presented it changes the semantics of Scheme, e.g.: ((lambda (x) x) 1 2) in Scheme is an error but in your semantics is (tuple 1 2).
Your proposed semantics also require a tuple of one element to be eq to the element, which seems very weird to me.