dash.el icon indicating copy to clipboard operation
dash.el copied to clipboard

--case-> macro?

Open alphapapa opened this issue 9 years ago • 4 comments

I'm still an elisp noob, so this is probably a bad idea and I just don't realize it yet. :) (I did search the issue tracker but I didn't find anything quite like this.)

What do you think about this macro?

(defmacro --case-> (value &rest cond-forms)
  `(let ((it ,value))
     (dolist (form ',cond-forms it)
       (when (eval (car form))
         (setq it (eval (cadr form)))))))

Used like this:

(--case-> (list 1 2 3)
          (t (nreverse it))
          (t (--map (* 2 it) it))) ; => (6 4 2)

I think it could be useful when handling option arguments to a function, where each option has the potential to mutate the result.

I thought that --> with when would work, like this:

(--> (list 1 2 3)
     (if t (nreverse it) it)
     (it t (--map (* 2 it) it) it))

But instead I get an error:

Debugger entered--Lisp error: (void-variable it)
  (mapcar (function (lambda (it) (* 2 it))) it)
  (progn (mapcar (function (lambda (it) (* 2 it))) it))
  (if t (progn (mapcar (function (lambda (it) (* 2 it))) it)))
  eval((if t (progn (mapcar (function (lambda (it) (* 2 it))) it))) nil)

And I don't understand the macro expansion, which leaves out everything except the last form:

(if t
    (progn
      (--map
       (* 2 it)
       it)))

But even if that did work, having to write (if COND FORM IT) instead of (COND FORM) is much more verbose, so it seems like (--case-> ... could still be useful.

There's also this one, which handles multiple forms:

(defmacro --case-multi-> (value &rest cond-forms)
  `(let ((it ,value))
     (dolist (form ',cond-forms it)
       (when (eval (car form))
         (dolist (f (cdr form))
           (setq it (eval f)))))))

Used like:

(--case-multi-> (list 1 2 3)
                (t (nreverse it))
                (t (--map (* 2 it) it)
                   (--map (+ 1 it) it))) ; => (7 5 3)

Thanks for your work on Dash.

alphapapa avatar Oct 15 '16 12:10 alphapapa

So if I understand it correctly, a more "proper" example could be

(let ((options (list :reverse t :pop nil :double t)))
  (--case-> (list 1 2 3)
            ((plist-get options :reverse) (nreverse it))
            ((plist-get options :pop) (pop it))
            ((plist-get options :double) (--map (* 2 it) it)))) ; => (6 4 2)

Is this what you mean? I can see the usefulness but I'm thinking if it couldn't be solved in some other way already available.

Fuco1 avatar Oct 15 '16 14:10 Fuco1

As for the implementation, you absolutely shouldn't need to use eval. In general, using eval means you are doing something evil. Try to rewrite it in a way you could avoid that (consider it an exercise in elisp :D).

Fuco1 avatar Oct 15 '16 14:10 Fuco1

Haha, thanks. My experience writing macros is very limited.

By the way, what made me think of it is this: https://github.com/alphapapa/mosey.el/blob/master/mosey.el#L150

Here I need to reverse the result if backward is set, and it seems ugly to do it the way I have there, by setting the variable again with an if. I wanted a way to simply wrap the original value that would reverse it if necessary. Of course, with a case like this, it would hardly justify writing a macro, but it made me wonder if a macro that could do this would be generally useful in more complicated cases.

alphapapa avatar Oct 16 '16 06:10 alphapapa

Does #349 satisfy this feature request?

basil-conto avatar Dec 18 '20 11:12 basil-conto