Skip to content

Commit

Permalink
Improve efficiency and various clean-ups.
Browse files Browse the repository at this point in the history
- Add `loopy--instr-let-var*`, `loopy--instr-let-const*`,
  `loopy--instr-let-var`, `loopy--instr-let-const`.  These macros are similar
  to `macroexp-let2*` in that the `const` versions try to pass constant values
  directly without creating a variable in the Loopy expansion.
  - Update `array`, `array-ref`, `cons`, `list`,  `list-ref`,
    `seq`, `seq-ref`, `seq-index`, `adjoin`, `union`, `nunion`

  - Don't update `numbers` until after we remove the non-keyword args.

- Replace some uses of `seq-let` with `cl-destructuring-bind`.

- Use `loopy--bind-main-body` in some places.

- Add some TODOs.

- Fix `list-ref` tests to not modify literal constant list.
- Fix `seq-ref` tests to not modify literal constant list.

- Make `loopy--find-start-by-end-dir-vals` return the test function.

- Add `:test` to `array`, `array-ref`, `sequence`, `sequence-index`, and
  `sequence-ref`.
  - When going up on lists, use `nthcdr` instead of `elt`.

- Add `sequence`, `sequence-ref`, `sequence-index`, `array`, and `array-ref`
  tests for `:downfrom` and `:upfrom` as needed.

See also issue #176 and this PR #180.
  • Loading branch information
