Как мне сделать что-либо с несколькими возвращаемыми значениями в racket?

Кажется, что для использования нескольких возвращаемых значений в Racket мне нужно либо использовать define-values, либо собрать их в список с (call-with-values (thunk (values-expr)) list). В последнем случае, почему кто-то должен выбрать, чтобы возвращать несколько значений вместо списка, если просто нужно их собирать в список? Кроме того, оба из них очень многословны и неудобны для работы с большинством кода. Я чувствую, что, должно быть, я неправильно понимаю что-то очень важное о множественных возвращаемых значениях. В этом отношении, как написать процедуру, принимающую несколько возвращаемых значений?

Ответ 1

Несмотря на то, что я могу пропустить некоторые истории Схемы и другие нюансы, я дам вам практический ответ.

Во-первых, одно правило - если вам нужно вернуть более двух или трех значений, не используйте несколько значений и не используйте список. Используйте struct. Это, как правило, легче читать и поддерживать.

Форматы Racket match значительно упрощают разрушение возвращаемого значения списка - так же просто, как define-values:

(define (f)
  (list 1 2))

(match-define (list a b) (f))
(do-something-with a b)

;; or

(match (f)
  [(list a b) (do-something-with a b)])

Если у вас есть еще одна функция, g, которая принимает (list/c a b), и вы хотите скомпоновать ее с f, проще, если f возвращает список. Это также проще, если оба используют двухэлементный struct. Я думаю, что call-with-values - это неловкая горячая беспорядок.

Разрешение множественного возвращаемого значения является изящной идеей, поскольку оно делает возвращаемые значения симметричными с аргументами. Использование нескольких значений также быстрее, чем списки или структуры (в текущей реализации Racket, хотя он мог бы работать в противном случае).

Однако, когда читаемость является более высоким приоритетом, чем производительность, то в современной Racket может быть более практичным использование list или struct, IMHO. Сказав, что я использую несколько значений для отдельных специальных вспомогательных функций.

Наконец, там длинное, интересное обсуждение в списке рассылки Racket.

Ответ 2

Racket doc дает нам квинтэссенционный пример, почему в маскировке:

> (let-values ([(q r) (quotient/remainder 10 3)])
    (if (zero? r)
      q
      "3 does *not* divide 10 evenly"))
"3 does *not* divide 10 evenly"

Мы получаем два значения напрямую и используем их отдельно в следующем вычислении.

update: в Common Lisp, с его решительным практическим, не-металлическим, нефункциональным подходом (там, где они связаны с каждым дополнительным выделением ячеек), это имеет гораздо больший смысл, тем более что он позволяет также называть такие процедуры "нормальным" способом, автоматически игнорируя "дополнительные" результаты, вроде как

(let ([q (quotient/remainder 10 3)])
    (list q))

Но в Racket это неверный код. Так что да, это похоже на постороннюю особенность, лучше всего избегать.

Ответ 3

Использование list, поскольку потребитель побеждает цель нескольких значений, поэтому в этом случае вы могли бы просто использовать списки для начала. Несколько значений на самом деле являются способом оптимизации.

Семантически возвращающий список и несколько значений похожи, но когда вы возвращаете много значений в процессе списка, происходит создание ячеек cons, чтобы сделать список и уничтожить аксессоров для получения значений на другом конце. Однако во многих случаях вы не заметите разницы в производительности.

С несколькими значениями значения находятся в стеке, а (call-with-values (lambda () ... (values x y z)) (lambda (x y z) ...) проверяет только число, чтобы проверить, правильно ли оно. Если это нормально, вы просто применяете следующую процедуру, поскольку в стеке есть аргументы, все из которых были установлены из предыдущего вызова.

Вы можете сделать синтаксический сахар вокруг этого, а некоторые популярные - let-values и получение SRFI-8 - это немного проще. Оба используют call-with-values как примитивные.

Ответ 4

values удобен, потому что он

  • проверяет правильность количества возвращаемых элементов
  • destructures

Например, используя

(define (out a b) (printf "a=~a b=~a\n" a b))

затем

(let ((lst (list 1 2 3)))
  (let ((a (first lst)) (b (second lst))) ; destructure
    (out a b)))

будет работать, хотя lst имеет 3 элемента, но

(let-values (((a b) (values 1 2 3)))
  (out a b))

не будет.

Если вам нужен тот же контроль и деструктурирование со списком, вы можете использовать match:

(let ((lst (list 1 2)))
  (match lst ((list a b) (out a b))))

Обратите внимание, что он создает структуру, например. (list 1 2) vs (values 1 2) эквивалентен.