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