Skip to content

Commit 2f4ba2e

Browse files
committed
Generalize to exhibit zip behavior
It appears that △ and ▽ are closely related to the usual `zip` and `unzip` operations in functional languages and represent a boundary condition in their semantics. Therefore, it's better to remove the tentative new `zip` core form in favor of generalizing the existing △ form (and in the future, likewise, also generalizing ▽). This does entail a small backwards incompatibility, as `(△ flo)` formerly accepted a single list and any number of additional values that would be passed to each invocation of flo along with each element of the list. That behavior is intuitively very similar to the "zip" behavior, except that we now (more properly, it would seem) simply expect these additional values to be present in separate input lists, and these values may _happen_ to all be the same at each index.
1 parent f799d41 commit 2f4ba2e

File tree

12 files changed

+132
-149
lines changed

12 files changed

+132
-149
lines changed

qi-doc/scribblings/forms.scrbl

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -150,20 +150,27 @@ The full surface syntax of Qi is given below. Note that this expands to a @secli
150150
@defform[#:link-target? #f
151151
(sep flo)]
152152
)]{
153-
Separate the input list into its component values. This is the inverse of @racket[▽].
153+
For a single input list, separate it into its component values after first applying @racket[flo], if provided. This is the inverse of @racket[▽].
154154

155-
When used in parametrized form with a presupplied @racket[flo], this @tech{flow} accepts any number of inputs, where the first is expected to be the list to be unraveled. In this form, the flow separates the list into its component values and passes each through @racket[flo] along with the remaining input values.
155+
When given multiple input lists, this resembles the standard "zip" operation in functional programming languages. Specifically: combine corresponding members of each input list using @racket[flo], producing these results directly as @seclink["values-model" #:doc '(lib "scribblings/reference/reference.scrbl")]{values}. If no @racket[flo] is specified, default to @racket[values], so that the behavior is to simply separate all input lists into values, in order of appearance, by index. A common choice of @racket[flo] here is @racket[list], yielding the usual "zip."
156+
157+
If the input lists aren't all of the same size, the operation is truncated when the shortest list is exhausted.
158+
159+
In the common case where @racket[flo] is @code{1 × 1} (i.e., where it accepts one input and produces one output), and where the input lists are all of the same size, the number of outputs will be the same as the length of each input list. But as @seclink["What_is_a_Flow_"]{flows can produce any number of values}, this is not necessarily the case in general.
156160

157161
@racket[△] and @racket[▽] often allow you to use functions directly where you might otherwise need to use an indirection like @racket[apply] or @racket[list].
158162

159163
@examples[
160164
#:eval eval-for-docs
161165
((☯ (~> △ +)) (list 1 2 3 4))
162166
((☯ (~> △ (>< sqr) ▽)) (list 1 2 3 4))
163-
((☯ (~> (△ +) ▽)) (list 1 2 3) 10)
167+
((☯ (~> (△ +) ▽)) (list 1 2 3) (list 10 10 10))
164168
(struct kitten (name age) #:transparent)
165169
((☯ (~> (△ kitten) ▽))
166-
(list "Ferdinand" "Imp" "Zacky") 0)
170+
(list "Ferdinand" "Imp" "Zacky") (list 0 0 0))
171+
((☯ (△ list)) '(a b c) '(1 2 3))
172+
((☯ (△ +)) (list 1 2 3) (list 1 2 3))
173+
((☯ (△ (~> (-< _ _) ▽))) (list 1 2 3))
167174
]
168175
}
169176

@@ -485,24 +492,6 @@ Note that the symbol form uses Unicode @code{0x2225} corresponding to LaTeX's @c
485492
]
486493
}
487494

