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

idea: recur clause #34

Open
Luis-Henriquez-Perez opened this issue Mar 5, 2021 · 2 comments
Open

idea: recur clause #34

Luis-Henriquez-Perez opened this issue Mar 5, 2021 · 2 comments
Labels
enhancement New feature or request unlikely Unlikely to be changed/added

Comments

@Luis-Henriquez-Perez
Copy link
Contributor

This is not a concrete idea. But it has been on my mind and I think would be useful so I wanted to explain it as best as I can.

The idea is to have a recur clause which can trigger a the current loopy loop, overwriting any variables declared.

A recur clause would take in a set of bindings. Basically recur would execute the same loopy loop, but with those bindings instead.

(recur &rest bindings)

;; In practice it would look like this:

;; => 120
(loopy (with (n 5))
       (loop (return (if (zerop n) 1 (* n (recur (n (1- n))))))))

I think that first of all this looks really cool. But in all seriousness, this could addresses the problem that sometimes you want to use recursion, but you want to do something to the result of the recursion. Normally, you'd have to wrap the recursive function in another function in that case because otherwise any post manipulation would recurse as well. This is why I think cl-labels partly was created. Also, perhaps it is possible for loopy to recure more efficiently than with function calls. I got some inspiration for this by looking at the package recur. Which, now that I mention it, could be a useful for #16 because it showcases "code-walking" which is what would be needed for arbitrarily nested clauses.

@okamsn
Copy link
Owner

okamsn commented Mar 6, 2021

As an aside, you might be interested in the recent tail-call optimization that was made on master: https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=29c7f8c915c3889dfd5b25878aa0692f826cd38f

It's used by the recently added named-let: https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=0d3635536d4ed8ada6946e98e7d9f03fa443bc36

Mail discussion on named-let: https://lists.gnu.org/archive/html/emacs-devel/2021-01/msg00566.html

@okamsn okamsn added the enhancement New feature or request label May 8, 2021
@okamsn okamsn added the unlikely Unlikely to be changed/added label Aug 21, 2021
@okamsn
Copy link
Owner

okamsn commented Dec 29, 2022

I'm still not sure how to make something that uses the return value without adding another
macro, but it occurred to me that the "less recursive" examples I saw online
for similar features could be built by just wrapping setq and the skip command.

I'm just collecting the thought here.

  • From Clojure:

    ;; => 120
    (loop [n (bigint 5), accumulator 1]
      (if (zero? n)
        accumulator  ; we're done
        (recur (dec n) (* accumulator n))))
  • Emacs's named-let, which seems similar:

    ;;; Using `named-let'
    ;; => 120
    (named-let loop ((n 5)
                     (accum 1))
      (if (zerop n)
          accum
        (loop (1- n) (* accum n))))
  • What these things are doing in basic loopy commands:

    ;; => 120
    (loopy (with (n 5)
                 (accum 1))
           (if (zerop n)
               (return accum)
             (set accum (* n accum))
             (set n (1- n))))
  • Writing a command that sets then skips:

    ;;; Defining a `recur'-ish command
    (cl-defun loopy--parse-recur-command ((_ &rest bindings))
      `((loopy--main-body (cl-psetq ,@(apply #'append bindings)))
        ;; This not really needed if in tail position.
        ,@(loopy--parse-skip-command 'ignored)))
    
    (add-to-list 'loopy-command-parsers (cons 'recur #'loopy--parse-recur-command))
    
    ;; => 120
    (loopy (with (n 5)
                 (accum 1))
           (if (zerop n)
               (return accum)
             (recur (n (1- n))
                    (accum (* n accum)))))
    
    ;; => 120
    (loopy outer
           (with (n 5)
                 (accum 1))
           (if (zerop n)
               (return accum)
             ;; Hard to think of a good example, but by using `skip' we can
             ;; "recur" in named loops.
             (loopy some-sort-of-loop
                    (cycle 25)
                    (at outer (recur (n (1- n))
                                     (accum (* n accum))))
                    (return-from outer 'failed))))
  • The built-in accumulation command:

    ;; => 120
    (loopy (numbers n 2 5)
           (multiply n))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request unlikely Unlikely to be changed/added
Projects
None yet
Development

No branches or pull requests

2 participants