13

I want to get the effect of a static variable by using defun inside of let with lexical binding to create a closure. However, when byte-compiling the file, I get a warning. Am I doing something wrong, or if not, is there a way to suppress this warning?

I've created an MCVE:

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

(let ((count 0))
  (defun increase-count ()
    (interactive)
    (setq count (1+ count))
    (message "Count is: %d" count))

  ;; The warning happens here.
  (increase-count))

The code works as expected: the function increase-count prints out "Count is: n" where n increases each time it is called. However, when byte-compiling this file, I get the following warning:

In end of data:
mcve.el:11:1:Warning: the function ‘increase-count’ is not known to be
    defined.

It seems to me that increase-count should always be defined before it is called at the end of the let-block. Is this not the case?

  • defun does not do what you think it does, it always creates a top-level definition. Elisp is after all not Scheme... – wasamasa Jan 7 '17 at 15:07
  • 2
    I'm aware that it creates a top-level definition; that's what I want. I just want that top level definition to be a closure. It seems to be working the way I want, except for this byte-compilation warning. – Will Kunkel Jan 7 '17 at 15:30
7

The byte-compiler's way to decide whether a function will be defined or not is very "naive" and gets fooled even in your "obvious" case. But you can write it in a way that lets the compiler understand what happens:

(defalias 'increase-count
  (let ((count 0))
    (lambda ()
      (interactive)
      (setq count (1+ count))
      (message "Count is: %d" count))))

Of course, even better would be to improve the byte-compiler's logic: patches welcome for that.

5

To suppress the byte-compiler warning, try adding this before your code, starting in column 0 (leftmost):

(declare-function increase-count "your-file-name.el")

C-h f declare-function tells you:

declare-function is a Lisp macro in subr.el.

(declare-function FN FILE &optional ARGLIST FILEONLY)

Tell the byte-compiler that function FN is defined, in FILE. The FILE argument is not used by the byte-compiler, but by the check-declare package, which checks that FILE contains a definition for FN.

FILE can be either a Lisp file (in which case the ".el" extension is optional), or a C file. C files are expanded relative to the Emacs "src/" directory. Lisp files are searched for using locate-library, and if that fails they are expanded relative to the location of the file containing the declaration. A FILE with an "ext:" prefix is an external file. check-declare will check such files if they are found, and skip them without error if they are not.

Optional ARGLIST specifies FN’s arguments, or is t to not specify FN’s arguments. An omitted ARGLIST defaults to t, not nil: a nil ARGLIST specifies an empty argument list, and an explicit t ARGLIST is a placeholder that allows supplying a later arg.

Optional FILEONLY non-nil means that check-declare will check only that FILE exists, not that it defines FN. This is intended for function definitions that check-declare does not recognize, e.g., defstruct.

Note that for the purposes of check-declare, this statement must be the first non-whitespace on a line.

For more information, see Info node (elisp)Declaring Functions.

  • Is a non-nil FILEONLY argument necessary for the case at hand? BTW, I would have given the same answer;-). – Tobias Mar 16 '18 at 20:30
  • @Tobias: FILEONLY didn't seem to be needed here, for me. Which would seem to indicate that check-declare recognizes the f and g defuns. – Drew Mar 16 '18 at 20:52
  • @Drew, I think that last comment about f and g only makes sense in the context of emacs.stackexchange.com/q/39439 ? – phils Mar 19 '18 at 22:19
  • @phils: Yes, I meant to say this: FILEONLY didn't seem to be needed here, for me. Which would seem to indicate that check-declare recognizes the increase-count defun. ;-) – Drew Mar 19 '18 at 23:13
3

I believe placing the definition in question within eval-and-compile would also superficially achieve the same result as in Stefan's correct answer:

(eval-and-compile
  (let ((count 0))
    (defun increase-count ()
      (interactive)
      (setq count (1+ count))
      (message "Count is: %d" count))))

I am, however, barely familiar with the subtleties of using eval-and-compile and, furthermore, do not expect this approach to be in any way superior.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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