I've looked into your bug comment.
The additional use case that I see is your example code:
(let ((v (values 1 2))) (call-with-values (lambda () v) cons))
It could be re-written as:
(let ((v '(1 2))) (call-with-values (lambda () (apply values v)) cons))
I don't see what a user-visible values structure provides when the user already has cons, vectors, and records.
I see the call-with-values as a way to create multi-value continuations and values as the standard way to call them. If the continuation does not accept multiple values (any continuation not created by call-with-values) then I'd like to see it behave as if you just returned one value.
Unfortunately, the only way to use the current implementation of 'values' properly is to de-structure it using call-with-values. I think that is redundant since I can perform the same with a list, vector, or record. What I would like is to forget about secondary values when I use it normally, and only care about them when I explicitly request it. The only way to achieve this is a values semantics that I'm advocating.
Some use cases (to name a few).
1. When the primary value is expensive to create and the secondary value is cheap and dependent on the primary value. 2. for table-ref, you can have a secondary value on whether the item was actually set or not.
Arthur
----- Original Message ----
I've once written a response to someone posting this as bug in bugzilla:
http://www.iro.umontreal.ca/~gambit/bugzilla/show_bug.cgi?id=63
I.e. I personally still think the "take first value if continuation only expects one value" approach is a bad idea.
If you're really wanting to see that behaviour, maybe you could describe the reasons in more detail and think about the issues I've lined out above; maybe there is a way to achieve both.
Christian.
Afficher les réponses par date
Arthur Smyles wrote:
I've looked into your bug comment.
The additional use case that I see is your example code:
(let ((v (values 1 2))) (call-with-values (lambda () v) cons))
It could be re-written as:
(let ((v '(1 2))) (call-with-values (lambda () (apply values v)) cons))
I don't see what a user-visible values structure provides when the user already has cons, vectors, and records.
I don't understand your point.
My point was that the (values 1 2) would be from user code.
Example (and yes macros don't even have to be involved):
;; library code:
(define (db-transaction db thunk) (db-begin! db) (with-exception-catcher (lambda (e) (db-rollback! db) (raise e)) (lambda () (let ((res (thunk))) (db-commit! db) res))))
;; user code:
(define (dosomequery-with-x x) (db-transaction mydb (lambda () (let ((a (db-query mydb "foo" x)) (b (db-query mydb "bar" x))) (values a b)))))
(call-with-values (dosomequery-with-x 1234) (lambda (a b) ...))
;; or
(let-values ((a b (dosomequery-with-x 1234))) ...)
The point was that the (values 1 2) would be provided by the user of the library. And that the user doesn't know about the implementation of the library. But be able to pass multiple values through the layer that the library might have (again, the user wouldn't necessarily be able to know whether the library imposes an additional layer between the continuation of the callback code and the continuation of the library call). And the library author needn't bother about multiple values either.
In this example, if Gambit were to work as you suggest, the let-values couldn't get at the b value.
I see the call-with-values as a way to create multi-value continuations and values as the standard way to call them. If the continuation does not accept multiple values (any continuation not created by call-with-values) then I'd like to see it behave as if you just returned one value.
I guess my and Brad's suggestions are to specify that at the call site explicitely.
Unfortunately, the only way to use the current implementation of 'values' properly is to de-structure it using call-with-values. I think that is redundant since I can perform the same with a list, vector, or record.
As I said, and as has discussed many times, yes, multiple-values is kind of redundant.
The idea has always been that since there are multiple-value function entries, there should also be (as a symmetry) multiple-values function exits.
Now whether the current ways multiple values are being implemented solve that well is an open question.
Manual continuation passing style is the best pre-existing and probably still best current way for passing multiple "return" values; then you can give (lambda (a . ignore) ...) as the continuation if you don't want to look at the remaining values.
Christian.
My understanding of this issue is clearer. But see comments below
Christian Jaeger wrote:
Arthur Smyles wrote:
I've looked into your bug comment.
The additional use case that I see is your example code:
(let ((v (values 1 2))) (call-with-values (lambda () v) cons))
It could be re-written as:
(let ((v '(1 2))) (call-with-values (lambda () (apply values v)) cons))
I don't see what a user-visible values structure provides when the user already has cons, vectors, and records.
I don't understand your point.
My point was that the (values 1 2) would be from user code.
Example (and yes macros don't even have to be involved):
;; library code:
(define (db-transaction db thunk) (db-begin! db) (with-exception-catcher (lambda (e) (db-rollback! db) (raise e)) (lambda () (let ((res (thunk))) (db-commit! db) res))))
;; user code:
(define (dosomequery-with-x x) (db-transaction mydb (lambda () (let ((a (db-query mydb "foo" x)) (b (db-query mydb "bar" x))) (values a b)))))
(call-with-values (dosomequery-with-x 1234) (lambda (a b) ...))
;; or
(let-values ((a b (dosomequery-with-x 1234))) ...)
The point was that the (values 1 2) would be provided by the user of the library. And that the user doesn't know about the implementation of the library. But be able to pass multiple values through the layer that the library might have (again, the user wouldn't necessarily be able to know whether the library imposes an additional layer between the continuation of the callback code and the continuation of the library call). And the library author needn't bother about multiple values either.
This only works because Gambit doesn't return multiple values. As Marc mentioned earlier, (values 1 2) is currently implemented as a vector with a special type. So you can achieve the same thing using the user code:
(define (dosomequery-with-x x) (db-transaction mydb (lambda () (let ((a (db-query mydb "foo" x)) (b (db-query mydb "bar" x))) (list a b)))))
(apply (lambda (a b) ...) (dosomequery-with-x 1234))
If the library wanted to be multi-value aware, then it could be re-written as:
(define (db-transaction db thunk) (db-begin! db) (with-exception-catcher (lambda (e) (db-rollback! db) (raise e)) (call-with-values thunk (lambda vals (db-commit! db) (apply values vals)))))
This makes sense since only call-with-values will create a multi-value continuation. call/cc by default only creates single-value continuations.
If I only wanted value a the user's code simplifies to
(dosomequery-with-x 1234)
In this example, if Gambit were to work as you suggest, the let-values couldn't get at the b value.
I see the call-with-values as a way to create multi-value continuations and values as the standard way to call them. If the continuation does not accept multiple values (any continuation not created by call-with-values) then I'd like to see it behave as if you just returned one value.
I guess my and Brad's suggestions are to specify that at the call site explicitely.
I am suggesting that too. It doesn't work that way though. My original example
(if (values #f #f) #t #f)
doesn't work in scheme because values creates a special object that is not false so to make it work properly, I have to do:
(if (call-with-values (lambda () (values #f #f)) (lambda (primary . secondary) primary)) #t #f)
But if I always have to do that in order to use my function, then I might as well not use values/call-with-values and split the function into 2.
Unfortunately, the only way to use the current implementation of 'values' properly is to de-structure it using call-with-values. I think that is redundant since I can perform the same with a list, vector, or record.
As I said, and as has discussed many times, yes, multiple-values is kind of redundant.
The idea has always been that since there are multiple-value function entries, there should also be (as a symmetry) multiple-values function exits.
Now whether the current ways multiple values are being implemented solve that well is an open question.
That's what I'm asking and answering :D
Manual continuation passing style is the best pre-existing and probably still best current way for passing multiple "return" values; then you can give (lambda (a . ignore) ...) as the continuation if you don't want to look at the remaining values.
Christian.
I think you've given me an idea as a workaround to simulate what I want
(define (mv-fun . k) (if (pair? k) ((car k) #f #f) #f))
In which case if I want the multiple-values I have to explicitly pass the continuation. I'd rather have the scheme implementation hide this stuff with values/call-with-values then have this kind of plumbing in apis that I'd develop.
So my take away from the discussions so far are:
If your scheme implementation does not support multi-value continuations then don't bother with values/call-with-values. They are basically standardized hooks for implementations that do support multi-value continuations.
Arthur
(Before replying let me remind ourselves that there are two things in this thread:
(1) whether the semantics of Gambit or that of dropping superfluous values is the better one. Or at least making each of us understand each other's points. (And I think, I do see the point that you'd like to have that comfort of just ignoring some less-important values that you might not always want, this is easy to see. And you'd like not having to specify anything syntactically on the receiver side to get this. I do question the merit of this versus the problems of it, though.)
(2) whether there is a good way unifying both approaches in one and the same system, with hopefully workable integration of code of both philosophies.
Let me state that I'm a bit unclear which of those you are targetting below at some places.)
Arthur Smyles wrote:
My understanding of this issue is clearer. But see comments below ... This only works because Gambit doesn't return multiple values. As Marc mentioned earlier, (values 1 2) is currently implemented as a vector with a special type.
(The implementation doesn't really matter (they could also be stored in global variables if the implementation wouldn't have to deal with call/cc or threads, or the vector could be optimized away if the consumer is the direct continuation of the producer); the fact how they are being implemented will just make clear how they behave. The point is that the multiple values are being tunneled through continuations which are not "prepared" (so to speak) of taking multiple values, until they reach a continuation which is.)
So you can achieve the same thing using the user code:
(define (dosomequery-with-x x) (db-transaction mydb (lambda ()
(let ((a (db-query mydb "foo" x)) (b (db-query mydb "bar" x))) (list a b)))))
(apply (lambda (a b) ...) (dosomequery-with-x 1234))
Yes, I already said "As I said, and as has [been] discussed many times, yes, multiple-values is kind of redundant" (not sure why you're bringing this up again, but I guess you're looking after that additional benefit they would provide you).
If the library wanted to be multi-value aware,
My point was that as library author I *don't want* to remember having to make everything multi-value aware.
(It's like not casting a data type in stone: I don't want to cast in stone how many values are being passed through a function of mine by the user of that function.)
then it could be re-written as:
(define (db-transaction db thunk) (db-begin! db) (with-exception-catcher (lambda (e) (db-rollback! db) (raise e)) (call-with-values thunk (lambda vals (db-commit! db) (apply values vals)))))
This makes sense since only call-with-values will create a multi-value continuation. call/cc by default only creates single-value continuations.
(call/cc makes continuations first class, it doesn't create them [well, it may create a continuation *object* representing the continuation]. A continuation captured by call/cc will expect exactly as many values as the continuation of the call/cc call takes (because it's the same).)
(What do you mean when you say "This makes sense"?)
Are you suggesting that I should really write my code as you have shown above? If so, I would have thought that it shows quite clearly why the approach of dropping values is flawed. You don't think so?
Tunneling multiple values without having to change the code makes "normal" Scheme code automatically compatible with multiple values, that's the good thing about the tuples/Gambit approach.
... I think you've given me an idea as a workaround to simulate what I want
(define (mv-fun . k) (if (pair? k) ((car k) #f #f) #f))
I don't understand. What would you pass to mv-fun?
In which case if I want the multiple-values I have to explicitly pass the continuation. I'd rather have the scheme implementation hide this stuff with values/call-with-values then have this kind of plumbing in apis that I'd develop.
I'd rather specify places where I expect multiple values explicitely than having to change all code which would *possibly* need to deal with a user expecting to be able to pass multiple values through. Since the former are few, whereas the latter could be *every* function.
Am I missing something?
So my take away from the discussions so far are:
I have big difficulties understanding the following paragraph.
If your scheme implementation does not support multi-value continuations
What is your definition of a scheme implementation which does support multi-value continuations?
In the R5RS sense, Gambit is one, as are the implementations which drop values. All of them do support multi-value continuations -- continuations which are created by call-with-values.
The difference is whether *other* continuations ("normal" ones) are behaving like (lambda (value . ignored) ..) including always dissecting the values immediately in the continuation the values are being passed to, or (lambda (value) ..) *without* dissection.
So (my guess) you are counting Gambit into the camp which "does not support multi-value continuations"?
then don't bother with values/call-with-values.
Why not?
They are basically standardized hooks
Why "hooks"?
for implementations that do support multi-value continuations.
What do you want to say? "Since |values| does not offer new features over |list| in an implementation like Gambit, don't bother using it there", or something else?
Christian.