3

In Common Lisp I can evaluate the following snippet of code (in SBCL) without being signalled any syntax error:

(let ((x 0))
   (defun my-incf (y)
     (setf x (+ x y)))
   (defun my-decf (y)
     (setf x (- x y))))
MY-DECF

CL-USER> (my-incf 1)
1
CL-USER> (my-incf 1)
2
CL-USER> (my-decf 1)
1
CL-USER> (my-decf 1)
0

When I try to evaluate a corresponding Scheme snippet of code (in DrRacket):

(let ((x 0))
  (define (my-incf y)
    (set! x (+ x y)))
  (define (my-decf y)
    (set! x (- x y))))

it signals a syntax error.

begin (possibly implicit): no expression after a sequence of internal definitions in: (begin (define (my-incf y) (set! x (+ x y))) (define (my-decf y) (set! x (- x y))))

Does anybody know the reason why this cannot be done in Scheme?

Paulo Tomé
  • 1,910
  • 3
  • 18
  • 27
  • 1
    You would need to look at the syntax definition of LET in DrRacket. The Common Lisp example is valid, but I would not use it in my code. – Rainer Joswig Jun 27 '13 at 00:05
  • @Reiner Joswig Why would you not use it in your code? – Paulo Tomé Jun 27 '13 at 07:43
  • 1
    I would use CLOS instead. Using above makes debugging harder and it prevents the DEFUN form being recognized as a function definition by the compiler. Inside LET the DEFUN is no longer a top-level form. – Rainer Joswig Jun 27 '13 at 10:02
  • @Rainer Joswig Could you explain with more detail how you would use CLOS to implement the code above? – Paulo Tomé Jun 27 '13 at 10:40
  • 3
    isn't that obvious? X is a slot in a CLOS instance. The functions then are methods. – Rainer Joswig Jun 27 '13 at 11:23
  • Of course. That's simple. – Paulo Tomé Jun 27 '13 at 11:56
  • 1
    Incidentally, concerning the similarity of what you're doing to what you could do with CLOS, Scheme was originally invented as a language in which to explore object-oriented ideas--presumably using techniques analogous to those described below. – Mars Jun 28 '13 at 07:02
  • 1
    I wouldn't necessarily use OOP to solve a simple problem of a module with a global variable. The problem of the functions not being toplevel forms can be solved with `(defvar *counter* 0)` and having the functions refer to `*counter*`. The OOP approach applies if the module screams "I should really be an object that supports multiple instantiation, and all these globals should be my instance variables". If you don't anticipate the need for multiple instantiation, or any benefits from inheritance and such, stick with globals. Dynamic vars are the "Common Lispy" way to do globals. – Kaz Jun 28 '13 at 18:51

3 Answers3

10

You can't define top-level bindings outside of the top-level, in Scheme. (And inside of a let is definitely outside of the top-level---what you had, instead, was internal definitions, which are not exported to the top-level.) However, using define-values, you can still do what you need to do:

(define-values (my-incf my-decf)
  (let ((x 0))
    (values (lambda (y)
              (set! x (+ x y))
              x)
            (lambda (y)
              (set! x (- x y))
              x))))

However, you can still use internal definitions, to make your code more readable:

(define-values (my-incf my-decf)
  (let ((x 0))
    (define (my-incf y)
      (set! x (+ x y))
      x)
    (define (my-decf y)
      (set! x (- x y))
      x)
    (values my-incf my-decf)))

Best of both worlds. :-) In this case, the values sends the internal my-incf and my-decf definitions to the outer define-values, which is where the real top-level definition happens.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
3

Chris' solution is what I would use in this case, but here's another one in the spirit of "Let over Lambda" which can come in handy if you increase the number of operations on x:

(define inc-dec
  (let ((x 0))
    (lambda (msg y)
      (case msg
        ((incf) (set! x (+ x y)))
        ((decf) (set! x (- x y)))
        (else (error "wot?")))
      x)))


(inc-dec 'incf 1)
(inc-dec 'incf 1)
(inc-dec 'decf 1)
(inc-dec 'decf 1)
uselpa
  • 18,732
  • 2
  • 34
  • 52
0

Less elegant, but very simple: Define top-level variables, then set! or setf them to lambdas from inside the let, depending on whether it's Scheme or CL.

Mars
  • 8,689
  • 2
  • 42
  • 70