0

I need to retrieve a local, lexical, runtime variable from a function, but I'm unable to modify the function to return it because it is from an external library. The variable I'm unable to reach is link-end.

(defun org-element-link-parser () (let ((link-end) ...) ... (setq link-end (something)) ...)) 

Language features I've looked into don't seem to be able to help, advising functions is basically a wrapper, and as to hooks, org-element-link-parser doesn't have hooks. I tried defining link-end in global scope, but it didn't change after the function quit.

0

2 Answers 2

2

The answer can be split into a general answer to the question as stated in the caption

Any way to access a lexical let variable outside of the let?

and an answer to your specific problem in the description, how to access link-end in org-element-link-parser. The general answer does not work for your specific problem since you do not want to modify org-element-link-parser.

The short answer to both parts is: Yes, you can.

First, I want to address your specific problem:

An excerpt from org-element-parser:

(defun org-element-link-parser () (let (... link-end ...) (cond ... ;; all cases look like the following: ((looking-at ...) ... (setq link-end (match-end 0)) ... ) ) ;; After the `cond': (save-excursion (setq post-blank (progn (goto-char link-end) (skip-chars-forward " \t"))) (setq end (point))) (list 'link (list ;; The element properties: ... :end end ...)))) 

So, end is essentially link-end, only that there is the additional operation (skip-chars-forward " \t").

If you want to retrieve the value of link-end you have to reverse the effect of (skip-chars-forward " \t") to the property :end. Luckily the number of chars skipped by (skip-chars-forward " \t") is saved as property :post-blank.

I.e., with point at a link, do:

(when-let ((link (org-element-link-parser)) (post-blank (org-element-property :post-blank link)) (end (org-element-property :end link)) (link-end (- end post-blank))) link-end) 

Now, the answer to the general question

Any way to access a lexical let variable outside of the let?

Yes, define setter/getter functions within the let for accessing the variable.
The variable will be in the lexical environment of these functions.

(declare-function my-set nil) (declare-function my-get nil) (defun my-function () "Function with the lexically bound variable `my-var'. We define `my-set' and `my-get' here." (let ((my-var 1)) (fset 'my-set (lambda (value) (setq my-var value))) (fset 'my-get (lambda () my-var))) "The function could also return the lambdas.") (format "The return value of `my-function': %s The value of `my-var': %d Setting `my-var' to 2: %d The value after `my-set': %d" (my-function) (my-get) (my-set 2) (my-get)) 

The resulting output is:

The return value of `my-function': The function could also return the lambdas.
The value of `my-var': 1
Setting `my-var' to 2: 2
The value after `my-set': 2

2

You can't do that and you shouldn't even try: let blocks are meant to define variables that are local in scope and limited in lifetime - they spring into existence when the block is entered and they go POOF when the block is exited. [1]

Instead, you should call the function and deduce what you need from what the function returns. In this case:

(setq my-link-end (org-element-property :end (org-element-link-parser))) 

The :end property of the element is actually after any white space that is trailing the link, so if you don't want that, you will need to back up:

(setq my-link-end (save-excursion (goto-char (org-element-property :end (org-element-link-parser))) (skip-chars-backward " \t") (point))) 

And there should probably be some error checking to make sure that you are at a link, otherwise the result of the org-element-property call will be nil and goto-char will barf.


Footnote:

[1] As Tobias made clear in his answer and as Zoey Hewll observes in a comment below, this statement is not true for lexically bound variables (they are local in scope but they can be captured in a nested lambda - e.g. a getter function - and so their lifetime is not limited).

6
  • I should've read the org-element-link-parser definition more carefully, I knew about :end, thought I didn't want it, but didn't realize that applying (skip-chars-backward " \t") on it is equivalent to link-end. In this case I won't need to expose a variable inside a let inside a lexical function, but I'll leave the question up just in wonder if it's possible. Commented Mar 30, 2023 at 20:22
  • In a lot of programming contexts I might feel the need to tweak one library function, and I find an extreme poverty of tools for that (short of redefining the function and falling behind on updates) in all programming languages I know about. Commented Mar 30, 2023 at 20:27
  • 1
    Emacs Lisp, like some other lisps, implements a general-purpose advice system (two of them, in fact) which is as good a system of "tweaking" other functions as I've encountered. gnu.org/software/emacs/manual/html_node/elisp/… documents the newer of the two systems. Advising functions is generally a last resort, but the ability is a blessing when you need it. Commented Mar 31, 2023 at 0:00
  • 1
    I see you've indicated in another comment that advice is inadequate for your purpose, and I'm not trying to argue otherwise; I'm just saying that "an extreme poverty of tools" isn't an accurate description when you have (a) the ability to redefine functions; (b) the ability to advise them in many different ways; and (c) if you really, really, really want to (and you shouldn't), the ability to manipulate code as data. Compared to many languages, this is a veritable wealth of tools. Commented Mar 31, 2023 at 0:09
  • 1
    Nitpick: your statement about let bindings being "local in scope and limited in lifetime" is misleading. let can introduce either a dynamic or lexical binding. Usually, either will when the block ends (so its lifetime is that of the block), but a lexical binding can be captured by a nested lambda, allowing the binding to outlive the block that established it (so that it has indefinite lifetime). That said, I agree with your conclusion in the context of this question. Commented Jan 29 at 5:51

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.