16

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?

Drew
  • 75,699
  • 9
  • 109
  • 225
Rose Kunkel
  • 273
  • 1
  • 7
  • `defun` does not do what you think it does, it always creates a top-level definition. Elisp is after all not Scheme... – wasamasa Jan 07 '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. – Rose Kunkel Jan 07 '17 at 15:30

3 Answers3

9

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.

Stefan
  • 26,154
  • 3
  • 46
  • 84
6

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.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • 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 https://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.

Basil
  • 12,019
  • 43
  • 69