Open NoahStoryM opened 2 years ago
I think (+)
is a bit of a special case of a more general rule (writing +
here would have the expected outcome).
Consider the following:
> (define ((f a) b) (+ a b))
> (~> (1) (f 2))
3
When the flow is an application like (f 2)
or (+)
, I think Qi tries to immediately resolve if the number of provided arguments is a valid arity for the proc. With (define (f a b) (+ a b))
instead, it does the currying.
Plus can be 0-arity, so it is immediately resolved. Another option (if you want to write parentheses) is (+ __)
.
It is unclear to me if this behavior comes from Qi or fancy-app, and I haven't looked at the code to see if I'm right, but I recall vaguely a discussion about this.
I seem to have forgotten about the (_ 4)
part of your flow: I do not believe Qi will produce flows as (intermediate) results unless directed to (either by partially-applying a curried function, or using clos
as you mentioned, etc.). In (~> (1 2 3) + (_ 4))
, the +
flow consumes the values (1 2 3)
and produces 6, which cannot be applied to 4
. Can you think of an unambiguous and unsurprising rule that would differentiate between "partially apply this flow" and "fully apply this flow"? Arguably, clos
does that in this case.
It is unclear to me if this behavior comes from Qi or fancy-app, and I haven't looked at the code to see if I'm right, but I recall vaguely a discussion about this.
The behavior I expect comes from curry
. In my understanding, Qi expands flo in this way:
(~> (1) (f 2))
((curry f 2) 1)
So from this perspective, (op)
should be expanded to (curry op)
:
(~> (1 2 3) (+))
((curry +) 1 2 3)
(~> (1 2 3) (+) (_ 4))
(((curry +) 1 2 3) 4)
In this way Qi naturally gets the function similar to clos
. And this change isn't difficult, just change (natex prarg ...+)
to (natex prarg ...)
in flow/compiler.rkt
(https://github.com/countvajhula/qi/blob/main/qi-lib/flow/compiler.rkt#L214) .
But I found that it's forbidden in tests file, so I'm curious if there is any special reason.
Not really a response, more for the record
> (expand #'(~> (1 2 3) (+) (_ 4)))
#<syntax:Library/Racket/8.6/pkgs/qi-lib/on.rkt:24:5 (#%app (#%app compose (lambda (_1) (#%app _1 (quote 4))) (#%app +)) (quote 1) (quote 2) (quote 3))>
> (expand #'(flow (~> (+) (_ 4))))
#<syntax:Library/Racket/8.6/pkgs/qi-lib/flow/compiler.rkt:96:7 (#%app compose (lambda (_2) (#%app _2 (quote 4))) (#%app +))>
> (expand #'(~> (1) (f 2)))
#<syntax:Library/Racket/8.6/pkgs/qi-lib/on.rkt:24:5 (#%app (#%app compose (#%app curryr f (quote 2))) (quote 1))>
Can you point out the test?
Ah, right: curry
does the hard work of the arity stuff I mentioned earlier. You said this, and I missed it.
And the specific issue you see is from not currying when a function has no arguments provided. Interesting.
The discussion above helps clarify the issue, and I don't actually recall if I had specific reasons in mind for this. But luckily, I seem to have gone through the trouble of documenting the behavior.
There's one explanation here: There's No Escaping esc
The fact that this is about how Qi behaves other than you might expect in this case would seem to be a sign that @NoahStoryM 's suggestion may be more natural here 😄
There's also some related discussion here: Using Racket to Define Flows, but I don't think this case would be affected.
The first link mentions that interpreting this as partial application "would not be useful," but maybe with the (_ ...)
application syntax the situation has changed, and your example above does seem like a reasonable use of the currying behavior.
It would be helpful to come up with more examples of cases that would be affected by this change (the docs example could be a good starting point). That would give us more confidence that there aren't any useful cases that would be negatively affected.
As of Qi 4, partial application syntax expects at least 1 argument pre-supplied, so that (+)
on its own is currently treated as a syntax error (no rule in the expander would match).
We could have a new "ad hoc" expansion rule (similar to this rule) that rewrites (f)
to (clos f)
to get the behavior proposed here. (f)
seems a pretty simple and intuitive way of indicating a closure...
This might be because I'm used to the old behavior, but every time (flow (+))
signals an error I'm surprised :) That said… it is an "unused syntax." OTOH, inserting clos
when we could write close
already is the kind of syntax sugar I'm not sure is actually clarifying code.
Are you suggesting that close
would be more clear than clos
? I think at the time, clos
was selected as short for "closure" rather than close/"close over".
I feel now in retrospect that a closure may be such a basic idea that, like partial application, having a name just makes it more complicated and confusing (e.g. (f a b)
vs (curry f a b)
), and from that perspective, having a compact syntax like (f)
to designate it seems appealing to me. Of course, adding (+)
syntax needn't mean we get rid of clos
or even introduce close
.
Sorry, close was a typo. The idea was mostly to avoid a possibly confusing syntactic sugar.
We discussed this in the meeting a couple of weeks ago.
What version of Racket are you using?
v8.6
What program did you run?
What should have happened?
I think currying a function directly is similar to using
clos
, but to my surprise Qi seems to avoid this usage intentionally, is there any special reason?