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.

- 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 aa6d3ac
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 218 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
79 changes: 68 additions & 11 deletions doc/loopy-doc.org
Original file line number Diff line number Diff line change
Expand Up @@ -2358,16 +2358,73 @@ all described below.

#+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~.

#+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
;; Don't adjoin items whose `car' is already present or whose `cdr' is 3.
;;
;; => ((a . 1) (b . 2) (e . 5))
(seq-union '((a . 1) (b . 2)) '((c . a) (d . b) (e . 5))
(lambda (v1 v2)
(equal (car v1)
(cdr v2))))

;; TODO: MAke subst version of `cl-member` so that we don't have to flip
;; flip the order?
;;
;; (loopy--member a b) => (cl-member b a)?
(loopy (list i '((a . 1) (b . 2) (c . a) (d . b) (e . 5)))
(union loopy-result i :test (lambda (var-val new-val)
(equal (car var-val)
(cdr new-val))))
(finally-return loopy-result))

;; => ((a . 1) (c . 4))
(loopy (with (test-fn (lambda (var-val new-val)
(or (eq (car var-val)
(car new-val))
(equal (cdr new-val)
3)))))
(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 the function to the tested value
more than once when searching through a long sequence, as would be done if it
had been called explicitly in test.

#+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.
#+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 myvar i :at end)
(finally-return myvar))

;; 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

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 +2674,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 +2807,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 +2876,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
89 changes: 77 additions & 12 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,org76167ca
@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,org1254d12
@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,org3460bbd
@lisp
;; => (1 2 3 4)
(cl-loop for elem in '((1 . 2) (3 . 4))
Expand Down Expand Up @@ -2523,15 +2523,80 @@ any command definition.
@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}.

@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
@end table


@lisp
;; Don't adjoin items whose `car' is already present or whose `cdr' is 3.
;;
;; => ((a . 1) (b . 2) (e . 5))
(seq-union '((a . 1) (b . 2)) '((c . a) (d . b) (e . 5))
(lambda (v1 v2)
(equal (car v1)
(cdr v2))))
;; TODO: MAke subst version of `cl-member` so that we don't have to flip
;; flip the order?
;;
;; (loopy--member a b) => (cl-member b a)?
(loopy (list i '((a . 1) (b . 2) (c . a) (d . b) (e . 5)))
(union loopy-result i :test (lambda (var-val new-val)
(equal (car var-val)
(cdr new-val))))
(finally-return loopy-result))
;; => ((a . 1) (c . 4))
(loopy (with (test-fn (lambda (var-val new-val)
(or (eq (car var-val)
(car new-val))
(equal (cdr new-val)
3)))))
(list i '((a . 1) (a . 2) (b . 3) (c . 4)))
(adjoin i :test test-fn))
@end lisp

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

The keyword @samp{key} is useful to avoid applying the function to the tested value
more than once when searching through a long sequence, as would be done if it
had been called explicitly in test.

@lisp
;; => ((a . 1) (b . 2) (c . 4))
(loopy (with (test #'car))
(list i '((a . 1) (b . 2) (a . 3) (c . 4)))
(adjoin myvar i :at end)
(finally-return myvar))
;; 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

@cindex accumulation keyword init
Expand Down Expand Up @@ -2809,12 +2874,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 +3023,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 +3101,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 +4182,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,org9d52037
@lisp
;; => ((1 2 3) (-3 -2 -1) (0))
(loopy-iter (arg accum-opt positives negatives other)
Expand Down
Loading

0 comments on commit aa6d3ac

Please sign in to comment.