Skip to content

Commit

Permalink
Improve keyword arguments test and key.
Browse files Browse the repository at this point in the history
- Document the argument order of `test` as the first argument being the value
  from the sequence and the second argument being the tested item.  This is the
  same order as used by `seq-contains-p` and the opposite of `cl-member`.

  - Add `loopy--member-p`, used for tests for `adjoin`, `union`, and `nunion`.
    This function can be optimized during byte compilation via
    `loopy--member-p-comp` to become `member`, `memq`, or `memql` when possible,
    as done with `cl-member`.

- Simplify `loopy--plist-bind` into a wrapper around `cl-destructuring-bind`.

- Add `loopy--instr-let2*`, with works like `macroexp-let2*` except that it will
  also append variable-instructions to the result of the body as needed.  This
  will allow us to stop manually checking in each command whether we need to
  create a variable to hold a value.  Now, we create the variable is
  `macroexp-const-p` believes that the value would not be constant.

- Do not remove `key`.  The use of a separate argument allows us to optimize the
  transform function by only calling it once on the tested item
  during the execution of each command.  It is decided that re-creating
  this behavior with `set` and an ignored argument in the test function
  is too awkward.

- Remove mention of `init` keyword argument, which should have been deleted
  already.

- Add examples for the common accumulation keyword arguments.

- Update the CHANGELOG and correct some links.

- Update the README.

See also issues #176, #170, and this PR.
  • Loading branch information
