4

Consider the following metacode:

;;; -*- lexical-binding: t -*-

(defvar var1 ...)
(defvar var2 ...)

(defun main ()
  "Main entry point" ...)
(defun func1 ...)
(defun func2 ...)
...
(defun funcn ...)

(defun f ...)
(defun g ...)

If a variable is relevant to the whole logic of your program, you make it visible to all functions declaring it global in defvar. If a variable is relevant only to some functions, say f and g above, you make it visible to the latter functions only. This is known as friendship, avoids "cluttering" the global space and can be easily implemented in elisp like follows:

;;; -*- lexical-binding: t -*-

(defun main ()
  "Main entry point"
  (message "f: %s; g: %s" (f) (g)))

(let ((x 0))
  (defun f () (1+ x))
  (defun g () (+ 2  x)))

Now f and g share the same variable x without using the global space, that is without (defvar x 0).

Unfortunately the compiler wrongly complains:

Warning: the following functions are not known to be defined: f, g

One can use without problems the byte-compiled code, but in so far as you want to use the compiler to lint your code, false positives are not welcomed. Plus, the let-bound variable x becomes static.

I would like to find an alternative approach avoiding warnings and static variables.

What I am not looking for

I am aware of this question. Will Kunkel receives the same warning, however he has a totally different objective: he "want(s) to get the effect of a static variable by using defun inside of let".

I have not one but several defuns and want to share variables, not make them static.

IMHO the proposed let-bound variables in defalias are better avoided because calling twice the defalias function may cause an error difficult to spot as it does not stop your program, it just produces a biased output. Of course, this does not apply in specific cases when you explicitly need your function to keep its value between invocations.