488-
@deftogether[(
489-
@defform[(zip flo)]
490-
@defidform[#:link-target? #f zip]
491-
)]{
492-
Combine corresponding members of each input list using @racket[flo], producing these results directly as @seclink["values-model" #:doc '(lib "scribblings/reference/reference.scrbl")]{values}.
493-
494-
If the lists aren't all of the same size, the operation is truncated when the shortest list is exhausted. If no @racket[flo] is specified, it defaults to @racket[list].
495-
496-
In the common case where @racket[flo] is @code{1 × 1} (i.e., where it accepts one input and produces one output), and where the input lists are all of the same size, the number of outputs will be the same as the length of each input list. But as @seclink["What_is_a_Flow_"]{flows can produce any number of values}, this is not necessarily the case in general.
497-
498-
@examples[
499-
#:eval eval-for-docs
500-
((☯ zip) '(a b c) '(1 2 3))
501-
((☯ (zip +)) (list 1 2 3) (list 1 2 3))
502-
((☯ (zip (~> (-< _ _) ▽))) (list 1 2 3))
503-
]
504-
}
505-
506495
@deftogether[(
507496
@defidform[fanout]
508497
@defform[#:link-target? #f

qi-doc/scribblings/list-operations.scrbl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Deforestable version of @racket[filter] from @racketmodname[racket/base].
7474
#:contracts
7575
((proc (-> any/c any/c)))]{
7676

77-
Deforestable version of @racket[map] from @racketmodname[racket/base]. Note that, unlike the Racket version, this accepts only one argument. For the "zip"-like behavior with multiple list inputs, see @racket[zip].
77+
Deforestable version of @racket[map] from @racketmodname[racket/base]. Note that, unlike the Racket version, this accepts only one argument. For the "zip"-like behavior with multiple list inputs, see @racket[].
7878

7979
}
8080

qi-doc/scribblings/principles.scrbl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ When reading languages like English, we understand what we read in terms of word
8888
@;clarify arity of adaptation; and scribble mathify some of these things (and review/distinguish from code); and add diagrams - look at the prolog bookmark for the fold diagram
8989
@itemlist[
9090
@item{@racket[(~> △ (>< f) ...)] -- "sep-amp". A standard mapping over values extracted from a list.}
91+
@item{@racket[(△ ▽)] -- "zip". A standard way to collect corresponding elements of lists.}
9192
@item{@racket[(~> (-< _ f ...) ...)] -- "augment". The tee junction may augment the flow in any order -- the signature of this phrase is the presence of a @racket[_] in the tee junction.}
9293
@item{@racket[(~> (-< f ...) g)] -- "diamond composition". This is one way to adapt a flow @racket[f] of arity @${k} to a flow @racket[g] of arity @${m}, that is, by branching the @${k} inputs into @${m} copies of @racket[f] (assuming @racket[f] produces one output). It is the same as the "composition operator" used in defining @hyperlink["https://en.wikipedia.org/wiki/Primitive_recursive_function"]{primitive recursive functions}.}
9394
@item{@racket[(group 1 car-flo cdr-flo)] -- "pare". This is analogous to "car and cdr" style destructuring with lists, but for segregating values instead. Note that while it is analogous, this isn't "destructuring," since the values taken together @seclink["Values_are_Not_Collections"]{do not form a data structure}.}
@@ -152,6 +153,7 @@ Qi flow expressions expand to a small core language which is then @seclink["It_s
152153
[floe _
153154
(gen expr ...)
154155
sep
156+
(sep floe)
155157
collect
156158
(esc expr)
157159
(clos floe)
@@ -171,8 +173,6 @@ Qi flow expressions expand to a small core language which is then @seclink["It_s
171173
(relay floe ...)
172174
tee
173175
(tee floe ...)
174-
zip
175-
(zip floe)
176176
fanout
177177
(fanout nat)
178178
feedback

qi-doc/scribblings/using-qi.scrbl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ For such cases, by means of a @racket[divert] (or its alias, @racket[%]) clause
350350
[begin? (~> (== begin-actions _) eval-sequence)]
351351
[cond? (~> (== cond->if _) eval)]
352352
[application? (~> (-< (~> (== operator _) eval)
353-
(~> (== operands _) (△ eval))) apply)]
353+
(~> (== operands (as env)) (>< (eval env)))) apply)]
354354
[else (error "Unknown expression type -- EVAL" 1>)])
355355
}
356356

qi-lib/flow/core/compiler/1000-qi0.rkt

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
;; map and filter
6060
[e:amp-form (amp-parser #'e)] ; NOTE: technically not core
6161
[e:pass-form (pass-parser #'e)] ; NOTE: technically not core
62-
[e:zip-form (zip-parser #'e)]
6362
;; prisms
6463
[e:sep-form (sep-parser #'e)]
6564
[(~or* (~datum ▽) (~datum collect))
@@ -167,15 +166,17 @@ already handled during expansion by Syntax Spec.
167166

168167
(define (sep-parser stx)
169168
(syntax-parse stx
170-
[_:id
171-
#'(qi0->racket (if (esc list?)
172-
(#%fine-template (apply values _))
173-
(#%fine-template (raise-argument-error '△
174-
"list?"
175-
_))))]
176-
[(_ onex:clause)
177-
#'(λ (v . vs)
178-
((qi0->racket (~> △ (>< (#%fine-template (apply (qi0->racket onex) _ vs))))) v))]))
169+
[(_ op:clause)
170+
#'(zip-with (qi0->racket op))]
171+
[_:id #'(λ args
172+
(if (singleton? args)
173+
(let ([v (first args)])
174+
(if (list? v)
175+
(apply values v) ; fast path to the basic △ behavior
176+
(raise-argument-error '△
177+
"list?"
178+
v)))
179+
(apply (qi0->racket (△ _)) args)))]))
179180

180181
(define (select-parser stx)
181182
(syntax-parse stx
@@ -337,12 +338,6 @@ already handled during expansion by Syntax Spec.
337338
[(_ onex:clause)
338339
#'(curry map-values (qi0->racket onex))]))
339340

340-
(define (zip-parser stx)
341-
(syntax-parse stx
342-
[(_ op:clause)
343-
#'(zip-with (qi0->racket op))]
344-
[_:id #'(qi0->racket (zip (esc list)))]))
345-
346341
(define (pass-parser stx)
347342
(syntax-parse stx
348343
[_:id

qi-lib/flow/core/runtime.rkt

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
feedback-times
1818
feedback-while
1919
kw-helper
20+
singleton?
2021
zip-with)
2122

2223
(require racket/match
@@ -171,25 +172,36 @@
171172

172173
(define for-all andmap)
173174

175+
(define (singleton? seq)
176+
;; cheap check to see if a list is of length 1,
177+
;; instead of traversing to compute the length
178+
(and (not (empty? seq))
179+
(empty? (rest seq))))
180+
174181
(define (~zip-with op seqs truncate)
175-
(if (empty? seqs)
176-
null
177-
(if (exists empty? seqs)
178-
(if (for-all empty? seqs)
179-
null
180-
(if truncate
181-
null
182-
(apply raise-arity-error
183-
'zip-with
184-
0
185-
(first (filter (negate empty?) seqs)))))
186-
(let ([vs (map first seqs)])
187-
(append (values->list (apply op vs))
188-
(~zip-with op (map rest seqs) truncate))))))
182+
(if (exists empty? seqs)
183+
(if (for-all empty? seqs)
184+
null
185+
(if truncate
186+
null
187+
(apply raise-arity-error
188+
'zip-with
189+
0
190+
(first (filter (negate empty?) seqs)))))
191+
(let ([vs (map first seqs)])
192+
(append (values->list (apply op vs))
193+
(~zip-with op (map rest seqs) truncate)))))
189194

190195
(define (zip-with op)
191196
(λ seqs
192-
(apply values (apply ~zip-with (list op seqs #true)))))
197+
(if (empty? seqs)
198+
(values)
199+
(let ([v (first seqs)])
200+
(if (list? v)
201+
(apply values (apply ~zip-with (list op seqs #true)))
202+
(raise-argument-error 'zip-with
203+
"list?"
204+
v))))))
193205

194206
;; from mischief/function - requiring it runs aground
195207
;; of some "name is protected" error while building docs, not sure why;

qi-lib/flow/core/syntax.rkt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
fanout-form
1515
if-form
1616
pass-form
17-
zip-form
1817
fold-left-form
1918
fold-right-form
2019
loop-form
@@ -113,12 +112,6 @@ See comments in flow.rkt for more details.
113112
(pattern
114113
((~datum pass) arg ...)))
115114

116-
(define-syntax-class zip-form
117-
(pattern
118-
(~datum zip))
119-
(pattern
120-
((~datum zip) arg)))
121-
122115
(define-syntax-class fold-left-form
123116
(pattern
124117
(~datum >>))

qi-lib/flow/extended/expander.rkt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,6 @@ core language's use of #%app, etc.).
102102
(report-syntax-error this-syntax
103103
"(>< flo)"
104104
"amp expects a single flow specification, but it received many."))
105-
(zip op:closed-floe)
106-
zip
107105
pass
108106
(pass f:closed-floe)
109107
sep

qi-lib/flow/extended/util.rkt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
amp
1717
tee
1818
relay
19-
zip
2019
gen
2120
pass
2221
sep
@@ -54,9 +53,6 @@
5453
[(relay
5554
expr ...)
5655
#`(== #,@(map prettify-flow-syntax (syntax->list #'(expr ...))))]
57-
[(zip
58-
expr ...)
59-
#`(zip #,@(map prettify-flow-syntax (syntax->list #'(expr ...))))]
6056
[(gen
6157
expr ...)
6258
#`(gen #,@(map prettify-flow-syntax (syntax->list #'(expr ...))))]

qi-sdk/benchmarks/local/benchmarks.rkt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ for the forms are run.
183183

184184
(define (~zip . vs)
185185
(apply
186-
(☯ (zip +))
186+
(☯ ( +))
187187
vs))
188188

189189
(define (run)

qi-test/tests/expander.rkt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@
104104
((#%host-expression f)
105105
(#%host-expression 1)
106106
__))))
107-
(test-expand "zip"
108-
#'(zip (>< f))
109-
#'(zip (amp (esc (#%host-expression f)))))
107+
(test-expand "sep"
108+
#'(sep (>< f))
109+
#'(sep (amp (esc (#%host-expression f)))))
110110
(test-expand "#%deforestable"
111111
#'(#%deforestable name info (floe 0) (expr 0))
112112
#'(#%deforestable name
@@ -128,7 +128,6 @@
128128
(relay (esc (#%host-expression f)) (esc (#%host-expression g)))
129129
(tee (esc (#%host-expression f)) (esc (#%host-expression g)))
130130
(thread (esc (#%host-expression f)) (esc (#%host-expression g)))
131-
(zip (esc (#%host-expression f)))
132131
(gen (#%host-expression 2) (#%host-expression 3))
133132
(pass (esc (#%host-expression f)))
134133
(sep (esc (#%host-expression g)))
@@ -162,7 +161,6 @@
162161
(== f g)
163162
(-< f g)
164163
(~> f g)
165-
(zip f)
166164
(gen 2 3)
167165
(pass f)
168166
(sep g)

0 commit comments

Comments
 (0)