okamsn committed Nov 8, 2023
1 parent 3819e0f commit 3aebae9
Show file tree
Hide file tree
Showing 5 changed files with 979 additions and 455 deletions.
23 changes: 18 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ This document describes the user-facing changes to Loopy.
(list i '(4 5 6)))
```

- In `adjoin`, `nunion`, and `union`, the `test` and `key` keywords are now
evaluated only once. This is now consistent with passing function values of
other loop commands. See [#170] and [#177].
- The keyword arguments of commands are now evaluated only once. This is now
consistent with passing function values of other loop commands. If constant
according to `macroexp-const-p`, then they are used directly. Otherwise, the
value is first stored in a variable. See [#170], [#177], [#176], and [#180].

- In accumulation commands using the `test` keyword argument, the argument order
of the two-argument test function is now document as `(SEQUENCE-ITEM,
Expand Down Expand Up @@ -161,9 +162,11 @@ This document describes the user-facing changes to Loopy.
be more convenient and consistent with other commands ([#144]).
- The commands now exit the loop without forcing a return value, which allows
implicit return values to be finalized.

- The commands now use variables to store the implicit return values of the
loop, defaulting to `loopy-result` and which can be specified via `:into`,
similar to accumulation commands.

```elisp
;; => "hello there"
(loopy (list i '(1 1 1 1))
Expand All @@ -176,12 +179,15 @@ This document describes the user-facing changes to Loopy.
(thereis i)
(finally-return (+ loopy-result 4)))
```

- As with other incompatible commands, an error is now signaled when trying to
use `thereis` with `always` or `never` **when using the same variable**
([#144]).

- Add a `:test` keyword argument to `numbers` ([#172]). This is useful when the
direction of the iteration is not known ahead of time.
- Add a `:test` keyword argument to `numbers`, `array`, `array-ref`, `sequence`,
`sequence-ref`, and `sequence-index` ([#172], [#180]). This is useful when
the direction of the iteration is not known ahead of time.

```elisp
;; => (10 9.5 9.0 8.5 8.0 7.5 7.0 6.5 6.0 5.5)
(loopy (with (start 10)
Expand All @@ -192,6 +198,11 @@ This document describes the user-facing changes to Loopy.
(collect i))
```

- By using `macroexp-const-p`, Loopy now better uses constant values ([#176],
[#180]). Instead of always creating a variable in some cases, it now better
uses the constant value directly, which Emacs can optimize to avoid some uses
of `funcall`.

### Other Changes

- Add `loopy--other-vars`, given the more explicit restriction on
Expand All @@ -213,7 +224,9 @@ This document describes the user-facing changes to Loopy.
[#171]: https://github.com/okamsn/loopy/pull/171
[#172]: https://github.com/okamsn/loopy/pull/172
[#173]: https://github.com/okamsn/loopy/pull/173
[#176]: https://github.com/okamsn/loopy/issues/176
[#177]: https://github.com/okamsn/loopy/pull/177
[#180]: https://github.com/okamsn/loopy/pull/180

## 0.11.2

Expand Down
82 changes: 70 additions & 12 deletions doc/loopy-doc.org
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,46 @@ For simplicity, the commands are described using the following notation:


Generally, =VAR= is initialized to ~nil~, but not always. This document tries
to note when that is not the case.
to note when that is not the case. For when that is not the case, the variable
can still be initialized to ~nil~ if it is set to ~nil~ using the =with= special
macro argument. These special cases allow for more efficient code and less
indirection.

#+begin_src emacs-lisp
;; => (0 1 2 3)
(loopy (collect i)
(numbers i :from 0 :to 3))

;; => (nil 0 1 2)
(loopy (with (i nil))
(collect i)
(numbers i :from 0 :to 3))
#+end_src

Unlike ~cl-loop~ in some cases, in Loopy, the values passed as keyword arguments
are evaluated only once. For example, the command =(list i some-list :by
(get-function))= evaluates ~(get-function)~ only once. It does not evaluate it
repeatedly for each step of the loop.

#+begin_src emacs-lisp
;; Passes the assertion:
;;
;; => (0 1 2 3 4 5 6 7 8 9 10)
(loopy (with (times 0))
(list i (number-sequence 0 10) :by (progn
(cl-assert (= times 0))
(cl-incf times)
#'cdr))
(collect i))

;; => Fails the assertion on the second step of the loop:
(cl-loop with times = 0
for i in (number-sequence 0 10) by (progn
(cl-assert (= times 0))
(cl-incf times)
#'cdr)
collect i)
#+end_src

** Basic Destructuring
:PROPERTIES:
Expand Down Expand Up @@ -1102,6 +1141,14 @@ error to use the same iteration variable for multiple iteration commands.
(finally-return t))
#+end_src

Unlike ~cl-loop~ and like Common Lisp's ~iterate~, arguments of the iteration
commands are evaluated only once. For example, while iterating through numbers,
you can't suddenly change the direction of the iteration in the middle of the
loop, nor can you change the final numeric value. Similarly, the function used
to iterate through the list in the =list= command is the same for the entire
loop. This restriction allows for producing more efficient code.


*** Generic Iteration
:PROPERTIES:
:CUSTOM_ID: generic-iteration
Expand Down Expand Up @@ -1364,7 +1411,8 @@ variants =numbers-up= and =numbers-down=.
=from= and =to=; it cannot be used with keywords that already describe a
direction and ending condition. To match the behavior of ~cl-loop~, the
default testing function is ~#'<=~. When =test= is given, =by= can be
negative.
negative. As there is no default end value when =test= is given, =to= must
also be given.

#+begin_src emacs-lisp
;; => (10 9.5 9.0 8.5 8.0 7.5 7.0 6.5 6.0 5.5)
Expand Down Expand Up @@ -1714,9 +1762,19 @@ source sequences.
This command also has the aliases =seqing= and =sequencing=.

=KEYS= is one or several of =from=, =upfrom=, =downfrom=, =to=, =upto=,
=downto=, =above=, =below=, =by=, and =index=. =index= names the variable
used to store the index being accessed. For others, see the =numbers=
command.
=downto=, =above=, =below=, =by=, =test=, and =index=. =index= names the
variable used to store the index being accessed. For the others, see the
=numbers= command.

#+ATTR_TEXINFO: :tag Warning
#+begin_quote
Array elements can be accessed in constant time, but not list elements. For
lists, the =sequence= command is fastest when moving forwards through the
list. In that case, the command does not have to search from the beginning of
the list each time to find the next element. The =sequence= command can be
noticeably slower for lists when working backwards or when the =test=
parameter (for which direction cannot be assumed) is provided.
#+end_quote

If multiple sequences are given, then these keyword arguments apply to the
resulting sequence of distributed elements.
Expand Down Expand Up @@ -1794,10 +1852,10 @@ iterate.
aliases =seqf=, =arrayf=, =listf=, and =stringf= of the =seq-ref= command.

=KEYS= is one or several of =from=, =upfrom=, =downfrom=, =to=, =upto=,
=downto=, =above=, =below=, and =by=. For their meaning, see the =numbers=
command. This command is very similar to =numbers=, except that it can
automatically end the loop when the final element is reached. With
=numbers=, one would first need to explicitly calculate the length of the
=downto=, =above=, =below=, =by=, and =test=. For their meaning, see the
=numbers= command. This command is very similar to =numbers=, except that it
can automatically end the loop when the index of the final element is reached.
With =numbers=, one would first need to explicitly calculate the length of the
sequence.

Similar to =numbers=, for efficiency, =VAR= is initialized to the starting
Expand Down Expand Up @@ -2028,9 +2086,9 @@ the accessed index during the loop.
~setf~-able place.

=KEYS= is one or several of =from=, =upfrom=, =downfrom=, =to=, =upto=,
=downto=, =above=, =below=, =by=, and =index=. =index= names the variable
used to store the index being accessed. For others, see the =numbers=
command.
=downto=, =above=, =below=, =by=, =test=, and =index=. =index= names the
variable used to store the index being accessed. For others, see the
=numbers= command.

#+BEGIN_SRC emacs-lisp
;; => (7 7 7 7)
Expand Down
Loading

0 comments on commit 3aebae9

Please sign in to comment.