antonio
  • 1,762
  • 12
  • 24
  • 3
    Possible duplicate of [Defun inside let with lexical binding gives byte-compile warning "the function is not known to be defined"](https://emacs.stackexchange.com/questions/29853/defun-inside-let-with-lexical-binding-gives-byte-compile-warning-the-function-i). – Basil Mar 16 '18 at 00:37
  • I don't think it's a duplicate. The OP clearly knows that `f` and `g` are not local definitions. This question is about how to make the byte compiler aware of the fact that they have been defined by the time `g` is invoked in the call to `message`. – Drew Mar 16 '18 at 04:19
  • @Drew The OP in the proposed dup target is [also aware of that](https://emacs.stackexchange.com/questions/29853/defun-inside-let-with-lexical-binding-gives-byte-compile-warning-the-function-i#comment45862_29853), and that question is also about making the byte compiler aware of bound functions. On the other hand, having two functions sharing a let-binding won't work with Stefan's answer there, I think. – npostavs Mar 16 '18 at 09:41
  • @npostavs: I guess they are more or less the same, as they both say they get the behavior they expect and they just want to know how to suppress the byte-compiler warnings. – Drew Mar 16 '18 at 14:30
  • 1
    @antonio after all your updates, I'm now much more confused as to what your question is. – npostavs Mar 19 '18 at 04:14
  • @npostavs: Rewrote 4th time from scratch and summarising. – antonio Mar 19 '18 at 22:07
  • 1
    In `(let ((x 0)) (defun f ()...) (defun g ()...))` the `x` is every bit as "static" as in the `defalias` case. So it seems your question is still confused/confusing. – npostavs Mar 20 '18 at 00:05
  • @npostavs: Why you say so? If I evaluate `(f)`, `(g)`, or `(main)` twice I get the same result. – antonio Mar 20 '18 at 00:21
  • 2
    @antonio: You get the same result each time because none of your functions modify the value of `x`. – phils Mar 20 '18 at 00:57
  • @phils: Oh yes, sorry, I was to fast too answer. Then the your implied answer seems: there is no shared variable without a static one. Isn't it? – antonio Mar 20 '18 at 17:37
  • Again, there's only dynamic scope or lexical scope -- but I'm going to say "yes". In this example, `x` is only visible to a pair of function definitions `f` and `g` (there being no other code within that `let` body), so it is "shared" between the things which can see it, and it is "static" in that the variable exists (and persists) independently of calls to those functions. If you wanted a variable which was local to a single function and reset its value each time the function was called, you would have typically have a `let` within the function body. – phils Mar 20 '18 at 19:48

1 Answers1

3

defalias "magically" stores the variables inside its functions, so their values can be reused with subsequent calls.

This is not what defalias does; see (elisp) Defining Functions. The means by which variables are "captured" in functions is called scoping and is specific to neither of defun, defalias or fset; see (elisp) Variable Scoping.

With defalias the variables are not actually shared, because the "owner" is the defalias function.

I think what you mean is that, so long as either of them is nested within a let, a defalias makes the function definition no clearer to the byte-compiler than a defun. There are various mitigations for the byte-compiler warnings listed under the linked question.

To capture local variables in functions, whether across a single function or multiple, you must enable lexical-binding:

;;; -*- lexical-binding: t -*-

(let ((greeting "hello"))
  (defun f ()
    (message "%s f" greeting))
  (defun g ()
    (message "%s g" greeting)))

(f)
(g)

The result is:

hello f
hello g

It doesn't matter whether you use defun, defalias or fset here - the result is always two closures f and g over the local variable greeting.

You can even modify the lexically bound variable:

;;; -*- lexical-binding: t -*-

(let ((greeting "hello"))
  (defun f ()
    (setq greeting "goodbye")
    (message "%s f" greeting))
  (defun g ()
    (message "%s g" greeting)))

(g)
(f)
(g)

The result is:

hello g
goodbye f
goodbye g

So, as long as you're not forgetting to enable lexical-binding, I'm not sure what you mean by "static storage" and why that gives rise to wrong values / errors.


Update

I would like to find an alternative approach avoiding warnings.

At the time of writing, the linked question showcases a couple of workarounds for the false byte-compiler warnings. In decreasing order of idiomaticity, simplicity, correctness, and elegance:

  1. Use declare-function as pointed out by Drew. (Highly recommended!)
  2. Wrap the whole form in eval-and-compile as pointed out by yours truly. (Your friends may laugh at you unless you know the precise reason for doing this, and friends are known to laugh regardless.)

The reason replacing (let ((...)) (defun ...)) with (defalias ... (let ((...)) (lambda ...))), as suggested by Stefan, doesn't work in your case is because sharing a lexical variable across multiple closures still requires a top-level let (as opposed to top-level defalias or defun), thus confusing the byte-compiler.

I am aware of this question. Will Kunkel receives the same warning, however he has a totally different objective: he "want(s) to get the effect of a static variable by using defun inside of let".

The objective of Will Kunkel's code is irrelevant; the linked question is about silencing the same false byte-compiler that you are seeing, whose cause is a non-top-level function definition, i.e. a function definition obfuscated by a surrounding let form.

IMHO the proposed let-bound variables in defalias are better avoided. Calling twice the defalias function may cause an error difficult to spot as it does not stop your program, it just produces a biased output.

I see neither reason to avoid the suggested closure, nor why it should give rise to errors, nor what you mean by "biased output". It sounds like you have misunderstood how lexical-binding and/or defalias work and are possibly misusing them. Keep in mind that defun is merely a wrapper around defalias.

Basil
  • 12,019
  • 43
  • 69
  • @phils Thanks, I've tried to clarify. The original intent was to address OP's question, not emphasise the multiple functions. – Basil Mar 18 '18 at 11:50
  • Thanks for spotting the missing lexical bindings. It was kind of implicit in my snippets, but it could cause misunderstandings, so I updated again, explaining the unclear parts too. As regards your solution, say I have 20 functions with global variables shared through `defvar` and "occasional" variables shared just by a couple of functions. Isolating the latter in a single `let` seemed (also visually) a good idea, but the compiler wrongly complains. Adding lexical binding has no effect on compiler warnings. – antonio Mar 18 '18 at 19:35
  • 1
    @antonio `lexical-binding` has nothing to do with compiler warnings and everything to do with scoping. You cannot create closures without `lexical-binding`. The reason `defalias` suppresses warnings in the linked question is because it is at the top-level, whereas a `defun` within a `let` is not at the top-level. Please see the [linked question](https://emacs.stackexchange.com/q/29853/15748) for ways of suppressing byte-compiler warnings pertaining to non-top-level function definitions. – Basil Mar 19 '18 at 13:42
  • 1
    @antonio In your updated question, you seem to be conflating concepts from other programming languages with Elisp. Please read the suggested [`(elisp) Variable Scoping`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Variable-Scoping.html) and [`(elisp) Variables`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Variables.html) more generally to understand how variable definitions and scoping work in Elisp. I also fail to see how your updated question has anything to do with its original title, so please consider opening a new, more focussed question. – Basil Mar 19 '18 at 13:45
  • @Because "Making a variable visible to some functions only" is a feature that exists in C++ (known to many). Theoretical discussions apart, your snippets still give me the compiler warnings. Btw, there is 4th rewrite. – antonio Mar 19 '18 at 22:08
  • @antonio As has already been pointed out to you, the linked question already tells you how to silence the byte-compiler. As my answer already explains, the snippets showcase `lexical-binding`, not how to silence the byte-compiler. See my updated answer. – Basil Mar 19 '18 at 22:46