6

For example, when defining the natural number sequence stream, I can use

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

(defun nats (n)
  (cons n (lambda () (nats (1+ n)))))

(nats 0)
     => (0 closure ((n . 0) t) nil (nats (1+ n)))

But I want to call simply (nats) since the natural numbers begins with 0, and I don't want define a global helper function for nats since it is useless everywhere else. I tried the following, but it doesn't work.

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

(defalias 'nats
  (flet ((f (n) (cons n (lambda () (f (1+ n))))))
    (lambda () (f 0))))

(nats)
error--> nats: Symbol's function definition is void: f

It looks like f is not in the closure's environment. I also tried some other options. cl-letf produces the same result. cl-flet doesn't allow recursive local function definition.

Andrew Swann
  • 3,436
  • 2
  • 15
  • 43
xuchunyang
  • 14,302
  • 1
  • 18
  • 39
  • 1
    If you want a recursive local function, you can use `letrec` (a newish Elisp construct) or `cl-labels`. – Stefan Sep 12 '16 at 19:47

1 Answers1

8

Without cl-lib:

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

(defun nats ()
  (letrec ((inner (lambda (n) (cons n (lambda () (funcall inner (1+ n)))))))
    (funcall inner 0)))

(let* ((stream (nats))
       (i (car stream)))
  (while (< i 10)
    (message "Got %i" i)
    (setq stream (funcall (cdr stream))
          i (car stream))))

See also https://github.com/josteink/csharp-mode/issues/39#issuecomment-129636221. As suggested in the linked post, I'd generally recommend against writing such code and going for a top-level helper function instead. Emacs Lisp isn't Scheme after all, so you're only going to confuse whoever may read your code...

wasamasa
  • 21,803
  • 1
  • 65
  • 97
  • Thanks, it looks this is a very straightforward but correct solution. There is an issue with `defun`: once `nats` is called, `inner` is globally accessible, I guess `makunbound` can be used to fix it. – xuchunyang Sep 11 '16 at 11:25
  • Huh, that shouldn't happen. Either I'm wrong about this solution or there's a bug in the lexical binding code... – wasamasa Sep 11 '16 at 11:55
  • Anyway, I've replaced it with an alternative solution that doesn't require `cl-lib`. – wasamasa Sep 11 '16 at 12:06
  • 1
    `defun` always makes global bindings, it expands to `defalias` which is like `fset`. `letrec` is the right choice here. – npostavs Sep 11 '16 at 13:18
  • 2
    +1 (especially for the recommendation and reminder that Emacs Lisp is not Scheme). – Drew Sep 11 '16 at 17:12
  • You also need another `funcall` for `inner` in the innermost lambda. Now I see, for something like `(let ((inner (lambda () (funcall inner ...)))) (funcall inner))`, `let` works for dynamic environment and `letrec` works for lexical environment. – xuchunyang Sep 13 '16 at 00:55
  • Ugh, I must have had another `inner` left over from my previous attempts. Fixed. – wasamasa Sep 13 '16 at 06:40