Skip to content

Commit 9d07251

Browse files
committed
Review evaluation of keyword arguments during run time and expansion time.
- Make `close` in `iter` evaluable. Make it evaluated at the beginning. - Make `on-failure` in `find` evaluated at the beginning. Closes issue #210.
1 parent a374774 commit 9d07251

File tree

4 files changed

+174
-55
lines changed

4 files changed

+174
-55
lines changed

doc/loopy-doc.org

+37-6
Original file line numberDiff line numberDiff line change
@@ -1507,13 +1507,17 @@ loop. This restriction allows for producing more efficient code.
15071507
holds the value yielded by the iterator. The loop ends when the iterator
15081508
finishes.
15091509

1510+
=close= is whether the generator should be closed via ~iter-close~ after the
1511+
loop ends. The default is ~t~. Note that Emacs will eventually close
1512+
un-closed, un-reachable generators during garbage collection. To be
1513+
consistent with other commands, =close= is evaluated at the start of the loop,
1514+
even though it's value is only used after the loop finishes.
1515+
15101516
=yield-result= is the optional second argument to the function ~iter-next~,
15111517
which is the value of ~iter-yield~ in the iterator (not to be confused with
1512-
the value yielded by calling ~iter-next~).
1513-
1514-
=close= is whether the generator should be closed via ~iter-close~ after the
1515-
loop ends. The default is ~t~. Note that Emacs will eventually close
1516-
un-closed, un-reachable generators during garbage collection.
1518+
the value yielded by calling ~iter-next~). Unlike =close=, which is evaluated
1519+
once, =yield-result= is an expression which is substituted into the loop body.
1520+
Therefore, =yield-result= can be used to repeatedly call functions.
15171521

15181522
For efficiency, when possible, =VAR= is bound to the yielded value before each
15191523
step of the loop, which is used to detect whether the iterator signals that it
@@ -1526,6 +1530,7 @@ loop. This restriction allows for producing more efficient code.
15261530

