Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve keyword arguments test and key. #177

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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