Skip to content

Commit 7410fe7

Browse files
committed
Remove initialization optimization for numbers.
1 parent 7e84ba5 commit 7410fe7

File tree

5 files changed

+239
-163
lines changed

5 files changed

+239
-163
lines changed

CHANGELOG.md

+42
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,46 @@ version is needed for generic sequences.
7272
- Allow the `unique` keyword argument of the commands `map` and `map-ref` to be
7373
evaluable at run time, instead of just checked at compile time ([#209]).
7474

75+
- Remove the initialization optimizations that produced faster code but
76+
could change final results ([#226, #204]). For example, consider the
77+
difference results between `cl-loop` and SBCL's `loop`:
78+
79+
``` emacs-lisp
80+
;; => (4 (1 2 3))
81+
(cl-loop for elem in (list 1 2 3)
82+
for i from 1
83+
collect i into is
84+
finally return (list i is))
85+
```
86+
87+
88+
``` common-lisp
89+
;; => (3 (1 2 3))
90+
(loop for elem in (list 1 2 3)
91+
for num from 1
92+
collect num into nums
93+
finally (return (list num nums)))
94+
```
95+
96+
Loopy would give the same result as `cl-loop` when using the optimization and
97+
would given the same result as SBCL's `loop` when not using the optimization.
98+
99+
Working around having different results based on unstated (though documented)
100+
settings would mean requiring users to fully know the implementations of each
101+
loop command and how they could change when optimized. That position also
102+
argues against making use of the optimization more explicit via an added
103+
`iter-opt` special macro argument, as discussed in [#226] and [#204].
104+
105+
Therefore, these optimizations are being removed and Loopy is reverting to its
106+
previous behavior of initializing the iteration variables to `nil` by default
107+
for the following commands:
108+
- `cons`
109+
- `cycle`
110+
- `iter`
111+
- `numbers`
112+
- `seq-index`
113+
- `substream`
114+
75115
### Improvements
76116

77117
- The `map` and `map-ref` commands now check for duplicate keys step by step,
@@ -107,6 +147,7 @@ version is needed for generic sequences.
107147
[#179]: https://github.com/okamsn/loopy/issues/179
108148
[#184]: https://github.com/okamsn/loopy/issues/184
109149
[#203]: https://github.com/okamsn/loopy/pull/203
150+
[#204]: https://github.com/okamsn/loopy/issues/204
110151
[#205]: https://github.com/okamsn/loopy/pull/205
111152
[#206]: https://github.com/okamsn/loopy/pull/206
112153
[#207]: https://github.com/okamsn/loopy/pull/207
@@ -117,6 +158,7 @@ version is needed for generic sequences.
117158
[#213]: https://github.com/okamsn/loopy/pull/213
118159
[#215]: https://github.com/okamsn/loopy/pull/215
119160
[#217]: https://github.com/okamsn/loopy/pull/217
161+
[#226]: https://github.com/okamsn/loopy/pull/226
120162

121163
## 0.13.0
122164

README.org

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ please let me know.
5353
beginning of the loop.
5454
- The =:on-failure= argument of the =find= command is now evaluated at the
5555
beginning of the loop.
56+
- Changed back to the old, slightly slower behavior of always initializing
57+
iteration variables to ~nil~, instead of sometimes initializing to the
58+
expected value during the first iteration step. This affects =cons=,
59+
=cycle=, =iter=, =numbers=, =seq-index=, and =substream=.
5660
- Version 0.13.0:
5761
- The deprecated =:init= keyword argument has been removed. Use the =with=
5862
special macro argument instead.

doc/loopy-doc.org

+100-40
Original file line numberDiff line numberDiff line change
@@ -1436,14 +1436,6 @@ In ~loopy~, iteration commands are named after what they iterate through. For
14361436
example, the =array= and =list= commands iterate through the elements of arrays
14371437
and lists, respectively.
14381438

1439-
#+ATTR_TEXINFO: :tag Note
1440-
#+begin_quote
1441-
In general, iteration variables (such as the ~i~ and ~j~ above) are initialized
1442-
to ~nil~. For efficiency, some commands do not do this. In such cases, the
1443-
initial value of an iteration variable can be set using the =with= special macro
1444-
argument, but this can result in less efficient code.
1445-
#+end_quote
1446-
14471439
Because some iteration commands use their variable to manage state, it is an
14481440
error to use the same iteration variable for multiple iteration commands.
14491441

@@ -1454,6 +1446,62 @@ error to use the same iteration variable for multiple iteration commands.
14541446
(finally-return t))
14551447
#+end_src
14561448

1449+
Iteration variables are initialized to ~nil~ and they are updated at the point
1450+
in the loop body corresponding to the loop command's position in the macro's
1451+
arguments.
1452+
1453+
#+begin_src emacs-lisp
1454+
;; `elem' retains its value from the previous
1455+
;; iteration until it is updated again:
1456+
;;
1457+
;; => (((1 . nil) ; before
1458+
;; (2 . 1)
1459+
;; (3 . 2)
1460+
;; (4 . 3))
1461+
;; ((1 . 1) ; after
1462+
;; (2 . 2)
1463+
;; (3 . 3)
1464+
;; (4 . 4)))
1465+
(loopy (numbers nth :from 1)
1466+
(collect elem-before (cons nth elem))
1467+
(list elem '(1 2 3 4))
1468+
(collect elem-after (cons nth elem))
1469+
(finally-return elem-before
1470+
elem-after))
1471+
#+end_src
1472+
1473+
Generally, iteration commands with conditions check whether to terminate the
1474+
loop /before/ the next iteration is run. They do not check their conditions
1475+
while running the current iteration. In the below example, note that the final
1476+
value of ~i~ is 2 and not 3, even though the =do= command (similar to
1477+
~cl-loop~'s =do= keyword) is placed before the =list= command. Even though ~i~
1478+
is updated before ~elem~ is updated, the =list= command's condition that decides
1479+
whether to continue the loop is checked /before/ the code in the =do= command is
1480+
run.
1481+
1482+
#+begin_src emacs-lisp
1483+
;; => 2, not 3
1484+
(let ((i 0))
1485+
(loopy (do (setq i (1+ i)))
1486+
(list elem '(0 1)))
1487+
i)
1488+
#+end_src
1489+
1490+
If you do wish to conditionally leave the loop during an iteration, consider
1491+
using the =leave= and =leave-from= commands ([[#exiting-the-loop-early]]).
1492+
1493+
#+begin_src emacs-lisp
1494+
;; => (3 (0 1))
1495+
(loopy (with (some-list (list 0 1))
1496+
(i 0))
1497+
(do (setq i (1+ i)))
1498+
(when (null some-list)
1499+
(leave))
1500+
(collect elems (car some-list))
1501+
(do (setq some-list (cdr some-list)))
1502+
(finally-return i elems))
1503+
#+end_src
1504+
14571505
Unlike ~cl-loop~ and like Common Lisp's ~iterate~, arguments of the iteration
14581506
commands are evaluated only once. For example, while iterating through numbers,
14591507
you can't suddenly change the direction of the iteration in the middle of the
@@ -1472,12 +1520,15 @@ loop. This restriction allows for producing more efficient code.
14721520
#+findex: cycling
14731521
#+findex: repeat
14741522
#+findex: repeating
1475-
- =(cycle|repeat [VAR] EXPR)= :: Run the loop for =EXPR= iterations. If
1476-
specified, =VAR= starts at 0, and is incremented by 1 at the end of each step
1477-
in the loop. If =EXPR= is 0, then the loop isn't run.
1523+
- =(cycle|repeat [VAR] EXPR)= :: Run the loop for =EXPR= iterations.
1524+
1525+
If given, then during the loop, =VAR= is set to the number of iteration steps
1526+
that have been run (0 for the first iteration step).
14781527

1479-
For efficiency, =VAR= is not initialized to ~nil~. This can be overridden
1480-
using the =with= special macro argument, which can result in slower code.
1528+
If =EXPR= is 0, then the loop isn't run.
1529+
1530+
=(cycle VAR EXPR)= works the same as =(numbers VAR :from 0 :below EXPR)=
1531+
([[#numeric-iteration]]).
14811532

14821533
This command also has the aliases =cycling= and =repeating=.
14831534

@@ -1493,6 +1544,14 @@ loop. This restriction allows for producing more efficient code.
14931544
(collect i)
14941545
(collect j))
14951546

1547+
;; Same as above:
1548+
;;
1549+
;; => (10 0 10 1 10 2)
1550+
(loopy (with (i 10))
1551+
(numbers j :from 0 :below 3)
1552+
(collect i)
1553+
(collect j))
1554+
14961555
;; An argument of 0 stops the loop from running:
14971556
;; => nil
14981557
(loopy (cycle 0)
@@ -1519,13 +1578,6 @@ loop. This restriction allows for producing more efficient code.
15191578
once, =yield-result= is an expression which is substituted into the loop body.
15201579
Therefore, =yield-result= can be used to repeatedly call functions.
15211580

1522-
For efficiency, when possible, =VAR= is bound to the yielded value before each
1523-
step of the loop, which is used to detect whether the iterator signals that it
1524-
is finished. This is not possible when destructuring. You can override this
1525-
behavior by using the =with= special macro argument, which can result in
1526-
slower code and tells the macro that the initial value of =VAR= is meaningful
1527-
and to update =VAR= during the loop.
1528-
15291581
This command also has the name =iterating=.
15301582

15311583
#+begin_src emacs-lisp
@@ -1632,11 +1684,6 @@ variants =numbers-up= and =numbers-down=.
16321684
=(list i (number-sequence 1 10))=, and =(numbers i 3)= is similar to
16331685
=(set i 3 (1+ i))=.
16341686

1635-
For efficiency, _=VAR= is initialized to the starting numeric value_, not
1636-
~nil~, and is updated at the end of each step of the loop. This can be
1637-
overridden using the =with= special macro argument, which can result in slower
1638-
code.
1639-
16401687
In its most basic form, =numbers= iterates from a starting value to an
16411688
inclusive ending value using the =:from= and =:to= keywords, respectively.
16421689

@@ -1646,6 +1693,34 @@ variants =numbers-up= and =numbers-down=.
16461693
(collect i))
16471694
#+end_src
16481695

1696+
Unlike ~cl-loop~, =VAR= is not initialized to the starting value given.
1697+
Instead, =VAR= is updated during the loop, like in other iteration
1698+
commands. This avoids unexpectedly changing the value of =VAR= after the
1699+
iteration step, as happens with some implementations of Common Lisp's ~loop~
1700+
macro (such ~cl-loop~).
1701+
1702+
#+begin_src emacs-lisp
1703+
;; => (4 (1 2 3 4))
1704+
(loopy (list elem (list 1 2 3 4))
1705+
(numbers num :from 1)
1706+
(collect nums num)
1707+
(finally-return num nums))
1708+
1709+
;; => (5 (1 2 3 4))
1710+
(cl-loop for elem in (list 1 2 3 4)
1711+
for num from 1
1712+
collect num into nums
1713+
finally return (list num nums))
1714+
1715+
;; SBCL returns 4, not 5:
1716+
;;
1717+
;; => (4 (1 2 3 4))
1718+
(loop for elem in (list 1 2 3 4)
1719+
for num from 1
1720+
collect num into nums
1721+
finally (return (list num nums)))
1722+
#+end_src
1723+
16491724
If the ending value is not given, then the value is incremented by 1 without
16501725
end.
16511726

@@ -1929,11 +2004,6 @@ source sequences.
19292004
- =(cons|conses VAR EXPR &key by)= :: Loop through the cons cells of =EXPR=.
19302005
Optionally, find the cons cells via the function =by= instead of =cdr=.
19312006

1932-
For efficiency, when possible, =VAR= is initialized to the value of =EXPR=,
1933-
not ~nil~, and is updated at the end of each step in the loop. This is not
1934-
possible when destructuring. Such initialization can be overridden by using
1935-
the =with= special macro argument, which can result in slower code.
1936-
19372007
This command also has the alias =consing=.
19382008

19392009
#+BEGIN_SRC emacs-lisp
@@ -2214,11 +2284,6 @@ source sequences.
22142284
and are compatible with features from the built-in =seq= library, such as
22152285
~seq-elt~ and ~seq-do~.
22162286

2217-
For efficiency, when possible, =VAR= is initialized to the value of =EXPR=,
2218-
not ~nil~, and is updated at the end of each step in the loop. This is not
2219-
possible when destructuring. Such initialization can be overridden by using
2220-
the =with= special macro argument, which can result in slower code.
2221-
22222287
Sub-streams can only be destructured using the =&seq= feature of the default
22232288
destructuring method ([[#basic-destructuring][Basic Destructuring]]), or by using the =seq= flag
22242289
([[#flags][Using Flags]]). Streams are neither lists nor arrays.
@@ -2313,11 +2378,6 @@ iterate.
23132378
With =numbers=, one would first need to explicitly calculate the length of the
23142379
sequence.
23152380

2316-
Similar to =numbers=, for efficiency, =VAR= is initialized to the starting
2317-
index value, not ~nil~, and is updated at the end of each step of the loop.
2318-
This can be overridden using the =with= special macro argument, which can
2319-
result in slower code.
2320-
23212381
#+begin_src emacs-lisp
23222382
;; => (97 98 99 100 101 102)
23232383
(loopy (with (my-string "abcdef"))

0 commit comments

Comments
 (0)