Function that potentially can return more than one value

46 views
Skip to first unread message

Jens Axel Søgaard

unread,
May 27, 2020, 6:35:09 AM5/27/20
to Racket list
Hi All,

While reading the documentation of Matlab I noticed that many functions can optionally return extra values.

An example:

image.png

In the first case  R = rref(A)   the function rref needs to return a single value,
and in the third case [R,p] = rref(A) it needs to return two values.

If the pivots aren't needed, there is no reason to compute then - therefore
the body of rref needs a way find the number of values to return.

In standard Racket an application doesn't communicate how many values
a function is expected to produce. 

I have written a small proof-of-concept of an assignment operator :=
that communicates to a function how many extra values it is expected
to produce (and still works with standard functions).

Are there alternative solutions that are better than the "use a keyword" approach?

/Jens Axel Søgaard
https://racket-stories.com



#lang racket
(provide :=  declare-can-return-extras-values)

(require (for-syntax syntax/parse syntax/id-table))

;;; Problem
; Consider these assignments

;   (:= x     (f a))  ; return single value
;   (:= (x y) (f a))  ; return two values

; In the single value case, f doesn't need to calculate the second return value.
; How does f know the number of values to compute?

; Solution:
;   Give functions that maybe returns extra values an optional keyword argument
;     #:extra-values
;   and let := supply it.

;   However := should only supply #:extra-values when the expression
;   is an application of a function that accepts the #:extra-values keyword.

;   The form  declare-can-return-extras-values  is used to declare
;   that a given identifier belongs to a function that can return
;   extra values.
   

(begin-for-syntax
  (define can-return-extra-values-table (make-free-id-table)))

