Search code examples
variablesscopeglobal-variablescommon-lisplocal-variables

Can you create local variables without a `let`?


eg1 - using a let

(defun demo1 ()
 (let ((a 1)
       (b 2))
  ; these print fine
  (print a)
  (print b)))

(demo1)
; these get "no value" errors, as expected
(print a)
(print b)

output:

1 
2 *** - EVAL: variable A has no value

eg2 - without a let, the variables escape

(this is very surprising for anyone used to more modern scope-rules like eg ruby's)

(defun demo2 ()
 (setf a 1)
 (setf b 2)
 ; these print fine
 (print a)
 (print b))

(demo2)
; these escape, and also print with no error!
(print a)
(print b)

output:

1 
2 
1 
2 

how can you make them not escape?
i monkey'd around with setq and defvar
(which were mentioned in the only results i could find by looking for documentation on "local variables")
but no joy at all

eg3 - trying to use a macro

(
this is the practical problem i wanted to solve in the first place --
the syntax of let forces you to use extra layers of parens,
and wrap up the entire function body in the outermost layer,
which just makes it harder to read and write for no reason
(since the overwhelmingly most common use case for let
always includes the entire function body and nothing else),
so i wanted to make a with macro instead
)

(defmacro with (&rest list-of-pairs)
 (loop for (name value) in list-of-pairs
    do  `(setf ,name ,value)    ;A
  ; do  (setf name value)       ;B
  ; (i tried both A and B)
 ))

(defun demo3 ()
 (with (a 1)
       (b 2))
 ; this gets a "no value" error when called
 (print a)
 (print b))

(demo3)
; it never even gets to this point cuz of the above error
(print a)
(print b)

output:

*** - PROGN: variable A has no value

how can you get the variables to escape into the function scope and not beyond?

[
this question asks

can anyone tell me how to define a local variable in lisp other than let?

but none of the answers were helpful to me
]

EDIT-TO-ADD eg4

thinking about the way the loop macro works
(from the point of view of someone calling it without understanding its internals, i mean)...
well, look:

(loop for i from 1 to 5 
 do (print i))

i don't know yet what the definition of loop looks like,
but it's abstractly something like this, right?:

(defmacro loop ([args somehow,
                 including the `sexpr`
                 which goes after the `do` keyword in the macro call])
 [other stuff]
 do ([sexpr])
 [other stuff])

(i'm focusing on the do keyword as an example just because the syntax of the call is relatively simple.)

so what i actually need to do is make my own my-defun macro
and include a with keyword,
right?

something like this:

(defmacro my-defun ([args somehow,
                     including a `paired-list-of-names-and-values`
                     to go after a `with` keyword in the macro call])
 (let
  ([paired-list-of-names-and-values])
  ([function body])))

(my-defun demo4 ()
 with (
 (a 1)
 (b 2)
 )
 ; this should print
 (print a)
 (print b))

(demo4)
; this should get a "no value" error
(print a)
(print b)

am i on the right track here?

if so, where do i go from here?

like, what are some simple, straight-forward macro definitions i can look at to induce how they work?
or something like that


Solution

  • [EDIT:
    i just realized that i messed up on the arguement handling,
    so my "solution" is broken
    if there aren't at least three lists given to my-defun after its parameter list.

    i put the (i think) actual working solution at the end.
    ]

    i actually figured this out!

    it's not too hard at all,
    even for a newb.

    (
    i mean,
    i wouldn't be surprised if horrible things would happen if one actually tried to use it in "real code",
    because edge-cases or something,
    but it's a working proof-of-concept
    )

    anyway, here's a definition of the my-defun macro,
    working as i described in eg4 in my question:

    (
    please nobody edit the weird formatting --
    i realize it's non-standard,
    but it really helps newbs read difficult new stuff.
    i mean,
    if another newb like me ever reads this,
    i think it'll help them significantly.
    )

    (defmacro my-defun (name params withsymbol withlist &body body)
     (cond
       ((equal withsymbol 'with)    
                                    ; (print 'withsymbol_set_to_)   ;diagnostic
                                    ; (print withsymbol)            ;diagnostic
                                    ; (print 'withlist_set_to_)     ;diagnostic
                                    ; (print withlist)              ;diagnostic
                                    ; (print                        ;diagnostic
                                     `(defun ,name ,params          
                                       (let ,withlist               
                                        (progn ,@body)              
                                       )                            
                                      )                             
                                    ; )                             ;diagnostic
       )                            
       (t                           
                                    ; (print 'withsymbol_not_set_to_with_but_)  ;diagnostic
                                    ; (print withsymbol)                        ;diagnostic
                                    ; (print 'withlist_set_to_)                 ;diagnostic
                                    ; (print withlist)                          ;diagnostic
                                    ; (print                                    ;diagnostic
                                     `(defun ,name ,params                      
                                        (progn ,withsymbol ,withlist ,@body)    
                                      )                                         
                                    ; )                                         ;diagnostic
    
       )
     )
    )
    

    first test, with a with:

    (my-defun demo4 (x)
     with (
      (a 1)
      (b 2)
     )
     ; this prints!
     (print a)
     (print b)
     (print x)
    )
    (demo4 "hi")
    ; this correctly gets a "no value" error!
    (print a)
    (print b)
    (print x)
    

    output:

    1 
    2 
    "hi" 
    

    output with diagnostic lines uncommented:

    WITHSYMBOL_SET_TO_ 
    WITH 
    WITHLIST_SET_TO_ 
    ((A 1) (B 2)) 
    (DEFUN DEMO4 (X) (LET ((A 1) (B 2)) (PROGN (PRINT A) (PRINT B) (PRINT X)))) 
    1 
    2 
    "hi" 
    

    second test, with no with:

    (so it acts exactly like a normal defun)

    (my-defun demo4 (x)
     ; (this stuff also prints)
     (print "i am not the withsymbol")
     (print "this is not the withlist")
     ; this prints!
     (print "symbol 'a would have no value")
     (print "symbol 'b would have no value")
     (print x)
    )
    
    (demo4 "hi")
    ; this correctly gets a "no value" error!
    '(print a)
    '(print b)
    '(print x)
    

    output:

    "i am not the withsymbol" 
    "this is not the withlist" 
    "symbol 'a would have no value" 
    "symbol 'b would have no value" 
    "hi" 
    

    output with diagnostic lines uncommented:

    WITHSYMBOL_NOT_SET_TO_WITH_BUT_ 
    (PRINT "i am not the withsymbol") 
    WITHLIST_SET_TO_ 
    (PRINT "this is not the withlist") 
    (DEFUN DEMO4 (X)
     (PROGN (PRINT "i am not the withsymbol") (PRINT "this is not the withlist") (PRINT "symbol 'a would have no value")
      (PRINT "symbol 'b would have no value") (PRINT X))) 
    "i am not the withsymbol" 
    "this is not the withlist" 
    "symbol 'a would have no value" 
    "symbol 'b would have no value" 
    "hi" 
    

    minimally different examples:

    using defun with let
    and using my-defun with with
    (just wanted to eyeball to what extent the result looks worth the trouble xD)

    (   defun demo (x)
     (let (
           (a 1)
           (b 2)
          )
      (print a)
      (print b)
      (print x)
     )
    )
    
    (my-defun demo (x)
     with (
           (a 1)
           (b 2)
          )
     (print a)
     (print b)
     (print x)
    )
    

    ACTUALLY WORKING SOLUTION (I HOPE):

    (defmacro fun (name params &rest rest)
     (let (
           (withsymbol  (car rest))
           (withlist    (car (cdr rest)))
           (body        (cdr (cdr rest)))
          )
      ; (p withsymbol   )   ;;debug
      ; (p withlist     )   ;;debug
      ; (p body         )   ;;debug
      (cond
       ((equal withsymbol 'with)    
                                    ; (print 'BRANCH_A)     ;;debug
                                    ; (print                ;;debug
                                     `(defun ,name ,params  
                                       (let* ,withlist      
                                        (progn ,@body)      
                                       )                    
                                      )                     
                                    ; )                     ;;debug
       )                            
       (t                           
                                    ; (print 'BRANCH_B)     ;;debug
                                    ; (print                ;;debug
                                     `(defun ,name ,params  
                                        (progn ,@rest)      
                                      )                     
                                    ; )                     ;;debug
    
       )
      )
     )
    )
    ;; for debugging
    (defmacro p (symbol)
     `(format t "~A ~A~%" ',symbol ,symbol)
    )
    

    although that was the earliest working version of the code, so maybe i messed it up without noticing by renaming variables incompletely or something.

    the most recent code that i actually just tested is more complicated:

    ;; used in debug
    (defmacro p (symbol)
     `(format t "~A ~A~%" ',symbol ,symbol))
    
    (defmacro mac-or-fun (which-one name params rest)
     (let (
           (withsymbol  (car rest))
           (withlist    (car (cdr rest)))
           (body        (cdr (cdr rest)))
          )
      ; (p withsymbol   )   ;;debug
      ; (p withlist     )   ;;debug
      ; (p body         )   ;;debug
      (cond
       ((equal withsymbol 'with)    
                                    ; (print 'BRANCH_A)         ;;debug
                                    ; (print                    ;;debug
                                     `(,which-one ,name ,params 
                                       (let* ,withlist          
                                        (progn ,@body)          
                                       )                        
                                      )                         
                                    ; )                         ;;debug
       )                            
       ((equal withsymbol 'omwith)  
                                    ; (print 'BRANCH_A)         ;;debug
                                    ; (print                    ;;debug
                                     `(,which-one ,name ,params 
                                       (omlet ,withlist         
                                        (progn ,@body)          
                                       )                        
                                      )                         
                                    ; )                         ;;debug
       )                            
       (t                           
                                    ; (print 'BRANCH_B)         ;;debug
                                    ; (print                    ;;debug
                                     `(,which-one ,name ,params 
                                        (progn ,@rest)          
                                      )                         
                                    ; )                         ;;debug
    
       )
      )
     )
    )
    (defmacro fun (name params &rest rest)
      `(mac-or-fun defun ,name ,params ,rest))
    (defmacro mac (name params &rest rest)
      `(mac-or-fun defmacro ,name ,params ,rest))
    
    
    ;; for use in tests
    (defun ps (&rest stringlist)
         (format t "~A~%" (eval `(concatenate 'string ,@stringlist))))
    (defparameter *vs-use-count* 0)
    (defmacro vs (&rest title)
     (setf *vs-use-count* (+ 1 *vs-use-count*))
     (ps "
    
    SECTION " (write-to-string *vs-use-count*) " " (write-to-string title)  " -"
     )
    )
    
    ;;;tests
    (progn
    
    (vs fun works with "with")
    (fun f ()
         with ((a 1))
         (print a)
         )
    (f)
    (vs fun works with "nil")
    (fun f ()
         ()
         )
    (print(f))
    
    (vs original fun test with "with")
    (fun demo4 (x)
     with (
      (a 1)
      (b 2)
     )
     ; this prints!
     (print a)
     (print b)
     (print x)
    )
    (demo4 "hi")
    ; these would correctly gets a "no value" error!
    '(print a)
    '(print b)
    '(print x)
    
    (vs original fun test with no "with")
    (fun demo4 (x)
     ; (this stuff also prints)
     (print "i am not the withsymbol")
     (print "this is not the withlist")
     ; this prints!
     (print "symbol 'a would have no value")
     (print "symbol 'b would have no value")
     (print x)
    )
    
    (demo4 "hi")
    ; these would correctly gets a "no value" error!
    '(print a)
    '(print b)
    '(print x)
    
    
    
    
    (vs mac works with "with")
    (mac m ()
         with ((a 1))
         (print a)
         )
    (m)
    
    (vs mac works with "nil")
    (mac m ()
         ()
         )
    (print(m))
    )
    
    
    ;;; more stuff,
    ;;; leading up to the macro `omlet`,
    ;;; which is used in `mac-or-fun`
    (fun pair-up (l)
     with (
      (a        (car l)         )
      (b        (car (cdr l))   )
      (l-past-b (cdr (cdr l))   )
     )
     (cond
      (
       (equal 2 (length l))
       (list l)
      )
      (t
       (cons (list a b) (pair-up l-past-b))
      )
     )
    )
    
    (fun crack-1 (eggs)
        with (
            (paired-list    (pair-up eggs))
            (paired-list    (loop for (k v) in paired-list collect `(,k ',v)))
        )
        paired-list
    )
    (fun crack-2 (eggs)
        with (
            (key-name           (car eggs))
            (value-name         (car (cdr eggs)))
            (eggs               (cdr (cdr eggs)))
            (paired-list        (pair-up eggs))
            (keys-list          (loop for pair in paired-list collect (first pair)))
            (values-list        (loop for pair in paired-list collect (second pair)))
            (key-name-keys      (list key-name keys-list))
            (value-name-values  (list value-name values-list))
            (paired-list        (append paired-list (list key-name-keys value-name-values)))
            (paired-list        (loop for (k v) in paired-list collect `(,k ',v)))
        )
        paired-list
    )
    (fun crack (eggs)
        (if
            (and
                (equal '- (car eggs))
                (oddp (length eggs))
            )
            (crack-2 (cdr eggs))
            (crack-1 eggs)
        )
    )
    (mac omlet (eggs &body body)
        with ((to-let (crack eggs)))
        `(let ,to-let (progn ,@body))
    )
    (mac lemego (&rest eggs)
        with ((to-set (crack eggs)))
        (loop for (name value) in to-set
            do (eval `(setf ,name ,value))
        )
    )
    
    
    ;;; more tests
    (progn
    
    (vs omlet 1)
    (omlet (
        x   a
        y   b
        z   c
    )
        (print  x   )
        (print  y   )
        (print  z   )
    )
    
    (vs omlet 2)
    (omlet (
        - names digits
        one     1
        two     2
        three   3
    )
        (print  one     )
        (print  two     )
        (print  three   )
        (print  names   )
        (print  digits  )
    )
    
    
    (vs fun with omwith 1)
    (fun f ()
    omwith (
        x   a
        y   b
        z   c
    )
        (print  x   )
        (print  y   )
        (print  z   )
    )
    (f)
    
    (vs fun with omwith 2)
    (fun f ()
    omwith (
        - names digits
        one     1
        two     2
        three   3
    )
        (print  one     )
        (print  two     )
        (print  three   )
        (print  names   )
        (print  digits  )
    )
    (f)
    
    
    (vs lemego 1)
    (lemego
    x   a
    y   b
    z   c
    )
    (print  x   )
    (print  y   )
    (print  z   )
    
    
    (vs lemego 2)
    (lemego
    - names digits
    one     1
    two     2
    three   3
    )
    (print  one     )
    (print  two     )
    (print  three   )
    (print  names   )
    (print  digits  )
    )