reddit / emacs

Lambdas with limited closures

Tohiko · 2022-05-23 08:01
In my functions, I often do the following: ``` (let ((var value) (many-other-vars other-long-value)) (add-hook 'mode-hook (lambda nil (do-something var)))) ``` Utilizing lexical binding. This works well, but then I have `many-other-vars` in the closure resulting in unnecessary saving of values and the previously small lambda function becomes quite cumbersome to view in subsequent inspections. I then resort to the following solution: ``` (defun make-hook (var) (lambda nil (do-something var))) (let ((var value) (many-other-vars other-long-value)) (add-hook 'mode-hook (make-hook var))) ``` which then has the desired effect of having only the variable `var` in the closure. But this is counterproductive as now I have to define a function for each lambda I need (defeating the goal of creating anonymous functions on the fly). Is there a way to limit the closure to specific variables? Other languages have such features (like C++'s lambda functions) but I can't seem to replicate it in emacs-lisp. I tried creating a macro to replicate the solution above but this seems impossible.
deaddyfreddy · 2022-05-23 13:47
> But this is counterproductive as now I have to define a function for each lambda I need actually no, just put it in an inline lambda
ErnestKz · 2022-05-23 14:35
Hmm yeah interesting, I've also made an attempt with macros but couldn't do it. I tried generating a defun inline, but when the defun is executed it itself captures all the variables in the scope, and passes it onto the lambda it seems.
Tohiko · 2022-05-23 14:51
Yes exactly. Same with writing an inline lambda than returns a lambda. I tried disabling lexical binding for the outer lambda (or defun) and enabling it for the inner one, but that didn't work either.
Tohiko · 2022-05-23 14:52
An inline lambda would capture `many-other-vars` and then pass that to the lambda it creates.
ErnestKz · 2022-05-23 16:24
``` (defmacro mk-lambda (as &rest bs) `(quote ,`(lambda ,as ,@bs))) (defun mk-hook (vars lmbda) `(lambda nil (funcall ,lmbda ,@vars))) (defmacro mk-ilambda (as &rest bs) `(mk-hook (list ,@as) (mk-lambda ,as ,@bs))) (let ((a1 "a") (a2 "b") (a3 "c") (a4 'something-big)) (mk-ilambda (a1 a2 a3) (print a1) (print a2) (print a3))) ``` Something along the lines of this? Not sure if it'll work in a hook, but `funcall` works on it.
axaxaxasmloe · 2022-05-23 17:20
This is ugly, but works: (defmacro clambda (vars &rest lambda-body) `(eval '(let ,(mapcar (lambda (sym) `(,sym ,sym)) vars) (lambda ,@lambda-body)) t)) `vars` is the list of lexical variables to capture, `lambda-body` is passed to `lambda`: (funcall (let ((a 1) (b 2)) (clambda (a) (x) (cons a x))) 3) => (1 . 3) Changing it to `(cons b x)` gives an error, since `b` is not captured. ---- Edit: it unfortunately doesn't handle multiple closures with shared captures, like setters and getters: (let ((a nil) (b nil)) (cons (clambda (a) (x) (setq a x)) (clambda (a) () a))) In the corresponding `lambda` version, the closures share `a`. ---- Edit 2: It does work using this modification: (defmacro capture (vars &rest body) (declare (indent defun)) `(eval '(let ,(mapcar (lambda (sym) (list sym sym)) vars) ,@body) t)) The first example now becomes: (let ((a 1) (b 2)) (capture (a) (lambda (x) (cons a x)))) which again only captures `a` but not `b`, but now it's possible to return two lambdas from the same `capture` block that share a lexical environment: (pcase-let ((`(,setter . ,getter) (let ((a nil) (b nil)) (capture (a) (cons (lambda (x) (setq a x)) (lambda () a)))))) (funcall setter 1) (funcall getter))
arthurno1 · 2022-05-23 21:27
> (let ((var value) (many-other-vars other-long-value)) (add-hook 'mode-hook (lambda nil (do-something var)))) > Is there a way to limit the closure to specific variables? (let ((var value)) (add-hook 'mode-hook (lambda nil (do-something var))) (let ((var1 val1) ... (varN valN)) (do-some-computations-with-your-vars))) That would be probably the simplest way I can think of.
Tohiko · 2022-05-24 09:01
That's brilliant. I didn't know `eval` does not share the same namespace. This is useful to know and the solution is quite great. I think setters will always be a problem because of something like this: ``` (let ((a 1) (b 2)) (funcall (capture (a) (lambda (x) (setq a x))) 10) a) ``` vs something like this: ``` (let ((a 1) (b 2)) (funcall (lambda (x) (setq a x)) 10) a) ```
axaxaxasmloe · 2022-05-24 09:18
Ah, right, `capture` creates a new environment, so the setter sees a different binding for `a` than the outside code. Yeah, that's probably unsolveable. I didn't know that about `eval` either, found it by playing around with the problem. I don't fully understand this magic tbh. It's not a completely new namespace, e.g. this works: (let ((a 1) (b 2)) (capture (a) b)) It appears to only apply to closures.
Phil-Hudson · 2022-05-24 09:28
(let ((var value) (many-other-vars other-long-value)) (add-hook 'mode-hook (defun uniquename nil (do-something var)))) Check it out. It works. Caveat: the return type of `defun` is undocumented. In (wildly improbable) theory, it *could* change under you.
Tohiko · 2022-05-24 10:05
Tried. It doesn't.
axaxaxasmloe · 2022-05-24 13:08
Another addition: according to [this article](https://nullprogram.com/blog/2013/12/30/) by /u/skeeto, byte compiling the function that generates the closure will get rid of unused captures. Edit: also [this one](https://nullprogram.com/blog/2017/12/14/)
Phil-Hudson · 2022-05-24 13:28
I use it in many places in my config. Perhaps we have some different value for the term "works".
oantolin · 2022-05-25 03:24
Try byte compiling, I think it does do that optimization.