3

So I want to know if there is standard way of having code like this:

(let ((x 10))
  (define (add10 a)
     (+ x a)))

I know about:

(define add10 (let ((x 10))
                 (lambda (a) (+ x a))))

but this will not work if I want to define multiple functions, I need to know standard way so I can write macro that will define functions. where you can call macro inside let:

(let ((x 10))
  (macro x))

and for instance macro will create list of functions:

(let ((x 10))
  (define (add1)
     (+ x 1))
  (define (add2)
     (+ x 2))
  (define (add3)
     (+ x 3)))

Is there standard way of defining functions add1..add3? In scheme I was testing, the functions will be local inside let and not accessible outside.

If you show macro code I'm only interested in lisp macros with define-macro and quasiquote, please no define-syntax, because this is mostly for use in my own lisp (based on scheme) where I have only lisp macros.

If scheme don't give support for something like that, does other dialect like Common Lisp allow something like this?

jcubic
  • 61,973
  • 54
  • 229
  • 402

3 Answers3

3
(let ((x 10))
  (somemacro x))

->

(let ((x 10))
  (define (add1)
     (+ x 1))
  (define (add2)
     (+ x 2))
  (define (add3)
     (+ x 3)))

In Common Lisp:

CL-USER 43 > (defmacro somemacro (var)
               `(progn
                  (defun add1 () (+ ,var 1))
                  (defun add2 () (+ ,var 2))
                  (defun add3 () (+ ,var 3))))
SOMEMACRO

CL-USER 44 > (let ((x 10))
               (somemacro x))
ADD3

CL-USER 45 > (add1)
11

CL-USER 46 > (add3)
13

One sees that sometime. Generally exactly this is slight bad style in Common Lisp, because the file compiler will then not recognize that there are global function declarations, because inside the LET a DEFUN is not at top-level. If a function is defined in a file at top-level, then at compile time the file compiler would see that this is a function and may do special things like noting the signature in the compile-time environment, inlining it. etc.

Note that when DEFINE in Scheme defines a local function, one still might be able to do (depending on what the implementation does additionally to the standard):

(let ((x 10))
  ()
  (define (add1) (+ x 1)))

Note that in Common Lisp defun defines global functions and flet / labels define local functions.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • The last paragraph in http://clhs.lisp.se/Body/s_eval_w.htm says that let over eval-when might be useful for defun/defmacro, while the description says that compile-time side-effects only take place when eval-when is a top-level form. Maybe there is a way to achieve what OP wants by wrapping the whole thing in eval-when (and I agree that this is bad style, btw) – coredump Apr 26 '19 at 11:57
  • will expanded function have `(+ x 1)` or `(+ 10 1)` what I need is `(+ x 1)`. I'm trying to simplify actual issue I'm having, where I have object that need to be in closure not evaluated and put into lisp code, since it can't have lips representation like number 10. – jcubic Apr 26 '19 at 11:58
  • @jcubic: the expanded function has `x`. – Rainer Joswig Apr 26 '19 at 11:59
  • That last example works in some Scheme and don't in other, I think it was working in previous guile or in Kawa (scheme in Java). – jcubic Apr 26 '19 at 11:59
  • 1
    Can you explain what that empty list do in scheme? This seems to work in biwascheme but don't know why. where without empty list if don't work. – jcubic Apr 26 '19 at 12:02
  • @jcubic: the empty list evaluates to the empty list. ;-) – Rainer Joswig Apr 26 '19 at 12:03
  • but what the difference if you have it and don't. check https://www.biwascheme.org/ if you use it add1 is defined globaly and without it it's local. This is weird this works `(let ((x 10)) 1 (define (add2) (+ x 2)))` – jcubic Apr 26 '19 at 12:05
  • That's weird maybe it's a bug in implementation, is this in the scheme spec? – jcubic Apr 26 '19 at 12:06
  • That's in the Scheme spec. 'Definitions can occur at the beginning of a ⟨body⟩'. Now what might not be in the spec: what to do with non-top-level non-local DEFINEs inside a LET. – Rainer Joswig Apr 26 '19 at 12:06
  • This don't work in guile, throw error that definition is in expression context where they are not allowed. – jcubic Apr 26 '19 at 12:09
  • That's why I wrote 'might'. It could be an implementation specific thing. One als might check if an implementation has other ways to create a top-level variable. – Rainer Joswig Apr 26 '19 at 12:10
  • `()` is not valid Scheme syntax and of course `define` needs to be in the beginning of a lambda body. The last code is therefore in violation of the Scheme reports and is undefined and unportable behaviour like mutating quoted lists in Scheme and CL. It might work, it might not, it might signal an error. – Sylwester Apr 26 '19 at 22:21
  • @Sylwester is doesn't, and it does. :) It doesn't work, and it does signal an error -- in Racket, both under `#lang r5rs` and `#lang racket` that I checked. – Will Ness Apr 29 '19 at 13:29
3

I think that no solution which wraps a binding around define can work at all portably or safely as the wrapped define's will either construct local bindings (leading forms in the body) or be illegal (non-leading forms in the body), although I'd be happy for a Scheme-standards person to point out where I'm wrong.

Instead something like this slightly nasty hack ought to work I think.

(begin
  (define inc undefined)
  (define dec undefined)
  (let ((x 3))
    (set! inc (lambda (y)
                (set! x (+ x y))
                x))
    (set! dec (lambda (y)
                (set! x (- x y))
                x))))

Here I've relied on a constant called undefined which means 'not yet defined properly': Racket provides this in racket/undefined but it can in general be anything: you could just, somewhere, say

(define undefined 'undefined)

for instance.

The trick is to define the things you want at top-level with placeholder values, and then assign to them inside the let.

I'm sure a macro can be defined which expands to something like (this is why I have the whole thing inside a begin): I haven't done so because it's fiddly and I use Racket so I can't easily write old-style Lisp macros in it.


Note that the obvious approach in a modern scheme is to use define-values:

(define-values (x y) (let (...) (values ...)))

Does what you want. As mentioned in another answer you can implement define-values as a macro if you only have multiple values. But if you don't have multiple values at all, then you can use something which defines things based on a list of results:

(define-list (x y) (let (...) (list ...)))

Here are two rough variants of that macro: the first uses Racket's native macros:

(require racket/undefined)

(define-syntax define-list
  (syntax-rules ()
    [(define-list () expr)
     (let ((results expr))
       (unless (zero? (length results))
         (error "need an empty list"))
       (void))]
    [(define-list (name ...) expr)
     (begin
       (define name undefined)
       ...
       (let ([results expr])
         (unless (= (length results)
                    (length '(name ...)))
           (error "wrong number of values"))
         (set! name (begin0
                      (car results)
                      (set! results (cdr results))))
         ...))]))

while the second uses unhygenic macros in Racket:

(require compatibility/defmacro
         racket/undefined)

(define-macro (define-list names expr)
  `(begin
     ,@(let loop ([ntail names] [defs '()])
         (if (null? ntail)
             (reverse defs)
             (loop (cdr ntail)
                   (cons `(define ,(car ntail) undefined) defs))))
     (let ([results ,expr])
       (unless (= (length results)
                  (length ',names))
         (error "wrong number of values"))
       ,@(let loop ([ntail names] [i 0] [assignments '()])
           (if (null? ntail)
               (reverse assignments)
               (loop (cdr ntail) (+ i 1)
                     (cons `(set! ,(car ntail) (list-ref results ,i))
                           assignments)))))))

Note that neither of these has been tested, and I'd need to spend a little time convincing myself that the second is hygenic enough.

But with these:

> (define-list (inc dec)
    (let ([i 0])
      (list
       (lambda ()
         (set! i (+ i 1))
         i)
       (lambda ()
         (set! i (- i 1))
         i))))
> inc
#<procedure>
> (inc)
1
> (dec)
0
> (dec)
-1
> 
  • Isn't it surprising that one can't define a global variable from inside a LET according to 'the' standard? Is that an unusual functionality? – Rainer Joswig Apr 26 '19 at 19:42
  • @RainerJoswig Well, I hate the CL `(let (...) (defun ...) ...)` equivalent, so I'm biased. If we're allowed Racket then `(define-values (...) (let (...) ... (values ...)))` works well, and I think `define-values` can be implemented as a macro, probably. –  Apr 26 '19 at 20:09
3

In R7RS, the latest Scheme report, we have define-values. It can be used in this way:

(define-values (add1 add2 add3)
  (let ((x 10))
    (values (lambda () (+ x 1))
            (lambda () (+ x 2))
            (lambda () (+ x 3)))))

Of course for larger blocks one might want to make a local define and the reference it in values instead.

In the R7RS report you'll find a syntax rule for define-values that will work for R6RS and R5RS. It uses call-with-values where the values are passed to list and then define from that. I bet it also works inside lambdas sucn that the Scheme implementation actually can transform this into a letrec so while it is not very elegant it does the dirty work.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • This is cool: I was trying to work out if `define-values` could be written as a macro, and it turns out that it can. Thanks! –  Apr 27 '19 at 08:58
  • Can `define-values` be used inside let, or is it the same as with define, let and single lambda? If it's the same then there is use for it with case where let is outside of macro that generate functions. – jcubic Apr 27 '19 at 18:03
  • @jcubic it works the same as `define`. if you do `(let () (define name ...))` then `name` becomes local probably transformed into a `letrec`. Thus you `let` needs to go in the expression as I have demonstrated. `define-values` is also useful when dealing with procedures that return several values eg. `(define-values (q r) (quotient-and-remainder 15 7))` and this is a use case fo local bindings. – Sylwester Apr 27 '19 at 20:14