(define-syntax (declare-can-return-extras-values stx)
  (syntax-parse stx
    [(_declare-can-return-extras-values f:id ...)
     (syntax/loc stx
       (begin-for-syntax
         (free-id-table-set! can-return-extra-values-table #'f #t)
         ...))]))


(define-syntax (:= stx)
  (syntax-parse stx
    [(_ ()         e:expr) (syntax/loc stx (void e))]     ; ignore return values
    [(_  x:id      e:expr) (syntax/loc stx (define x e))] ; one return value
    [(_ (x:id)     e:expr) (syntax/loc stx (define x e))] ; one return value
    [(_ (x:id ...) e:expr) ; at least one extra return value
     ; Expand e to see, whether it is a call to a function that
     ; can produce extra values.
     (define expanded-e (local-expand #'e 'expression #f))
     (syntax-parse expanded-e
       [(f arg ...)
        #:when (free-id-table-ref can-return-extra-values-table #'f #f)
        ; The extra values are going to be bound to:
        (define xs #'(x ...))
        ; The number of extra values to return is therefore:
        (define extra-values (length (syntax->list xs)))
        ; Supply the number of expta values using the keyword #:extra-values.
        (with-syntax ([extra-values extra-values])
          (syntax/loc stx
            (define-values (x ...)
              (f arg ... #:extra-values (max 0 (- extra-values 1))))))]
       ; Also an application of a function f, but this time f is a standard
       ; function that doesn't accept the #:extra-values keyword.
       [(f arg ...)
        (syntax/loc stx
          (define-values (x ...)
            (f arg ...)))]
       [_
        ; Turns out e wasn't function call, so don't touch it.
        expanded-e])]
    [_
     (raise-syntax-error ':= "bad syntax" stx)]))


(define (foo x #:extra-values [n 0])
  (case n
    [(0)  x]
    [(1)  (values x (* 2 x))]
    [else (error 'foo "can't return that many values")]))


(declare-can-return-extras-values foo)

(let () (:= x     1)       x)
(let () (:= x     (+ 1 1)) x)
(let () (:= (x y) (foo 1)) (+ x y))


Laurent

unread,
May 27, 2020, 7:23:34 AM5/27/20
to Jens Axel Søgaard, Racket list
Something related but a little different: I've been experimenting with 'rich-return' values:
(look at the drracket submodule for examples)

The problem I wanted to solve is that I often have algorithms that produce many potentially useful values, but not all callers need all these values, and sometimes I want a small subset of them as values, sometimes I want them as a list, sometimes I prefer a dictionary. I found the boilerplate code to be annoying both to write and to read, so 'rich-return' does this all in one place instead.

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CABefVgxaHsLkTwGrqPXJ3xonVw4iuzhNMLGcge1qouUT0aqccw%40mail.gmail.com.

George Neuner

unread,
May 27, 2020, 1:27:57 PM5/27/20
to racket...@googlegroups.com

On 5/27/2020 6:34 AM, Jens Axel Søgaard wrote:
>
> In standard Racket an application doesn't communicate how many values
> a function is expected to produce.

You can ask:  see procedure-result-arity
    https://docs.racket-lang.org/reference/procedures.html


> I have written a small proof-of-concept of an assignment operator :=
> that communicates to a function how many extra values it is expected
> to produce (and still works with standard functions).
>
> Are there alternative solutions that are better than the "use a
> keyword" approach?

You can use  call-with-values  which connects the function that produces
with the one that consumes, but they have to agree on the number of
values being passed or it won't work.

You can use a Rest argument in the consumer if the values from the
producer are put into a list.


Unfortunately, Scheme did not embrace the idea of using tuples for
return values, nor did it allow to catch only the first N values as was
permitted by Lisp.

George

Jens Axel Søgaard

unread,
May 27, 2020, 1:51:16 PM5/27/20
to George Neuner, Racket list
Den ons. 27. maj 2020 kl. 19.27 skrev George Neuner <gneu...@comcast.net>:

On 5/27/2020 6:34 AM, Jens Axel Søgaard wrote:
>
> In standard Racket an application doesn't communicate how many values
> a function is expected to produce.

You can ask:  see procedure-result-arity
     https://docs.racket-lang.org/reference/procedures.html

I think  procedure-result-arity  answers the question "how many
values could this procedure return?" and not "how many values
is the procedure expected to return in this context?".
 
> I have written a small proof-of-concept of an assignment operator :=
> that communicates to a function how many extra values it is expected
> to produce (and still works with standard functions).
>
> Are there alternative solutions that are better than the "use a
> keyword" approach?

You can use  call-with-values  which connects the function that produces
with the one that consumes, but they have to agree on the number of
values being passed or it won't work.

My problem is that the producer still needs to be told how many values to produce.

/Jens Axel

George Neuner

unread,
May 27, 2020, 2:31:44 PM5/27/20
to Jens Axel Søgaard, Racket list


On 5/27/2020 1:50 PM, Jens Axel Søgaard wrote:
Den ons. 27. maj 2020 kl. 19.27 skrev George Neuner <gneu...@comcast.net>:

On 5/27/2020 6:34 AM, Jens Axel Søgaard wrote:
>
> In standard Racket an application doesn't communicate how many values
> a function is expected to produce.

You can ask:  see procedure-result-arity
     https://docs.racket-lang.org/reference/procedures.html

I think  procedure-result-arity  answers the question "how many
values could this procedure return?" and not "how many values
is the procedure expected to return in this context?".

Oh, I get it ... it's not that you "don't know" what's expected, it's that you've got something that returns N values on some path and M values on some other path, etc.  and you can't predict which path will be followed.

Yeah, that is a problem:  e.g., I don't think  procedure-result-arity  works for  case-lambda  where different invocations can return different numbers of results.

In that case, better to return a container: a list, vector, hash, etc. rather than a bunch of individual values.  Or, if the function is canned [e..g, in a library] to put them into a container for your code to work with.


 
> I have written a small proof-of-concept of an assignment operator :=
> that communicates to a function how many extra values it is expected
> to produce (and still works with standard functions).
>
> Are there alternative solutions that are better than the "use a
> keyword" approach?

You can use  call-with-values  which connects the function that produces
with the one that consumes, but they have to agree on the number of
values being passed or it won't work.

My problem is that the producer still needs to be told how many values to produce.


Sorry, I missed that - I thought you needed to know how many were produced by something.

The only way to call attention - particularly compiler attention - to a particular argument is to make it a keyword with no default value.

If your function has default behavior that the keyword(s) would only modify, then you can make keyword(s) optional using default values or by defining the function with  make-keyword-procedure.


George
Reply all
Reply to author
Forward
0 new messages