qi
qi copied to clipboard
[Feature Request] Converting `rest-args` from `list` to `values` first.
See previous discussion here: https://github.com/countvajhula/qi/pull/59
@NoahStoryM : I noticed that when switch-lambda and flow-lambda use rest-args, rest-args is treated as input to flow directly. Can we consider converting rest-args from list to values first? I think it is more in line with the point-free style.
@benknoble : I would like to point out, though, that when looking at (flow-lambda args …), I can tell that the flow expects 0 or more input values, and by analogy with regular Racket I expect the values to be in a list. It is not difficult to use sep as the first part of the flow to split the rest-args. Breaking the analogy to regular Racket would be odd, and might also cause confusion in the way inputs to the flow are handled.
I can tell that the flow expects 0 or more input values
It seems that this change doesn't affect it?
and by analogy with regular Racket I expect the values to be in a list.
I think the reason why regular Racket stores the rest args into a list is that it can't handle values
directly. This problem does not exist in Qi.
Breaking the analogy to regular Racket would be odd, and might also cause confusion in the way inputs to the flow are handled.
Well, in my understanding, the input accepted by flow should be values
. It's odd for me to convert part of the input to list
.
I can tell that the flow expects 0 or more input values
It seems that this change doesn't affect it?
Yes, that comment was a hold-over from a draft where my example involved more arguments (like (flow-lambda (a b . rest) …)
), which isn't currently supported in Qi anyway. But your PR #59 adds that. In that case, I can immediately say the flow expects 2 input values, plus an arbitrary many more; by analogy with Racket, I still expect the rest
to be in a list.
and by analogy with regular Racket I expect the values to be in a list.
I think the reason why regular Racket stores the rest args into a list is that it can't handle
values
directly. This problem does not exist in Qi.
This is true: I question how much Qi wants to break with Racket.
Breaking the analogy to regular Racket would be odd, and might also cause confusion in the way inputs to the flow are handled.
Well, in my understanding, the input accepted by flow should be
values
. It's odd for me to convert part of the input tolist
.
Again, my argument is that this is what Racket does naturally, and it is odd for me to break that analogy. OTOH, I can see some value in separating the list and staying in values
land.
Newly occurring to me, there is an efficiency question: Racket is going to collect the input into a list for us (assuming a translation like (flow-lambda (a b . rest) body) => (lambda (a b . rest) …)
).
- The current Qi behavior (to do nothing) avoids doing any work. If I want
rest
asvalues
instead oflist
, I am explicit about the work ofsep
ing it. - The proposed Qi behavior (to automatically
sep
the list) makes Qi do some invisible work. Then, if I want the opposite case (rest
as alist
instead ofvalues
), I have to ask Racket to do more (and duplicate) work tocollect
it back.
It may be that (a) values to lists and back is a cheap conversion, in which case this may not matter, or (b) that in fact the semantics of automatically sep
ing the rest arguments are by far the common case, so that we are ok with any performance cost to undo the automatic conversion. I don't know whether either is true.
I will point out that my original comment about "breaking change" still holds:
((flow-lambda rest (~> sep count)) 1 2 3)
is valid in current Qi but would break with the proposed change.
I find that passing flow-lambda
's rest-args as a list directly to flo will cause some consistency issues:
(define (t a #:b b . x) `(a b ,(apply + x)))
(~> (1 2 3 4 #:b -1 5 6) t) ; '(a b 20)
((flow-lambda (a #:b b . x) (~> t)) 1 2 3 4 #:b -1 5 6) ; ERROR -- `x` that `t` receives should be number values, but flow-lambda passes `t` a list.
Because t
's arguments contain a keyword, we cannot split the input. And I think the key of the issues is that the arguments of flow-lambda
should be consistent with the arguments accepted by flo.
Interesting example. I suppose you could write (~> (== (gen a) (gen #:b b) (~> (gen x) sep)) t)
if keywords became acceptable flows, but that would require changes everywhere (I'm not actually sure if the gen
s are necessary). Frankly it seems that passing keywords along is a bit difficult, because suddenly you want to be able to manipulate them. Another version without this issue might be (~> (== (gen a) (~> (gen x) sep)) (t #:b b))
. But neither is as "pretty" as ((flow-lambda (a #:b b . x) t) 1 2 3 4 #:b -1 5 6)
. I will note for future readers that this is clearly a toy example, as in this case it would be easier to just write (t …)
—the example does demonstrate that composing flows with rest arguments is painful.