Open alphapapa opened 7 years ago
I think we had this proposed before and for some reason it didn't move forward or was rejected (or maybe the issue is still open?)
I think there is now a discussion about adding this to Emacs Lisp core language, something like those clojure forms %()
or so (I don't use clojure).
I can't contribute to the discussion of such a macro, but I'd like to note a couple of things:
#'car
as the :key
.dash-functional
provides the partially-anaphoric (I'm not really familiar with this terminology) lambda
macro -cut
, though it is less general than the one being proposed here.I think there is now a discussion about adding this to Emacs Lisp core language, something like those clojure forms %() or so (I don't use clojure).
Oh, cool, could you link me to that please? :)
Technically the anonymous functions in the example are redundant, as they are equivalent to using #'car as the :key.
Yes, I chose a simple example, but I was inspired by a more complex example that required a lambda:
(cl-sort org-recent-headings-list #'>
:key (lambda (it)
(frecency-score (cdr it) :get-fn #'plist-get)))
And that doesn't work with -cut
:
(cl-sort list #'>
:key (-cut frecency-score (cdr <>) :get-fn #'plist-get))
Because -cut
returns a lambda that takes no arguments:
(lambda nil (frecency-score (cdr <>) :get-fn (function plist-get)))
But thanks for reminding me about -cut
. :)
I guess maybe @Fuco1 was referring to this:
https://www.reddit.com/r/emacs/comments/6j3fi0/anonymous_function_macro_in_elisp/ https://www.reddit.com/r/emacs/comments/6yhg4m/anonymous_function_macro_in_elisp/
But if there's a more official discussion... :)
Having read through those, here's another function that might be useful. It recursively finds symbols starting with $
, sorts them, and makes them the lambda's arg list:
(defmacro -$ (&rest body)
(cl-labels ((collect-vars
(&rest forms)
(cl-loop for form in forms
append (cl-loop for atom in form
if (and (symbolp atom)
(string-match (rx bos "$")
(symbol-name atom)))
collect atom
else if (consp form)
append (collect-vars atom)))))
`(lambda ,(cl-sort (collect-vars body)
#'string<
:key #'symbol-name)
,@body)))
Used like:
(let ((l '((1 "one" :one) (2 "two" :two))))
(cl-loop for triple in l
for (digit word symbol) = triple
collect (funcall (-$ (list (list $3 $2) $1))
digit word symbol)))
;; => (((:one "one") 1) ((:two "two") 2))
I wrote a long comment looking for this, then discovered https://github.com/troyp/fn.el exists, and implements this.
Well, that's interesting. I also had no idea that fn.el exists and is in MELPA. However, I think the <>
/ <1> <2>
syntax is awkward, and especially doesn't look good when an argument is next to a comparator like (fn: < <1> <2>)
or (fn: > <> 0)
. I much prefer e.g. (-$ < $1 $2)
or (-$ > $ 0)
.
And I think I prefer the explicit parens version as well, like (-$ (< $1 $2))
or (-$ (> $ 0))
, because it preserves the form of the actual function body.
@alphapapa, I just wanted to point out that instead of using cl-labels
and recursively sorting the body of -$
, you can just use -flatten
and -filter
to get the symbols:
(defun --dollar-symbol? (sym)
(string-prefix-p "$" (symbol-name sym)))
(defmacro -$ (&rest body)
(let ((args (->> body
(-flatten)
(-filter #'--dollar-symbol?)
(-uniq)
(--sort (string< (symbol-name it) (symbol-name other))))))
`(lambda ,args ,@body)))
An example:
(-$ (+ $one $two $two $fourth))
;; => expands to
(lambda
($fourth $one $two)
(+ $one $two $two $fourth))
Question: what's the reason for sorting the arguments?
I think this can be a pretty convenient macro. The only reservation I have about it is that it means that you need to be careful about using it when you have symbols prefixed by $
. For someone like xah lee who always prefixes his symbols with a $
, for example, this macro may not be so useful.
@Luis-Henriquez-Perez Yes, and you could also use --filter
to avoid having a defined --dollar-symbol?
function.
Question: what's the reason for sorting the arguments?
The intention was to use digit arguments, not ordinal numbers in English. The idea is that the order in which arguments are used in the lambda doesn't matter, but the order in which arguments are passed to it does matter. And we don't want to have a predefined argument list, which would defeat the purpose of the macro.
I think this can be a pretty convenient macro. The only reservation I have about it is that it means that you need to be careful about using it when you have symbols prefixed by $.
A symbol has to be chosen, because bare digits cannot be used as variable names. You could easily make the symbol prefix an argument to the macro.
Yes, and you could also use --filter to avoid having a defined --dollar-symbol? function.
True. I did it this way because I think --dollar-symbol?
is slightly more readable. However, since dash is not likely to use --dollar-symbol?
often, you're right that it's better just to avoid defining it.
The intention was to use digit arguments, not ordinal numbers in English. The idea is that the order in which arguments are used in the lambda doesn't matter, but the order in which arguments are passed to it does matter
Ok, that makes sense. Darn, I was hoping we could use any $name
so we could make the the bodies more readable, but then we can't determine the order of arguments.
A symbol has to be chosen, because bare digits cannot be used as variable names.
Definitely. My reservation was towards using a particular symbol.
You could easily make the symbol prefix an argument to the macro.
That is possible, although I would not be fond of entering that every time I use the macro. Maybe the user should just set the symbol with a variable. However, this would have to be customized before the macro is defined.
Ok, that makes sense. Darn, I was hoping we could use any $name so we could make the the bodies more readable, but then we can't determine the order of arguments.
And if you're going to specify the argument list, you're only saving the 4 characters between -$
and lambda
. Maybe you would like prettify-symbols
to replace lambda
with the Greek letter. :)
That is possible, although I would not be fond of entering that every time I use the macro. Maybe the user should just set the symbol with a variable. However, this would have to be customized before the macro is defined.
The typical (or Common Lisp-like) thing to do would be to specify $
(or whatever) as the default symbol prefix so it wouldn't need to be specified, but the user could override it with e.g. a keyword argument. However, I don't think this is a significant concern. We're talking about small, anonymous functions using a handful of numbered arguments. If an author really needs a variable named $1
(or should we number from 0? Dijkstra would not approve...), he can probably figure out a solution, or just use lambda
.
Hey, it's me again with another crazy macro idea. :)
Used like:
The idea is that it's nicer than writing:
Not a big deal necessarily, but single-argument lambdas are so common that it's nice to have a macro for it. I've used an
it
macro that does the same thing, but--
seems to be consistent with dash's scheme.If
--
is too short of a name,--it
might also work, or--lambda
could be the anaphoric, single-arg version of-lambda
.Thanks.