okamsn committed Oct 23, 2023
1 parent 0780e28 commit 7ea85f9
Show file tree
Hide file tree
Showing 7 changed files with 466 additions and 228 deletions.
19 changes: 15 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ This document describes the user-facing changes to Loopy.
(reduce i #'*))
```

- Fix `find` when `:on-failure` is nil ([#170]). Previously, `nil` was
- Fix `find` when `:on-failure` is nil ([#171]). Previously, `nil` was
interpreted as not passing `:on-failure`.

```emacs-lisp
Expand All @@ -34,7 +34,7 @@ This document describes the user-facing changes to Loopy.
(finally-return val))
```

- Fix `find` when `EXPR` is nil and `:on-failure` is given ([#170]).
- Fix `find` when `EXPR` is nil and `:on-failure` is given ([#171]).
Previously, after the test passed and `VAR` was set to `nil`, that `nil` was
interpreted as not passing the test, so that `VAR` then bound to the value
passed for `:on-failure`.
Expand Down Expand Up @@ -79,6 +79,15 @@ 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].

- In accumulation commands using the `test` keyword argument, the argument order
of the two-argument test function is now document as `(SEQUENCE-ITEM,
TESTED-ITEM)`, similar to `seq-contains-p`. The argument order was previously
undocumented and not guaranteed. See [#170] and [#177].

#### Removals

- The deprecated flag `split` was removed ([#165], [#131], [#124]). Instead,
Expand Down Expand Up @@ -200,9 +209,11 @@ This document describes the user-facing changes to Loopy.
[#163]: https://github.com/okamsn/loopy/pull/163
[#164]: https://github.com/okamsn/loopy/pull/164
[#165]: https://github.com/okamsn/loopy/pull/165
[#170]: https://github.com/okamsn/loopy/pull/170
[#171]: https://github.com/okamsn/loopy/pull/172
[#170]: https://github.com/okamsn/loopy/issues/170
[#171]: https://github.com/okamsn/loopy/pull/171
[#172]: https://github.com/okamsn/loopy/pull/172
[#173]: https://github.com/okamsn/loopy/pull/173
[#177]: https://github.com/okamsn/loopy/pull/177

## 0.11.2

Expand Down
5 changes: 5 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ please let me know.
flexible and explicit than the non-keyword arguments.
- Fixed a problem with macro expansion in some cases for sub-macros
that created a new macro environment (e.g., ~cl-flet~).
- In =adjoin=, =nunion=, and =union=, only evaluate =:test= and =:key=
arguments once.
- Change the argument order of =test= to be (1) the sequence element then (2)
the tested value, like in ~seq-contains-p~ and _unlike_ in ~cl-member~, and
explicitly state this order in the documentation.
- See the [[https://github.com/okamsn/loopy/blob/master/CHANGELOG.md][change log]] for less recent changes.

# This auto-generated by toc-org.
Expand Down
91 changes: 78 additions & 13 deletions doc/loopy-doc.org
Original file line number Diff line number Diff line change
Expand Up @@ -2334,8 +2334,8 @@ You will notice that each accumulation command has an alias of the command name
in the present participle form (the "-ing" form). For example, instead of
"minimize", you can use "minimizing". Instead of "sum" and "append", you can
use "summing" and "appending". This is similar to the behavior of ~cl-loop~,
and helps to avoid name collisions when using the ~loopy-iter~ macro ([[#loopy-iter][The
~loopy-iter~ Macro]]).
and helps to avoid name collisions when using the ~loopy-iter~ macro
([[#loopy-iter][The ~loopy-iter~ Macro]]).

#+cindex: accumulation keyword arguments
Some accumulation commands have optional keyword parameters, which are listed
Expand All @@ -2347,6 +2347,16 @@ all described below.
(equivalent to =start=). If ungiven, defaults to =end=. These positions
need not be quoted.

#+begin_src emacs-lisp
;; => (1 2 3)
(loopy (list i '(1 2 3))
(collect i :at end))

;; => (3 2 1)
(loopy (list i '(1 2 3))
(collect i :at start))
#+end_src

#+cindex: accumulation keyword into
- =into= :: An alternative way to specify the variable into which to
accumulate values. One would normally just give =VAR= as the first
Expand All @@ -2356,18 +2366,73 @@ all described below.
As all accumulation commands support this keyword, it is not listed in
any command definition.

#+begin_src emacs-lisp
;; => (1 2 3)
(loopy (list i '(1 2 3))
(collect my-collection i)
(finally-return my-collection))

;; => (1 2 3)
(loopy (list i '(1 2 3))
(collect i :into my-collection)
(finally-return my-collection))
#+end_src

#+cindex: accumulation keyword test
- =test= :: A function of two arguments, usually used to test for equality.
Most tests default to ~equal~, like in other Emacs Lisp libraries. This is
different from =cl-lib=, which mimics Common Lisp and prefers using ~eql~.
This function is normally used to test if a value is already present in the
accumulating sequence. If so, the function should return a non-nil value.

#+attr_texinfo: :tag Note
#+begin_quote
This argument is similar to the =:test= argument used by =cl-lib=, but is
closer to the optional =testfn= argument used by =seq= (for example, in
~seq-contains-p~). This are two important differences:
1. Tests default to ~equal~, like in other Emacs Lisp libraries, not ~eql~.
2. The first argument is the existing value or sequence and the second
argument is the tested value. This is the /opposite/ of the order used by
~cl-member~ and ~memq~.
#+end_quote

#+begin_src emacs-lisp
;; Only add items to the list whose `car's are not already present
;; or whose `cdr' is not 3:
;;
;; => ((a . 1) (c . 4))
(loopy (with (test-fn (lambda (seq-val new-val)
(or (equal (cdr new-val)
3)
(eq (car seq-val)
(car new-val))))))
(list i '((a . 1) (a . 2) (b . 3) (c . 4)))
(adjoin i :test test-fn))
#+end_src

#+cindex: accumulation keyword key
- =key= :: A function of one argument, used to transform the inputs of
=test=.
- =key= :: A one-argument function that transforms the _both_ tested value and
the value from sequence used by the =test= keyword.

The keyword =key= is useful to avoid applying a transforming function to the
tested value more than once when searching through a long sequence, as would
be done if it were called explicitly in =test=.

#+begin_src emacs-lisp
;; => ((a . 1) (b . 2) (c . 4))
(loopy (with (test #'car))
(list i '((a . 1) (b . 2) (a . 3) (c . 4)))
(adjoin i :at end :key #'car))

;; Similary to the above:
;;
;; => ((a . 1) (b . 2) (c . 4))
(loopy (with (test-val))
(list i '((a . 1) (b . 2) (a . 3) (c . 4)))
(set test-val (car i))
(adjoin i :test (lambda (seq-val _)
(equal (car seq-val)
test-val))))
#+end_src

#+cindex: accumulation keyword init
- =init= :: The initial value of =VAR=. For explicitly named variables, one
can use this argument or the =with= special macro argument.

The arguments to the =test= and =key= parameters can be quoted functions or
variables, just like when using ~cl-union~, ~cl-adjoin~, and so on. ~loopy~
Expand Down Expand Up @@ -2617,11 +2682,11 @@ multiplying values together.
:END:

Sequence accumulation commands are used to join lists (such as =union= and
=append=) and to collect items into lists (such as =collect=).
=append=) and to collect items into lists (such as =collect= and =adjoin=).

#+findex: adjoin
#+findex: adjoining
- =(adjoin VAR EXPR &key at test)= :: Repeatedly add =EXPR= to =VAR= if it
- =(adjoin VAR EXPR &key at test key)= :: Repeatedly add =EXPR= to =VAR= if it
is not already present in the list.

This command also has the alias =adjoining=.
Expand Down Expand Up @@ -2750,7 +2815,7 @@ Sequence accumulation commands are used to join lists (such as =union= and

#+findex: nunion
#+findex: nunioning
- =(nunion VAR EXPR &key test at)= :: Repeatedly and /destructively/ insert
- =(nunion VAR EXPR &key test at key)= :: Repeatedly and /destructively/ insert
into =VAR= the elements of =EXPR= which are not already present in =VAR=.

This command also has the alias =nunioning=.
Expand Down Expand Up @@ -2819,7 +2884,7 @@ Sequence accumulation commands are used to join lists (such as =union= and

#+findex: union
#+findex: unioning
- =(union VAR EXPR &key test at)= :: Repeatedly insert into =VAR= the
- =(union VAR EXPR &key test at key)= :: Repeatedly insert into =VAR= the
elements of the list =EXPR= that are not already present in =VAR=.

This command also has the alias =unioning=.
Expand Down
106 changes: 86 additions & 20 deletions doc/loopy.texi
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ You should keep in mind that commands are evaluated in order. This means that
attempting something like the below example might not do what you expect, as @samp{i}
is assigned a value from the list after collecting @samp{i} into @samp{coll}.

@float Listing,orgd11885b
@float Listing,org9e6a997
@lisp
;; => (nil 1 2)
(loopy (collect coll i)
Expand Down Expand Up @@ -828,7 +828,7 @@ the flag @samp{dash} provided by the package @samp{loopy-dash}.

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

@float Listing,org27388de
@float Listing,org866b582
@lisp
;; => (1 2 3 4)
(cl-loop for (i . j) in '((1 . 2) (3 . 4))
Expand All @@ -843,7 +843,7 @@ Below are two examples of destructuring in @code{cl-loop} and @code{loopy}.
@caption{Destructuring values in a list.}
@end float

@float Listing,org2966e15
@float Listing,orgedbe07f
@lisp
;; => (1 2 3 4)
(cl-loop for elem in '((1 . 2) (3 . 4))
Expand Down Expand Up @@ -2491,8 +2491,8 @@ You will notice that each accumulation command has an alias of the command name
in the present participle form (the ``-ing'' form). For example, instead of
``minimize'', you can use ``minimizing''. Instead of ``sum'' and ``append'', you can
use ``summing'' and ``appending''. This is similar to the behavior of @code{cl-loop},
and helps to avoid name collisions when using the @code{loopy-iter} macro (@ref{The @code{loopy-iter} Macro, , The
@code{loopy-iter} Macro}).
and helps to avoid name collisions when using the @code{loopy-iter} macro
(@ref{The @code{loopy-iter} Macro}).

@cindex accumulation keyword arguments
Some accumulation commands have optional keyword parameters, which are listed
Expand All @@ -2505,6 +2505,16 @@ all described below.
Where to place a value. One of @samp{end}, @samp{start}, or @samp{beginning}
(equivalent to @samp{start}). If ungiven, defaults to @samp{end}. These positions
need not be quoted.

@lisp
;; => (1 2 3)
(loopy (list i '(1 2 3))
(collect i :at end))
;; => (3 2 1)
(loopy (list i '(1 2 3))
(collect i :at start))
@end lisp
@end table

@cindex accumulation keyword into
Expand All @@ -2517,30 +2527,86 @@ argument for a more @code{cl-loop}-like syntax.

As all accumulation commands support this keyword, it is not listed in
any command definition.

@lisp
;; => (1 2 3)
(loopy (list i '(1 2 3))
(collect my-collection i)
(finally-return my-collection))
;; => (1 2 3)
(loopy (list i '(1 2 3))
(collect i :into my-collection)
(finally-return my-collection))
@end lisp
@end table

@cindex accumulation keyword test
@table @asis
@item @samp{test}
A function of two arguments, usually used to test for equality.
Most tests default to @code{equal}, like in other Emacs Lisp libraries. This is
different from @samp{cl-lib}, which mimics Common Lisp and prefers using @code{eql}.
This function is normally used to test if a value is already present in the
accumulating sequence. If so, the function should return a non-nil value.

@quotation Note
This argument is similar to the @samp{:test} argument used by @samp{cl-lib}, but is
closer to the optional @samp{testfn} argument used by @samp{seq} (for example, in
@code{seq-contains-p}). This are two important differences:
@enumerate
@item
Tests default to @code{equal}, like in other Emacs Lisp libraries, not @code{eql}.
@item
The first argument is the existing value or sequence and the second
argument is the tested value. This is the @emph{opposite} of the order used by
@code{cl-member} and @code{memq}.
@end enumerate
@end quotation

@lisp
;; Only add items to the list whose `car's are not already present
;; or whose `cdr' is not 3:
;;
;; => ((a . 1) (c . 4))
(loopy (with (test-fn (lambda (seq-val new-val)
(or (equal (cdr new-val)
3)
(eq (car seq-val)
(car new-val))))))
(list i '((a . 1) (a . 2) (b . 3) (c . 4)))
(adjoin i :test test-fn))
@end lisp
@end table

@cindex accumulation keyword key
@table @asis
@item @samp{key}
A function of one argument, used to transform the inputs of
@samp{test}.
@end table
A one-argument function that transforms the both tested value and
the value from sequence used by the @samp{test} keyword.

@cindex accumulation keyword init
@table @asis
@item @samp{init}
The initial value of @samp{VAR}. For explicitly named variables, one
can use this argument or the @samp{with} special macro argument.
The keyword @samp{key} is useful to avoid applying a transforming function to the
tested value more than once when searching through a long sequence, as would
be done if it were called explicitly in @samp{test}.

@lisp
;; => ((a . 1) (b . 2) (c . 4))
(loopy (with (test #'car))
(list i '((a . 1) (b . 2) (a . 3) (c . 4)))
(adjoin i :at end :key #'car))
;; Similary to the above:
;;
;; => ((a . 1) (b . 2) (c . 4))
(loopy (with (test-val))
(list i '((a . 1) (b . 2) (a . 3) (c . 4)))
(set test-val (car i))
(adjoin i :test (lambda (seq-val _)
(equal (car seq-val)
test-val))))
@end lisp
@end table


The arguments to the @samp{test} and @samp{key} parameters can be quoted functions or
variables, just like when using @code{cl-union}, @code{cl-adjoin}, and so on. @code{loopy}
knows how to expand efficiently for either case.
Expand Down Expand Up @@ -2809,12 +2875,12 @@ This command also has the alias @samp{summing}.
@subsection Sequence Accumulation

Sequence accumulation commands are used to join lists (such as @samp{union} and
@samp{append}) and to collect items into lists (such as @samp{collect}).
@samp{append}) and to collect items into lists (such as @samp{collect} and @samp{adjoin}).

@findex adjoin
@findex adjoining
@table @asis
@item @samp{(adjoin VAR EXPR &key at test)}
@item @samp{(adjoin VAR EXPR &key at test key)}
Repeatedly add @samp{EXPR} to @samp{VAR} if it
is not already present in the list.

Expand Down Expand Up @@ -2958,7 +3024,7 @@ apply wherever that value is used (@ref{Self-Evaluating Forms,,,elisp,}).
@findex nunion
@findex nunioning
@table @asis
@item @samp{(nunion VAR EXPR &key test at)}
@item @samp{(nunion VAR EXPR &key test at key)}
Repeatedly and @emph{destructively} insert
into @samp{VAR} the elements of @samp{EXPR} which are not already present in @samp{VAR}.

Expand Down Expand Up @@ -3036,7 +3102,7 @@ convenience.
@findex union
@findex unioning
@table @asis
@item @samp{(union VAR EXPR &key test at)}
@item @samp{(union VAR EXPR &key test at key)}
Repeatedly insert into @samp{VAR} the
elements of the list @samp{EXPR} that are not already present in @samp{VAR}.

Expand Down Expand Up @@ -4117,7 +4183,7 @@ using the @code{let*} special form.
This method recognizes all commands and their aliases in the user option
@code{loopy-aliases}.

@float Listing,org808789f
@float Listing,org0290e68
@lisp
;; => ((1 2 3) (-3 -2 -1) (0))
(loopy-iter (arg accum-opt positives negatives other)
Expand Down
Loading

0 comments on commit 7ea85f9

Please sign in to comment.