So on Scheme and other Lisps, you don’t have to write “return” to return from functions, they just instead automatically return the value of the last expression.
If you wanna return multiple values, though, that’s when you can write
(values foo bar)
.
(+ 1 2 (values 3 4) 5)
That’s 11 on Common Lisp and Chicken (the four gets discarded) but borks on some other Schemes. It’s great for providing something a li’l extra (like the remainder in a quotient function).
But when you do need the extra value, that’s when people sigh and moan
and start looking at their SRFI-8 cheat sheet (receive
, our version
of multiple-value-bind
).
It’s even to the point that people often return a list or tuple of values instead of multiple values, and then destructure that when they need it.
And brev makes that destructuring easier since our define
can destructure.
(define (frobnicate (five three))
(+ five three))
(frobnicate (list 5 3))
That said, I like multiple values, and it’s not like receive
is super hard to use:
(receive (five three)
(values 5 3)
(+ five three))
and it’s often the best way to do it.
But there is something even more awesome! That’s right! I’m talking
about the life-changing magic of compose
!
Compose is a way to combine several wrapped functions into one.
((compose char->integer car string->list) "hello")
That’s 104, same as
(char->integer (car (string->list "hello")))
That’s not even the awesome part! If you’re only returning one thing
from each function, you might as well use o
since it’s a way faster
single-value variant. (And easier to type.)
Compose understands multiple values! That’s the awesome part.
You can define functions that return (via multiple values) what the next function needs exactly and then hook them all up.
((compose + values) 5 3)
⇒ 8
As long as one function’s return values map exactly to the other function’s arguments, you can be in compose paradise.♥︎
If you need to throw away arguments, that’s obviously not hard, just
jam a . rest
on that lambda list.
((compose (fn (+ x z)) values) 1 2 4 8 16 32)
⇒ 5
It’s a li’l tricker and consier if you wanna slide along some extra values first:
(apply + 9 ((compose list values) 7 6))
and even worse after:
(apply + `(9 ,@((compose list values) 7 6) 5))
A recieve or a whole new function is probably neater for those cases:
((compose (fn (+ 9 x y 5)) values) 7 6)
(receive (x y) (values 7 6) (+ 9 x y 5))
You can also combine brev’s destructuring define with a thunk argument:
(define (frobnicate foo (= (compose list (fn (x))) (bar baz)))
(* foo bar baz))
(frobnicate 3 (fn (values 5 7)))
⇒ 105
Now, this last trick
(compose list (fn (x)))
Or if you wanna give it a name with
(define values->list (compose list (fn (x))))
That’s not much greater than if you were to just return a list in the first place and not even bother with multpiple values. The use case, though, is when you have extra values just for the extra, you usually just use the first value (which is easy on CL and Chicken, and not so much on other Schemes), and you only rarely need to grab the extra vals.
Those special cases aren’t the painless part (they’re pretty rough slmost to the point of obfuscsted); the painless part is when you write functions that line up perfectly values to arguments in a long wonderful chain.
That’s also where the fanciest define of all time can help you, because if some of the values are tuples, lists, records, anything, you can destructure them:
(define-record point x y altitude name)
(define (calculate-altitude roof chimney) (+ 100 (+ roof chimney)))
(define (frobnicate x y roof chimney name)
(values (make-point x y (calculate-altitude roof chimney) name) "Jolly good!"))
(define (display-fancily ($ point _ _ altitude name) review)
(print name " is at altitude " altitude ". " review))
((compose display-fancily frobnicate) 7 8 12 2 "The Kent building")
⇒ The Kent building is at altitude 114. Jolly good!
I just remembered belatedly that the clojurian thrush combinators also support multiple values neatly:
(->* 4 (values 5 6) list)
⇒ (4 5 6)
(->* (values 3 4) (+ 1 2 5))
⇒ 15
This is a great fit for the technique of using define arguments for naming, which I’ve advocated for here in this text, and in define vs let.