diff --git a/doc/loopy-doc.org b/doc/loopy-doc.org index 02ec699b..172905f1 100644 --- a/doc/loopy-doc.org +++ b/doc/loopy-doc.org @@ -924,15 +924,10 @@ An element in the sequence =VAR= can be one of the following: #+findex: setting #+findex: expr #+findex: exprs -- =(set|expr VAR [EXPRS] &key init)= :: Bind =VAR= to each =EXPR= in order. +- =(set|expr VAR [EXPRS])= :: Bind =VAR= to each =EXPR= in order. Once the last =EXPR= is reached, it is used repeatedly for the rest of the loop. With no =EXPR=, =VAR= is repeatedly bound to ~nil~. - If =INIT= is provided, use that as the initial value of =VAR=. This could - also be achieved by specifying a value using the =with= special macro - argument. When destructuring, each variable is initialized to =INIT=, not - a destructured part of =INIT=. - This command also has the aliases =setting= and =exprs=. #+ATTR_TEXINFO: :tag Note @@ -963,23 +958,6 @@ An element in the sequence =VAR= can be one of the following: (set i 0 (1+ i)) (collect coll i) (finally-return coll)) - - ;; Note that `i' is initialized to 0, and set to 1 in - ;; the middle of the first cycle of the loop. - ;; - ;; => ((0 1 2) (1 2 3)) - (loopy (cycle 3) - (collect befores i) - (set i 1 (1+ i) :init 0) - (collect afters i) - (finally-return befores afters)) - - ;; Note that using `with' has a similar effect. - ;; => (0 1 2) - (loopy (with (i 0)) - (cycle 3) - (collect i) - (set i 1 (1+ i))) #+END_SRC #+findex: group @@ -1017,13 +995,9 @@ An element in the sequence =VAR= can be one of the following: #+findex: prev-set #+findex: prev-expr #+findex: prev -- =(set-prev|prev-expr VAR VAL &key init back)= :: Bind - =VAR= to a value =VAL= from a previous cycle in the loop. =VAR= is - initialized to =INIT= or nil. With =BACK=, use the value from that many - cycles previous. This command /does not/ work like a queue. - - As in =set=, when using destructuring, each variable is initialized to - =INIT=, not a destructured part of =INIT=. +- =(set-prev|prev-expr VAR VAL &key back)= :: Bind =VAR= to a value =VAL= from a + previous cycle in the loop. With =BACK=, use the value from that many cycles + previous. This command /does not/ work like a queue. This command also has the aliases =setting-prev=, =prev-set=, and =prev=. @@ -1033,25 +1007,25 @@ An element in the sequence =VAR= can be one of the following: (set-prev j i) (collect j)) - ;; (nil nil nil 1 2) + ;; => (nil nil nil 1 2) (loopy (list i '(1 2 3 4 5)) (set-prev j i :back 3) (collect j)) - ;; => ((7 7 1 3) (7 7 2 4)) - (loopy (list i '((1 2) (3 4) (5 6) (7 8))) - (set-prev (a b) i :back 2 :init 7) - (collect c1 a) - (collect c2 b) - (finally-return c1 c2)) + ;; => ((first-val nil) (first-val nil) (1 2) (3 4)) + (loopy (with (j 'first-val)) + (list i '((1 . 2) (3 . 4) (5 . 6) (7 . 8))) + (set-prev (j . k) i :back 2) + (collect (list j k))) ;; NOTE: `prev-expr' keeps track of the previous value of `i', ;; even when `j' isn't updated. ;; ;; => (first-val first-val 2 2 4 4 6 6 8 8) - (loopy (numbers i :from 1 :to 10) + (loopy (with (j 'first-val)) + (numbers i :from 1 :to 10) (when (cl-oddp i) - (set-prev j i :init 'first-val)) + (set-prev j i)) (collect j)) #+end_src @@ -2296,9 +2270,8 @@ using the =set= command. #+findex: set-accum #+findex: setting-accum -- =(set-accum VAR EXPR &key init)= :: Set the accumulation variable =VAR= to the - value of =EXPR=. =init= sets the initial value of =VAR=, which defaults to - ~nil~. +- =(set-accum VAR EXPR)= :: Set the accumulation variable =VAR= to the + value of =EXPR=. This command also has the alias =setting-accum=. @@ -2311,36 +2284,36 @@ using the =set= command. #+begin_src emacs-lisp ;; => 16 (loopy (array i [1 2 3]) - (set-accum (+ loopy-result i) :init 10)) + (set-accum (+ loopy-result i))) - ;; These are equivalent: + ;; These are equivalent to the above example: ;; => 16 (loopy (array i [1 2 3]) - (set-accum my-sum (+ my-sum i) :init 10) - (finally-return my-sum)) + (set loopy-result (+ loopy-result i)) + (finally-return loopy-result)) ;; => 16 (loopy (array i [1 2 3]) - (set my-sum (+ my-sum i) :init 10) - (finally-return my-sum)) + (set-accum loopy-result (+ loopy-result i)) + (finally-return loopy-result)) #+end_src #+findex: accumulate #+findex: accumulating -- =(accumulate|accumulating VAR EXPR FUNC &key init)= :: Accumulate the result +- =(accumulate|accumulating VAR EXPR FUNC)= :: Accumulate the result of applying function =FUNC= to =EXPR= and =VAR=. =EXPR= and =VAR= are used as the first and second arguments to =FUNC=, respectively. This is a generic accumulation command in case the others don't meet your - needs. It is similar in effect to using the command =expr=. + needs. It is similar in effect to using the command =set=. #+begin_src emacs-lisp ;; Call `(cons i my-accum)' ;; ;; => (2 1) (loopy (list i '(1 2)) - (accumulate my-accum i #'cons :init nil) + (accumulate my-accum i #'cons) (finally-return my-accum)) ;; Works mostly the same as the above: @@ -2348,9 +2321,11 @@ using the =set= command. (set my-accum (cons i my-accum)) (finally-return my-accum)) - ;; => ((3 1) (4 2)) - (loopy (list i '((1 2) (3 4))) - (accumulate (accum1 accum2) i #'cons :init nil) + ;; => ((3 1) (4 2 8 9 10)) + (loopy (with (accum1 nil) + (accum2 (list 8 9 10))) + (list i '((1 2) (3 4))) + (accumulate (accum1 accum2) i #'cons) (finally-return accum1 accum2)) #+end_src @@ -2360,7 +2335,7 @@ using the =set= command. #+begin_src emacs-lisp (loopy (list i '(1 2)) - (callf2 my-accum i #'cons :init nil) + (callf2 my-accum i #'cons) (finally-return my-accum)) ;; Is the same as the above: @@ -2372,31 +2347,32 @@ using the =set= command. #+findex: reduce #+findex: reducing -- =(reduce VAR EXPR FUNC &key init)= :: Reduce =EXPR= into =VAR= by =FUNC=. - =FUNC= is called with =VAR= as the first argument and =EXPR= as the second - argument. This is unlike =accumulate=, which gives =VAR= and =EXPR= to =FUNC= - in the opposite order (that is, =EXPR= first, then =VAR=). +- =(reduce VAR EXPR FUNC)= :: Reduce =EXPR= into =VAR= by =FUNC=. =FUNC= is + called with =VAR= as the first argument and =EXPR= as the second argument. + This is unlike =accumulate=, which gives =VAR= and =EXPR= to =FUNC= in the + opposite order (that is, =EXPR= first, then =VAR=). This command also has the alias =reducing=. - =VAR= is initialized to =INIT=, if provided, or ~nil~. - This command is similar in effect to the =set= command. #+begin_src emacs-lisp ;; = > 6 - (loopy (list i '(1 2 3)) - (reduce my-reduction i #'+ :init 0) + (loopy (with (my-reduction 0)) + (list i '(1 2 3)) + (reduce my-reduction i #'+) (finally-return my-reduction)) ;; Works similarly to above: - (loopy (list i '(1 2 3)) - (set my-reduction (+ i my-reduction) :init 0) + (loopy (with (my-reduction 0)) + (list i '(1 2 3)) + (set my-reduction (+ i my-reduction)) (finally-return my-reduction)) ;; => 24 - (loopy (list i '(1 2 3 4)) - (reduce i #'* :init 1)) + (loopy (with (loopy-result 1)) + (list i '(1 2 3 4)) + (reduce i #'*)) #+end_src This command also has the alias =callf=. It is similar to using the @@ -2405,8 +2381,9 @@ using the =set= command. order. #+begin_src emacs-lisp - (loopy (list i '(1 2 3)) - (callf my-reduction i #'+ :init 0) + (loopy (with (my-reduction 0)) + (list i '(1 2 3)) + (callf my-reduction i #'+) (finally-return my-reduction)) ;; Is similar to the above: @@ -2512,8 +2489,8 @@ Sequence accumulation commands are used to join lists (such as =union= and #+findex: adjoin #+findex: adjoining -- =(adjoin VAR EXPR &key at test key init)= :: Repeatedly add =EXPR= - to =VAR= if it is not already present in the list. +- =(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=. diff --git a/loopy-commands.el b/loopy-commands.el index 8582db6f..6dc65034 100644 --- a/loopy-commands.el +++ b/loopy-commands.el @@ -219,6 +219,11 @@ handled by `loopy-iter'." ':init)) (init-arg (when using-init-arg (nth (1- length-vals) vals)))) + + (when using-init-arg + (warn "Loopy: `set': The `:init' argument is deprecated. +Instead, use the special macro argument `with'.")) + (let ((arg-length (if using-init-arg (- length-vals 2) length-vals)) @@ -272,7 +277,8 @@ handled by `loopy-iter'." needed-instructions))))) ;;;;;; Prev Expr -(cl-defun loopy--parse-set-prev-command ((_ var val &key init back)) +(cl-defun loopy--parse-set-prev-command + ((&whole cmd _ var val &key (init nil init-provided) back)) "Parse the `set-prev' command as (set-prev VAR VAL &key init back). VAR is set to a version of VAL in a past loop cycle. With INIT, @@ -280,49 +286,94 @@ initialize VAR to INIT. With BACK, wait that many cycle before beginning to update VAR. This command does not wait for VAL to change before updating VAR." - (let ((holding-vars (cl-loop for i from 1 to (or back 1) - collect (gensym "set-prev-hold"))) - (init-value-holder (gensym "set-prev-init")) - (init-destr-value-holder (gensym "set-prev-destr-init")) - (using-destructuring (sequencep var))) - ;; TODO: This feels more complicated than it needs to be, but the resulting - ;; code is pretty simple. - `(,@(if init - `((loopy--other-vars (,init-value-holder ,init)) - ;; When using destructuring, each variable in `holding-vars' - ;; needs to be initialized to a value that can be destructured - ;; according to VAR. - ;; - ;; We only want to calculate the initial value once, even for the - ;; destructuring, so we require two holding variables. - ,@(if using-destructuring - `((loopy--other-vars - (,init-destr-value-holder - (loopy--mimic-init-structure - (quote ,var) ,init-value-holder))) - ,@(mapcar (lambda (x) - `(loopy--other-vars - (,x ,init-destr-value-holder))) - holding-vars)) - (mapcar (lambda (x) - `(loopy--other-vars (,x ,init-value-holder))) - holding-vars))) - (mapcar (lambda (x) - `(loopy--other-vars (,x nil))) - holding-vars)) - ,@(loopy--substitute-using - (pcase-lambda ((and instr `(,place (,var ,_)))) - (if (eq place 'loopy--other-vars) - `(,place (,var ,(when init - init-value-holder))) - instr)) - (loopy--destructure-for-other-command - var (car (last holding-vars)))) - (loopy--latter-body - (setq ,@(apply #'append - (nreverse (cl-loop for pvar = val then hv - for hv in holding-vars - collect (list hv pvar))))))))) + (let* ((holding-vars (cl-loop for i from 1 to (or back 1) + collect (gensym "set-prev-hold"))) + (init-value-holder (gensym "set-prev-init")) + (init-destr-value-holder (gensym "set-prev-destr-init")) + (using-destructuring (sequencep var)) + (with-bound (if using-destructuring + (cl-loop for instr + in (loopy--destructure-for-other-command var 'blah) + when (eq (car instr) 'loopy--other-vars) + for (_ (sym _)) = instr + thereis (loopy--with-bound-p sym)) + (loopy--with-bound-p var)))) + + (when init-provided + (warn "Loopy: `set-prev': The `:init' argument is deprecated. +Instead, use the special macro argument `with'.")) + + (when (and init-provided with-bound) + (error "Can't use `with' and `:init': %s" cmd)) + + (let ((holding-vars-setq + `(loopy--latter-body + (setq ,@(apply #'append + (nreverse (cl-loop for pvar = val then hv + for hv in holding-vars + collect (list hv pvar)))))))) + (cond + ((and with-bound (not using-destructuring)) + `(,@(mapcar (lambda (x) `(loopy--other-vars (,x ,var))) + holding-vars) + (loopy--main-body (setq ,var ,(car (last holding-vars)))) + ,holding-vars-setq)) + ((and with-bound using-destructuring) + (let ((cnt-holder (gensym "count")) + (back-holder (gensym "back"))) + `((loopy--other-vars (,cnt-holder 0)) + (loopy--latter-body (setq ,cnt-holder (1+ ,cnt-holder))) + (loopy--other-vars (,back-holder ,back)) + ,@(mapcar (lambda (x) `(loopy--other-vars (,x nil))) + holding-vars) + ,@(seq-let (main-exprs rest-instr) + (loopy--extract-main-body + (loopy--destructure-for-other-command + var (car (last holding-vars)))) + `((loopy--main-body (when (>= ,cnt-holder ,back-holder) + ,@main-exprs)) + ,@rest-instr)) + ,holding-vars-setq))) + ;; TODO: Old code. Update when `:init' removed. + (t + ;; TODO: This feels more complicated than it needs to be, but the resulting + ;; code is pretty simple. + `(,@(if init + `((loopy--other-vars (,init-value-holder ,init)) + ;; When using destructuring, each variable in `holding-vars' + ;; needs to be initialized to a value that can be destructured + ;; according to VAR. + ;; + ;; We only want to calculate the initial value once, even for the + ;; destructuring, so we require two holding variables. + ,@(if using-destructuring + `((loopy--other-vars + (,init-destr-value-holder + (loopy--mimic-init-structure + (quote ,var) ,init-value-holder))) + ,@(mapcar (lambda (x) + `(loopy--other-vars + (,x ,init-destr-value-holder))) + holding-vars)) + (mapcar (lambda (x) + `(loopy--other-vars (,x ,init-value-holder))) + holding-vars))) + (mapcar (lambda (x) + `(loopy--other-vars (,x nil))) + holding-vars)) + ,@(loopy--substitute-using + (pcase-lambda ((and instr `(,place (,var ,_)))) + (if (eq place 'loopy--other-vars) + `(,place (,var ,(when init + init-value-holder))) + instr)) + (loopy--destructure-for-other-command + var (car (last holding-vars)))) + (loopy--latter-body + (setq ,@(apply #'append + (nreverse (cl-loop for pvar = val then hv + for hv in holding-vars + collect (list hv pvar)))))))))))) ;;;;;; Group (cl-defun loopy--parse-group-command ((_ &rest body)) @@ -1701,6 +1752,13 @@ macro argument, such as `finally-return'. See also `accum-opt' at the Info node `(loopy)Optimizing Accumulations'." name)) + (when (plist-member opts :init) + (warn "Loopy: `%s': The `:init' argument is deprecated. +Instead, use the special macro argument `with' for the +accumulation variable. The default accumulation variable is +`loopy-result'." + name)) + (let ((arg-length (length args))) (cond ((= arg-length ,implicit-num-args) diff --git a/tests/tests.el b/tests/tests.el index 98d38819..75497efd 100644 --- a/tests/tests.el +++ b/tests/tests.el @@ -802,8 +802,8 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." :loopy t :iter-keyword (do return)) -;;;;; Expr -(loopy-deftest expr-init +;;;;; Set +(loopy-deftest set-init :result 3 :body ((cycle 3) (set var (1+ var) :init 0) @@ -813,7 +813,19 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." :iter-bare ((cycle . cycling) (set . setting))) -(loopy-deftest expr-init-destr +(loopy-deftest set-with + :doc "Test to make sure that we can replace `:init' with `with'." + :result 3 + :body ((with (var 0)) + (cycle 3) + (set var (1+ var)) + (finally-return var)) + :loopy t + :iter-keyword (cycle set) + :iter-bare ((cycle . cycling) + (set . setting))) + +(loopy-deftest set-init-destr :doc "Each variable is initialized to `:init', not a destructured part of `:init'." :result '((0 0 0) (1 2 3)) :body ((collect (list i j k)) @@ -826,7 +838,23 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." (leave . leaving) (set . setting))) -(loopy-deftest expr-when +(loopy-deftest set-with-destr + :doc "Test to make sure that we can replace `:init' with `with'." + :result '((0 0 0) (1 2 3)) + :body ((with (i 0) + (j 0) + (k 0)) + (collect (list i j k)) + (set (i j k) '(1 2 3)) + (collect (list i j k)) + (leave)) + :loopy t + :iter-keyword (leave set collect) + :iter-bare ((collect . collecting) + (leave . leaving) + (set . setting))) + +(loopy-deftest set-when :result '(nil 0 0 1 1 2 2 3) :body ((list i '(1 2 3 4 5 6 7 8)) (when (cl-evenp i) @@ -887,7 +915,7 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." (loopy--set-value-tests) -(loopy-deftest expr-dont-repeat +(loopy-deftest set-dont-repeat :doc "Make sure commands don't repeatedly create/declare the same variable." :result 1 :wrap ((x . `(with-temp-buffer @@ -918,8 +946,8 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." :iter-keyword ((list . list) (_group . (group command-do)))) -;;;;; Prev-Expr -(loopy-deftest prev-expr +;;;;; Set-Prev +(loopy-deftest set-prev :result '(nil 1 2 3 4) :body ((list i '(1 2 3 4 5)) (_set-prev j i) @@ -933,7 +961,7 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." (collect . collect) (_set-prev . (set-prev prev-set prev-expr)))) -(loopy-deftest prev-expr-keyword-back +(loopy-deftest set-prev-keyword-back :result '(nil nil nil 1 2) :body ((list i '(1 2 3 4 5)) (set-prev j i :back 3) @@ -944,7 +972,7 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." (set-prev . setting-prev)) :iter-keyword (list set-prev collect)) -(loopy-deftest prev-expr-keyword-init +(loopy-deftest set-prev-keyword-init :result '(first-val first-val 2 2 4 4 6 6 8 8) :body ((numbers i 1 10) (when (cl-oddp i) @@ -956,7 +984,21 @@ SYMS-STR are the string names of symbols from `loopy-iter-bare-commands'." (set-prev . setting-prev)) :iter-keyword (numbers set-prev collect)) -(loopy-deftest prev-expr-destructuring +(loopy-deftest set-prev-:back-with + :doc "Special behavior in command for with-bound variables." + :result '((first-val 22) (first-val 22) (1 2) (3 4)) + :body ((with (j 'first-val) + (k 22)) + (list i '((1 . 2) (3 . 4) (5 . 6) (7 . 8))) + (set-prev (j . k) i :back 2) + (collect (loopy-test-escape (list j k)))) + :loopy t + :iter-bare ((list . listing) + (collect . collecting) + (set-prev . setting-prev)) + :iter-keyword (list set-prev collect)) + +(loopy-deftest set-prev-destructuring :result '((7 7 1 3) (7 7 2 4)) :body ((list i '((1 2) (3 4) (5 6) (7 8))) (set-prev (a b) i :back 2 :init 7) @@ -2639,6 +2681,18 @@ Othe cases use `elt'." :iter-bare ((list . listing) (_cmd . (accumulating)))) +(loopy-deftest accumulate-with + :doc "Test to make sure that we can replace `:inti' with `with'." + :result 10 + :body ((with (my-accum 1)) + (list i '(2 3 4)) + (accumulate my-accum i #'+) + (finally-return my-accum)) + :loopy t + :iter-keyword (accumulate list) + :iter-bare ((list . listing) + (accumulate . accumulating))) + (loopy-deftest accumulate-:init :result 10 :body ((list i '(2 3 4)) @@ -4106,6 +4160,26 @@ Using `start' and `end' in either order should give the same result." :iter-bare ((list . listing) (_cmd . (reducing)))) +(loopy-deftest reduce-with + :doc "Test that we can replace `:init' with `with'." + :result 6 + :multi-body t + :body [((with (r 0)) + (list i '(1 2 3)) + (_cmd r i #'+) + (finally-return r)) + + ((with (r 0)) + (list i '(1 2 3)) + (_cmd r i #'+) + (finally-return r))] + :repeat _cmd + :loopy ((_cmd . (reduce reducing callf))) + :iter-keyword ((list . list) + (_cmd . (reduce reducing callf))) + :iter-bare ((list . listing) + (_cmd . (reducing)))) + (loopy-deftest reduce-append :result '(1 2 3) :multi-body t @@ -4160,6 +4234,23 @@ Using `start' and `end' in either order should give the same result." :iter-bare ((list . listing) (_cmd . (setting-accum)))) +(loopy-deftest set-accum-+-2 + :doc "Test to make sure that we can replace `:init' with `with'." + :result 16 + :multi-body t + :body [((with (my-sum 10)) + (list i '(1 2 3)) + (set-accum my-sum (+ my-sum i)) + (finally-return my-sum)) + + ((with (loopy-result 10)) + (list i '(1 2 3)) + (set-accum (+ loopy-result i)))] + :loopy t + :iter-keyword (list set-accum) + :iter-bare ((list . listing) + (set-accum . setting-accum))) + (loopy-deftest set-accum-cons :result '(3 2 1) :multi-body t