15271531
#+begin_src emacs-lisp
15281532
;; With var:
1533+
;;
15291534
;; => ((1 . 4) (2 . 5) (3 . 6))
15301535
(loopy (with (iter-maker (iter-lambda (x)
15311536
(while x
@@ -1535,6 +1540,7 @@ loop. This restriction allows for producing more efficient code.
15351540
(collect (cons i j)))
15361541

15371542
;; Without var:
1543+
;;
15381544
;; => (1 2 3)
15391545
(loopy (iter (funcall (iter-lambda ()
15401546
;; These yielded values are all ignored.
@@ -1543,6 +1549,21 @@ loop. This restriction allows for producing more efficient code.
15431549
(iter-yield 'third-yield))))
15441550
(set i 1 (1+ i))
15451551
(collect i))
1552+
1553+
;; Using `yield-result':
1554+
;;
1555+
;; => (3 2 1)
1556+
(loopy (with (yield-results nil))
1557+
(set i 1 (1+ i))
1558+
(iter (funcall (iter-lambda ()
1559+
;; The value from the expression specified by
1560+
;; `:yield-result' is `push'-ed:
1561+
(push (iter-yield 'first-yield) yield-results)
1562+
(push (iter-yield 'second-yield) yield-results)
1563+
(push (iter-yield 'third-yield) yield-results)))
1564+
;; Note that the value of `i' evaluated each time:
1565+
:yield-result i)
1566+
(finally-return yield-results))
15461567
#+end_src
15471568

15481569
#+ATTR_TEXINFO: :tag Warning
@@ -2123,7 +2144,7 @@ source sequences.
21232144
=EXPR=. If =by= is non-nil (default: 1), then move to the next n-th element
21242145
during each iteration. This command is a special case of the =substream=
21252146
command (described below), setting =VAR= to the first element of each
2126-
substream. For more information, see the command =substream=.
2147+
substream. For more information on streams, see the command =substream=.
21272148

21282149
This command also has the alias =streaming=.
21292150

@@ -3396,6 +3417,9 @@ Sequence accumulation commands are used to join lists (such as =union= and
33963417
normal failure and =VAR= will be set to the value of =ON-FAILURE=, if
33973418
provided.
33983419

3420+
To be consistent with other commands, =ON-FAILURE= is evaluated at the
3421+
start of the loop, even though that is not necessarily where it is used.
3422+
33993423
#+BEGIN_SRC emacs-lisp
34003424
;; => (13 (1 2))
34013425
(loopy (list i '(1 2 3 4 5 6 7 8))
@@ -3419,6 +3443,13 @@ Sequence accumulation commands are used to join lists (such as =union= and
34193443
;; => nil
34203444
(loopy (list i '(1 2 3 4 5 6))
34213445
(find nil (> i 3) :on-failure 27))
3446+
3447+
;; Value of `:on-failure' gotten at the start of the loop:
3448+
;; => 27
3449+
(loopy (with (on-fail 27))
3450+
(list i '(1 2 3))
3451+
(set on-fail 33)
3452+
(find i (> i 4) :on-failure on-fail))
34223453
#+END_SRC
34233454

34243455
*** Optimizing Accumulations

doc/loopy.texi

+41-10
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ You should keep in mind that commands are evaluated in order. This means that
705705
attempting something like the below example might not do what you expect, as @samp{i}
706706
is assigned a value from the list after collecting @samp{i} into @samp{coll}.
707707

708-
@float Listing,org5119497
708+
@float Listing,org57e6a65
709709
@lisp
710710
;; => (nil 1 2)
711711
(loopy (collect coll i)
@@ -887,7 +887,7 @@ the flag @samp{dash} provided by the package @samp{loopy-dash}.
887887

888888
Below are two examples of destructuring in @code{cl-loop} and @code{loopy}.
889889

890-
@float Listing,orgf3a9f7c
890+
@float Listing,orgdb6dedf
891891
@lisp
892892
;; => (1 2 3 4)
893893
(cl-loop for (i . j) in '((1 . 2) (3 . 4))
@@ -902,7 +902,7 @@ Below are two examples of destructuring in @code{cl-loop} and @code{loopy}.
902902
@caption{Destructuring values in a list.}
903903
@end float
904904

905-
@float Listing,org1bf5dc0
905+
@float Listing,orgb7824db
906906
@lisp
907907
;; => (1 2 3 4)
908908
(cl-loop for elem in '((1 . 2) (3 . 4))
@@ -1668,13 +1668,17 @@ iterator object produced by a calling a generator function. If given, @samp{VAR
16681668
holds the value yielded by the iterator. The loop ends when the iterator
16691669
finishes.
16701670

1671+
@samp{close} is whether the generator should be closed via @code{iter-close} after the
1672+
loop ends. The default is @code{t}. Note that Emacs will eventually close
1673+
un-closed, un-reachable generators during garbage collection. To be
1674+
consistent with other commands, @samp{close} is evaluated at the start of the loop,
1675+
even though it's value is only used after the loop finishes.
1676+
16711677
@samp{yield-result} is the optional second argument to the function @code{iter-next},
16721678
which is the value of @code{iter-yield} in the iterator (not to be confused with
1673-
the value yielded by calling @code{iter-next}).
1674-
1675-
@samp{close} is whether the generator should be closed via @code{iter-close} after the
1676-
loop ends. The default is @code{t}. Note that Emacs will eventually close
1677-
un-closed, un-reachable generators during garbage collection.
1679+
the value yielded by calling @code{iter-next}). Unlike @samp{close}, which is evaluated
1680+
once, @samp{yield-result} is an expression which is substituted into the loop body.
1681+
Therefore, @samp{yield-result} can be used to repeatedly call functions.
16781682

16791683
For efficiency, when possible, @samp{VAR} is bound to the yielded value before each
16801684
step of the loop, which is used to detect whether the iterator signals that it
@@ -1687,6 +1691,7 @@ This command also has the name @samp{iterating}.
16871691

16881692
@lisp
16891693
;; With var:
1694+
;;
16901695
;; => ((1 . 4) (2 . 5) (3 . 6))
16911696
(loopy (with (iter-maker (iter-lambda (x)
16921697
(while x
@@ -1696,6 +1701,7 @@ This command also has the name @samp{iterating}.
16961701
(collect (cons i j)))
16971702
16981703
;; Without var:
1704+
;;
16991705
;; => (1 2 3)
17001706
(loopy (iter (funcall (iter-lambda ()
17011707
;; These yielded values are all ignored.
@@ -1704,6 +1710,21 @@ This command also has the name @samp{iterating}.
17041710
(iter-yield 'third-yield))))
17051711
(set i 1 (1+ i))
17061712
(collect i))
1713+
1714+
;; Using `yield-result':
1715+
;;
1716+
;; => (3 2 1)
1717+
(loopy (with (yield-results nil))
1718+
(set i 1 (1+ i))
1719+
(iter (funcall (iter-lambda ()
1720+
;; The value from the expression specified by
1721+
;; `:yield-result' is `push'-ed:
1722+
(push (iter-yield 'first-yield) yield-results)
1723+
(push (iter-yield 'second-yield) yield-results)
1724+
(push (iter-yield 'third-yield) yield-results)))
1725+
;; Note that the value of `i' evaluated each time:
1726+
:yield-result i)
1727+
(finally-return yield-results))
17071728
@end lisp
17081729

17091730
@quotation Warning
@@ -2305,7 +2326,7 @@ Iterate through the elements for the stream
23052326
@samp{EXPR}. If @samp{by} is non-nil (default: 1), then move to the next n-th element
23062327
during each iteration. This command is a special case of the @samp{substream}
23072328
command (described below), setting @samp{VAR} to the first element of each
2308-
substream. For more information, see the command @samp{substream}.
2329+
substream. For more information on streams, see the command @samp{substream}.
23092330

23102331
This command also has the alias @samp{streaming}.
23112332

@@ -3677,6 +3698,9 @@ If the loop is left early and @samp{TEST} was never non-nil, this is the same as
36773698
normal failure and @samp{VAR} will be set to the value of @samp{ON-FAILURE}, if
36783699
provided.
36793700

3701+
To be consistent with other commands, @samp{ON-FAILURE} is evaluated at the
3702+
start of the loop, even though that is not necessarily where it is used.
3703+
36803704
@lisp
36813705
;; => (13 (1 2))
36823706
(loopy (list i '(1 2 3 4 5 6 7 8))
@@ -3700,6 +3724,13 @@ provided.
37003724
;; => nil
37013725
(loopy (list i '(1 2 3 4 5 6))
37023726
(find nil (> i 3) :on-failure 27))
3727+
3728+
;; Value of `:on-failure' gotten at the start of the loop:
3729+
;; => 27
3730+
(loopy (with (on-fail 27))
3731+
(list i '(1 2 3))
3732+
(set on-fail 33)
3733+
(find i (> i 4) :on-failure on-fail))
37033734
@end lisp
37043735
@end table
37053736

@@ -4653,7 +4684,7 @@ using the @code{let*} special form.
46534684
This method recognizes all commands and their aliases in the user option
46544685
@code{loopy-aliases}.
46554686

4656-
@float Listing,orgb546acb
4687+
@float Listing,org36b4a9a
46574688
@lisp
46584689
;; => ((1 2 3) (-3 -2 -1) (0))
46594690
(loopy-iter (arg accum-opt positives negatives other)

loopy-commands.el

+52-39
Original file line numberDiff line numberDiff line change
@@ -782,8 +782,9 @@ is a function by which to update VAR (default `cdr')."
782782
783783
VAR is the variable. VAL is an generator-producing function, as
784784
from `iter-defun' or `iter-lambda'. YIELD-RESULT is the optional
785-
value of `iter-next'. CLOSE is whether the iterator should be
786-
closed after the loop completes."
785+
value of `iter-next' and is an expression, not a value. CLOSE is
786+
whether the iterator should be closed after the loop completes
787+
and is a value."
787788
:required-vals 0 ;; Require values /after/ `var'.
788789
:other-vals (0 1)
789790
:keywords (:yield-result :close)
@@ -825,11 +826,16 @@ closed after the loop completes."
825826
(iter-next ,obj-holder ,yield-result)
826827
(iter-end-of-sequence nil)
827828
(:success t))))))
828-
,(when close
829-
`(loopy--vars-final-updates
830-
(,obj-holder . (iter-close ,obj-holder))))))))
831-
832-
829+
,@(pcase close
830+
('nil nil)
831+
('t `((loopy--vars-final-updates
832+
(,obj-holder . (iter-close ,obj-holder)))))
833+
(_
834+
(loopy--instr-let-const* ((close2 close))
835+
loopy--iteration-vars
836+
`((loopy--vars-final-updates
837+
(,obj-holder . (when ,close2
838+
(iter-close ,obj-holder))))))))))))
833839

834840
;;;;;; List
835841
;; TODO: Make this a normal function.
@@ -2161,57 +2167,64 @@ This function is called by `loopy--expand-optimized-accum'."
21612167

21622168
;;;;;;; Find
21632169
(loopy--defaccumulation find
2164-
"Parse a command of the form `(finding VAR EXPR TEST &key ON-FAILURE)'."
2170+
"Parse a command of the form `(finding VAR EXPR TEST &key ON-FAILURE)'.
2171+
2172+
ON-FAILURE is evaluated at the beginning of the loop, even though
2173+
it is only meaningful at the end."
21652174
:num-args 3
21662175
:keywords (on-failure)
21672176
:explicit (let* ((test-arg (cl-third args))
21682177
(test-form (if (loopy--quoted-form-p test-arg)
21692178
`(,(loopy--get-function-symbol test-arg) ,val)
21702179
test-arg))
2171-
(of-used (plist-member opts :on-failure))
2180+
(on-failure-given (plist-member opts :on-failure))
21722181
(on-failure (plist-get opts :on-failure))
21732182
(tag-name (loopy--produce-non-returning-exit-tag-name
21742183
loopy--loop-name))
21752184
(found (gensym "found")))
21762185

2177-
`((loopy--non-returning-exit-used ,tag-name)
2178-
(loopy--accumulation-vars (,var nil))
2179-
,@(if of-used
2180-
;; If TEST always nil, bind to ON-FAILURE.
2181-
`((loopy--accumulation-vars (,found nil))
2182-
(loopy--main-body (when ,test-form
2183-
(setq ,var ,val
2184-
,found t)
2185-
(throw (quote ,tag-name) t)))
2186-
(loopy--vars-final-updates
2187-
(,var . (unless ,found (setq ,var ,on-failure)))))
2188-
`((loopy--main-body (when ,test-form
2189-
(setq ,var ,val)
2190-
(throw (quote ,tag-name) t)))))))
2186+
(loopy--instr-let-const* ((on-failure2 on-failure))
2187+
loopy--accumulation-vars
2188+
`((loopy--non-returning-exit-used ,tag-name)
2189+
(loopy--accumulation-vars (,var nil))
2190+
,@(if on-failure-given
2191+
;; If TEST always nil, bind to ON-FAILURE.
2192+
`((loopy--accumulation-vars (,found nil))
2193+
(loopy--main-body (when ,test-form
2194+
(setq ,var ,val
2195+
,found t)
2196+
(throw (quote ,tag-name) t)))
2197+
(loopy--vars-final-updates
2198+
(,var . (unless ,found (setq ,var ,on-failure2)))))
2199+
`((loopy--main-body (when ,test-form
2200+
(setq ,var ,val)
2201+
(throw (quote ,tag-name) t))))))))
21912202
:implicit (let* ((test-arg (cl-second args))
21922203
(test-form (if (loopy--quoted-form-p test-arg)
21932204
`(,(loopy--get-function-symbol test-arg) ,val)
21942205
test-arg))
2195-
(of-used (plist-member opts :on-failure))
2206+
(on-failure-given (plist-member opts :on-failure))
21962207
(on-failure (plist-get opts :on-failure))
21972208
(found (gensym "found"))
21982209
(tag-name (loopy--produce-non-returning-exit-tag-name
21992210
loopy--loop-name)))
2200-
`((loopy--non-returning-exit-used ,tag-name)
2201-
(loopy--accumulation-vars (,var nil))
2202-
,@(if of-used
2203-
;; If TEST always nil, bind to ON-FAILURE.
2204-
`((loopy--accumulation-vars (,found nil))
2205-
(loopy--main-body (when ,test-form
2206-
(setq ,var ,val
2207-
,found t)
2208-
(throw (quote ,tag-name) t)))
2209-
(loopy--vars-final-updates
2210-
(,var . (unless ,found (setq ,var ,on-failure)))))
2211-
`((loopy--main-body (when ,test-form
2212-
(setq ,var ,val)
2213-
(throw (quote ,tag-name) t)))))
2214-
(loopy--implicit-return ,var))))
2211+
(loopy--instr-let-const* ((on-failure2 on-failure))
2212+
loopy--accumulation-vars
2213+
`((loopy--non-returning-exit-used ,tag-name)
2214+
(loopy--accumulation-vars (,var nil))
2215+
,@(if on-failure-given
2216+
;; If TEST always nil, bind to ON-FAILURE.
2217+
`((loopy--accumulation-vars (,found nil))
2218+
(loopy--main-body (when ,test-form
2219+
(setq ,var ,val
2220+
,found t)
2221+
(throw (quote ,tag-name) t)))
2222+
(loopy--vars-final-updates
2223+
(,var . (unless ,found (setq ,var ,on-failure2)))))
2224+
`((loopy--main-body (when ,test-form
2225+
(setq ,var ,val)
2226+
(throw (quote ,tag-name) t)))))
2227+
(loopy--implicit-return ,var)))))
22152228

22162229
;;;;;;; Set Accum
22172230
(loopy--defaccumulation set-accum

0 commit comments

Comments
 (0)