5

I am reading SICP to learn Lisp, the book uses Scheme dialect. I want accommodate it to emacs lisp, I find something weird in 3.1.1 and cannot figure it out, say I have a test.el file:

(defun make-account ()
  (defun withdraw (amount)
    (print "in withdraw"))
  (lambda () 'withdraw))


(setq a (make-account))
(withdraw 10)

Execute this file with command emacs -batch -l test.el yields "in withdraw". Why is not function withdraw local in make-account?

And if I comment out (setq a (make-account)), it complains about Symbol’s function definition is void: withdraw.

What am I missing here? Thanks for any help!

Drew
  • 75,699
  • 9
  • 109
  • 225
Alaneuler
  • 277
  • 1
  • 8
  • `withdraw` is global, but only declared when `make-account` has been run. – choroba Dec 24 '18 at 03:36
  • so how can I define a local function just like `define` in Scheme? then as a returned value which can be called outside? – Alaneuler Dec 24 '18 at 03:48
  • 2
    Emacs Lisp is *not* Scheme. They're completely different dialects of Lisp, with different idioms. What you'd solve in Scheme by defining a local function is solved in Emacs Lisp by defining another top-level function and using it. For this reason it's common to see functions following the `my--helper-function` or `my-helper-function-1` pattern. – wasamasa Dec 24 '18 at 10:31

2 Answers2

4

Use cl-labels to define local functions (cl-flet works, but recursive function reports error), and in the beginning of the .el file enable the lexical scoping:

;; -*- lexical-binding: t -*-
(defun make-account ()
  (cl-labels ((withdraw (amount)
                        (print "in withdraw")))
    (lambda () #'withdraw)))

Remember the # before the quoting of function name (for why # is needed, see link).

I've tested this in ielm mode, the output of (make-account) is:

ELISP> (make-account)
(closure
 ((--cl-withdraw-- closure #1
                   (amount)
                   (print "in withdraw"))
  t)
 nil --cl-withdraw--)

We can see that the closure for withdraw is returned.

Thanks to @stefan and @db48x for helpful information.

Alaneuler
  • 277
  • 1
  • 8
0

You probably want to use flet:

(defun make-account ()
  (flet ((withdraw (amount)
                   (print "in withdraw")))
    (lambda () 'withdraw)))
db48x
  • 15,741
  • 1
  • 19
  • 23
  • 2
    cl.el's `flet` doesn't behave like Common-Lisp's so this won't work. But if you change that to use `cl-flet` (and then fix the code to use `#'withdraw` and to add a pair of parentheses around `withdraw`'s definition), and tell Aleneuler to use `lexical-binding` then indeed it should work. – Stefan Dec 24 '18 at 05:23
  • @stefan or db48x: Please consider adding such info to the answer (or another answer). Comments can be deleted at any time. Thx. – Drew Dec 24 '18 at 06:12