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.

11
  • 3
  • 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
    Commented Mar 16, 2018 at 4:19
  • 1
    @antonio after all your updates, I'm now much more confused as to what your question is.
    – npostavs
    Commented Mar 19, 2018 at 4:14
  • 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
    Commented Mar 20, 2018 at 0:05
  • 2
    @antonio: You get the same result each time because none of your functions modify the value of x.
    – phils
    Commented Mar 20, 2018 at 0:57

1 Answer 1

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.

6
  • @phils Thanks, I've tried to clarify. The original intent was to address OP's question, not emphasise the multiple functions.
    – Basil
    Commented Mar 18, 2018 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
    Commented Mar 18, 2018 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 for ways of suppressing byte-compiler warnings pertaining to non-top-level function definitions.
    – Basil
    Commented Mar 19, 2018 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 and (elisp) Variables 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
    Commented Mar 19, 2018 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
    Commented Mar 19, 2018 at 22:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.