How to check equality of `values`?

The intuitive way doesn't work

> (equal? (values 1 2) (values 1 2))
result arity mismatch;
 expected number of values not received
  expected: 1
  received: 2
 [,bt for context]

(P.S. Interestingly I failed to find a solution to this seemingly simple question, because searching keyword values appears in so many contexts.)

Compare one pair of values at at a time.

> (let-values ([(a b) (values 1 2)]
               [(c d) (values 1 2)])
      (and (equal? a c)
           (equal? b d)))
#t

That looks cumbersome. Also any reasons why equal? don't work?

In an application (proc-expr arg ...) each arg expression must evaluate to a single value. The context of the first (values 1 2) expects a single value, so you get an error. To work with values, you need to use either let-values (or similar) or call-with-values .

Oh I get this. So values is not a simple data structure but rather some syntax-level stuff?

#lang racket

;; define in one module 

(define-syntax-rule (equal-values? x y)
  (call-with-values
   (λ () x)
   (λ a
     (call-with-values
      (λ () y)
      (λ b
        (and (equal? (length a) (length b))
             (andmap equal? a b)))))))

;; —————— ———————— ——————— 

;;; import into this module: 

(equal-values? (values 1 2) (values 1 2))

(equal-values? (values 1 2) (values 1 2 3))
1 Like

I've worked out my own version too:

(define-syntax values-equal?
    (syntax-rules ()
      [(_ a b) (equal? (call-with-values (lambda () a) list)
                             (call-with-values (lambda () b) list))])))
1 Like

Oh I get this. So values is not a simple data structure but rather some syntax-level stuff?

In a simple stack [1] based implementation return values from function calls are pushed to the stack. [In the following I am ignoring the return address and the argument count.] A call (+ 1 2) will push the values 1 and 2 to the stack. Jump to the code implementing +. Here the values 1and 2 will be popped from the stack and the result 3 is pushed to stack
before returning.

The expression (values 1 2) will simply push two values 1 and 2 onto the stack.

The problem with (equal? (values 1 2) (values 3 4)) is that it pushes 4 values in total to the stack. And equal? expects exactly 2 values on the stack.

[1] Since call/cc is available, it's technically a tree.

3 Likes

Warning: Rambling ahead.

One of the things I'm not sure if I like about Common Lisp is that extra values returned over the number expected are silently discarded (And if not enough are returned, the extras become nil). So (equal (values 1 2) (values 3 4)) is nil in CL, not an error.

There are times I find myself wondering if it'd be possible to emulate that behavior with a #lang in Racket by replacing #%app etc.

1 Like

I think, it would be difficult to make a #lang based solution without affecting the speed of the compiled code.

If you want a low-level approach.

"An Efficient Implementation of Multiple Return Values in Scheme" by J. Michael Ashley and R. Kent Dybvig

From the introduction:

In this paper we describe an implementation of the new Scheme multiple values interface that handles multiple values efficiently without penalizing code that does not use them or sacrificing our fast implementation of first-class continuations. Full run-time error checking is performed to insure that the correct number of values is received in all situations. We also describe how the implementation can be adapted to handle Common Lisp’s multiple values interface.

https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=1490ecc3267e0ff759506ed51f9071ee9fed91a8

1 Like

I don't think I would call it syntax-level stuff.

I would say that generally speaking, multiple values are a nifty theoretical side-slip that doesn't actually turn out to be the right solution to most problems.

... quick trip to google scholar ...

I took a quick look, and I see both the Danvy/Danvy-Lawall "Back to Direct Style I/II" papers and also the Sabry/Felleisen papers; I skimmed the Back to Direct Style papers and didn't immediately see anything like "now what if we perform this cps->ds transformation on functions of multiple arguments?". Was this part of the work with Amr? Or is it later, with Dybvig or Clinger or someone else?

... okay, back to main point ...

Anyhow, my claim is that the origin of values in Scheme is probably related to the train of thought relating to "what if we allow continuations to accept more than one argument? Hey, everything works out, that's pretty cool!"

Unfortunately, multiple values appear to represent an "orthogonal dimension" for most Scheme/Racket code; that is, the vast majority of Scheme/Racket contexts expect exactly one value, so using multiple values nearly always creates a minor headache on the part of the receiver. On the other hand, values do very clearly express the idea that you don't want to use any more memory or create a new compound value, you just want to return two values....

2 Likes

If this is the case should I use it? I see values being used in racket standard library too, such as quotient/remainder

I find multiple values very useful. More convenient than packing stuff up in a list/vector/struct and extracting it later.

It is definitely possible to create a Racket language with these semantics. Basically, you would wrap every expression from which you want to discard extra values with something like this:

#lang racket
(define (call-as-single-valued thunk)
  (call-with-values thunk
    (case-lambda
      [(v)
       v]
      [()
       #f]
      [(v . more)
       v])))
(define-syntax-rule (as-single-valued expr)
  (call-as-single-valued (λ () expr)))

Replacing #%app would let you handle function argument expressions, which may be the most important case, but it wouldn't be quite enough in general. For example, in:

(let-values ([(a b) e])
  body ...+)

a replacement for #%app wouldn't have enough information to know that e is actually expected to evaluate to two values.

Instead, I think you would want to replace #%module-begin to first fully expand the module, then insert wrappers as needed (for example, not around e1 in (begin e1 e2)).

That is definitely an open question, but it seems possible that the speed penalty might not be severe in common cases. In particular, the compiler has a certain amount of support for recognizing procedures that are known to return a single value, so hopefully it might be able to optimize away as-single-valued wrappers in many cases.