How to Explain Lexical vs Dynamic Binding? - lisp

I read a relevant post on binding, however still have questions.
Here are the following examples I found. Can someone tell me if the conclusions are correct?
Dynamic Binding of x in (i):
(defun j ()
(let ((x 1))
(i)))
(defun i ()
(+ x x))
> (j)
2
Lexical Binding of x in i2:
(defun i2 (x)
(+ x x))
(defun k ()
(let ((x 1))
(i2 2)))
> (k)
4
No Global Lexical Variables in ANSI CL so Dynamic Binding is performed:
(setq x 3)
(defun z () x)
> (let ((x 4)) (z))
4
Dynamic Binding, which appears to bind to a lexically scoped variable:
(defvar x 1)
(defun f (x) (g 2))
(defun g (y) (+ x y))
> (f 5)
7
Based on the above tests, CL first tries lexical binding. If there is no lexical match in the environment, then CL tries dynamic binding. It appears that any previously lexically scoped variables become available to dynamic binding. Is this correct? If not, what is the behavior?

(defun j ()
(let ((x 1))
(i)))
(defun i ()
(+ x x))
> (j)
2
This is actually undefined behavior in Common Lisp. The exact consequences of using undefined variables (here in function i) is not defined in the standard.
CL-USER 75 > (defun j ()
(let ((x 1))
(i)))
J
CL-USER 76 > (defun i ()
(+ x x))
I
CL-USER 77 > (j)
Error: The variable X is unbound.
1 (continue) Try evaluating X again.
2 Return the value of :X instead.
3 Specify a value to use this time instead of evaluating X.
4 Specify a value to set X to.
5 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 78 : 1 >
As you see, the Lisp interpreter (!) complains at runtime.
Now:
(setq x 3)
SETQ sets an undefined variable. That's also not fully defined in the standard. Most compilers will complain:
LispWorks
;;;*** Warning in (TOP-LEVEL-FORM 1): X assumed special in SETQ
; (TOP-LEVEL-FORM 1)
;; Processing Cross Reference Information
;;; Compilation finished with 1 warning, 0 errors, 0 notes.
or SBCL
; in: SETQ X
; (SETQ X 1)
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::X
;
; compilation unit finished
; Undefined variable:
; X
; caught 1 WARNING condition
(defvar x 1)
(defun f (x) (g 2))
(defun g (y) (+ x y))
> (f 5)
7
Dynamic Binding, which appears to bind to a lexically scoped variable
No, x is globally defined to be special by DEFVAR. Thus f creates a dynamic binding for x and the value of x in the function g is looked up in the dynamic environment.
Basic rules for the developer
never use undefined variables
when using special variables always put * around them, so that it is always visible when using them, that dynamic binding&lookup is being used. This also makes sure that one does NOT declare variables by accident globally as special. One (defvar x 42) and x will from then on always be a special variable using dynamic binding. This is usually not what is wanted and it may lead to hard to debug errors.

In summary: no, CL never 'tries one kind of binding then another': rather it establishes what kind of binding is in effect, at compile time, and refers to that. Further, variable bindings and references are always lexical unless there is a special declaration in effect, in which case they are dynamic. The only time when it is not lexically apparent whether there is a special declaration in effect is for global special declarations, usually performed via defvar / defparameter, which may not be visible (for instance they could be in other source files).
There are two good reasons it decides about binding like this:
firstly it means that a human reading the code can (except possibly in the case of a global special declaration which is not visible) know what sort of binding is in use – getting a surprise about whether a variable reference is to a dynamic or lexical binding of that variable is seldom a pleasant experience;
secondly it means that good compilation is possible – unless the compiler can know what a variable's binding type is, it can never compile good code for references to that variable, and this is particularly the case for lexical bindings with definite extent where variable references can often be compiled entirely away.
An important aside: always use a visually-distinct way of writing variables which have global special declarations. So never say anything like (defvar x ...): the language does not forbid this but it's just catastrophically misleading when reading code, as this is the exact case where a special declaration is often not visible to the person reading the code. Further, use *...* for global specials like the global specials defined by the language and like everyone else does. I often use %...% for nonglobal specials but there is no best practice for this that I know of. It's fine (and there are plenty of examples defined by the language) to use unadorned names for constants since these may not be bound.
The detailed examples and answers below assume:
Common Lisp (not any other Lisp);
that the code quoted is all the code, so there are no additional declarations or anything like that.
Here is an example where there is a global special declaration in effect:
;;; Declare *X* globally special, but establish no top-level binding
;;; for it
;;;
(defvar *x*)
(defun foo ()
;; FOO refers to the dynamic binding of *X*
*x*)
;;; Call FOO with no binding for *X*: this will signal an
;;; UNBOUND-VARIABLE error, which we catch and report
;;;
(handler-case
(foo)
(unbound-variable (u)
(format *error-output* "~&~S unbound in FOO~%"
(cell-error-name u))))
(defun bar (x)
;; X is lexical in BAR
(let ((*x* x))
;; *X* is special, so calling FOO will now be OK
(foo)))
;;; This call will therefore return 3
;;;
(bar 3)
Here is an example where there are nonglobal special declarations.
(defun foo (x)
;; X is lexical
(let ((%y% x))
(declare (special %y%))
;; The binding of %Y% here is special. This means that the
;; compiler can't know if it is referenced so there will be no
;; compiler message even though it is unreferenced in FOO.
(bar)))
(defun bar ()
(let ((%y% 1))
;; There is no special declaration in effect here for %Y%, so this
;; binding of %Y% is lexical. Therefore it is also unused, and
;; tere will likely be a compiler message about this.
(fog)))
(defun fog ()
;; FOG refers to the dynamic binding of %Y%. Therefore there is no
;; compiler message even though there is no apparent binding of it
;; at compile time nor gobal special declaration.
(declare (special %y%))
%y%)
;;; This returns 3
;;;
(foo 3)
Note that in this example it is always lexically apparent what binding should be in effect for %y%: just looking at the functions on their own tells you what you need to know.
Now here are come comments on your sample code fragments.
(defun j ()
(let ((x 1))
(i)))
(defun i ()
(+ x x))
> (j)
<error>
This is illegal in CL: x is not bound in i and so a call to i should signal an error (specifically an unbound-variable error).
(defun i2 (x)
(+ x x))
(defun k ()
(let ((x 1))
(i2 2)))
> (k)
4
This is fine: i2 binds x lexically, so the binding established by k is never used. You will likely get a compiler warning about unused variables in k but this is implementation-dependent of course.
(setq x 3)
(defun z () x)
> (let ((x 4)) (z))
<undefined>
This is undefined behaviour in CL: setq's portable behaviour is to mutate an existing binding but not to create a new bindings. You are trying to use it to do the latter, which is undefined behaviour. Many implementations allow setq to be used like this at top-level and they may either create what is essentially a global lexical, a global special, or do some other thing. While this is often done in practice when interacting with a given implementation's top level, that does not make it defined behaviour in the language: programs should never do this. My own implementation squirts white-hot jets of lead from hidden nozzles in the general direction of the programmer when you do this.
(defvar x 1)
(defun f (x) (g 2))
(defun g (y) (+ x y))
> (f 5)
7
This is legal. Here:
defvar declares x globally special, so all bindings of x will be dynamic, and establishes a top-level binding of x to 1;
in f the binding of its argument, x will therefore be dynamic, not lexical (without the preceding defvar it would be lexical);
in g the free reference to x will be to its dynamic binding (without the preceding defvar it would be a compile-time warning (implementation-dependent) and a run-time error (not implementation-dependent).

Related

Common Lisp: Any way to avoid defvar or defparameter?

I'm using SBCL 2.0.1.debian and Paul Graham's ANSI Common Lisp to learn Lisp.
Right in Chapter 2 though, I'm realizing that I cannot use setf like the author can! A little googling and I learn that I must use defvar or defparameter to 'introduce' my globals before I can set them with setq!
Is there any way to avoid having to introduce globals via the defvar or defparameter, either from inside SBCL or from outside via switches? Do other Lisp's too mandate this?
I understand their value-add in large codebases but right now I'm just learning by writing smallish programs, and so am finding them cumbersome. I'm used to using globals in other languages, so don't necessarily mind global- / local-variable bugs.
If you are writing programs, in the sense of things which have some persistent existence in files, then use the def* forms. Top-level setf / setq of an undefined variable has undefined semantics in CL and, even worse, has differing semantics across implementations. Typing defvar, defparameter or defconstant is not much harder than typing setf or setq and means your programs will have defined semantics, which is usually considered a good thing. So no, for programs there is no way to avoid using the def* forms, or some equivalent thereof.
If you are simply typing things at a REPL / listener to play with things, then I think just using setf at top-level is fine (no-one uses environments where things typed at the REPL are really persistent any more I think).
You say you are used to using globals in other languages. Depending on what those other languages are this quite probably means you're not used to CL's semantics for bindings defined with def* forms, which are not only global, but globally special, or globally dynamic. I don't know which other languages even have CL's special / lexical distinction, but I suspect that not that many do. For instance consider this Python (3) program:
x = 1
def foo():
x = 0
print(x)
def bar():
nonlocal x
x += 1
return x
return bar
y = foo()
print(y())
print(y())
print(x)
If you run this it will print
0
1
2
1
So consider the 'equivalent' CL program (note: never write a program with variables named like this):
(defvar x 1)
(defun foo ()
(let ((x 0))
(print x)
(lambda ()
(incf x))))
(let ((y (foo)))
(print (funcall y))
(print (funcall y))
(print x)
(values))
If you run this it will print
0
2
3
3
Which I think you can agree is very different from what the Python program printed.
That's because top-level variables in CL are special, or dynamic variables: in the above CL program x is dynamically scoped because x has been declared globally special by the defvar, and this means that the calls to the function returned by foo are altering the global value of x.
Python doesn't have dynamic bindings at all (but you can write Python modules which will simulate them as functions which access an explicit binding stack, with a bit of deviousness). Something like this is also how other languages expose dynamic bindings. For instance this is how Racket does it:
(define d (make-parameter 1))
(define (foo)
(parameterize ([d 0])
(display (d))
(λ ()
(d (+ (d) 1))
(d))))
(let ([y (foo)])
(display (y))
(display (y))
(display (d)))
will print
0
2
3
3
While this program
(define x 1)
(define (foo)
(let ([x 0])
(displayln x)
(λ ()
(set! x (+ x 1))
x)))
(let ([y (foo)])
(displayln (y))
(displayln (y))
(displayln x))
will print
0
1
2
1
as the Python one does.
If it wasn't for CL's compatibility requirements this is perhaps how CL should have done this too (that's obviously a personal opinion, and I'm happy with how CL does do it).
CL doesn't have top-level (global) lexical bindings at all (but you can write a CL program which will simulate them in a pretty convincing way using symbol macros). If you want such things I suspect there are already some on the internet or I could get on and tidy up my implementation. As an example of a program which uses such a thing:
(defglex x 1)
(defun foo ()
(let ((x 0))
(print x)
(lambda ()
(incf x))))
(let ((y (foo)))
(print (funcall y))
(print (funcall y))
(print x))
will print
0
1
2
1
CL has both lexical and dynamic (special) nonglobal bindings.
As a note: the fact that things defined with the def* forms are globally special / dynamic, which means that all bindings of them are dynamic, is why you should always distinguish the names of such variables, using the * convention: (defvar *my-var* ...) and never (defvar my-var ...). ((defconstant my-constant ...) is OK however.)

How Lisp (Allegro Common Lisp) uses variables in lambda with ' vs #'

I am hoping someone can explain why tests 1-5 work but test 6 does not. I thought that quoting a lambda with ' and using #' in front of a lambda both returned pointers to the function with the only difference being that the #' will compile it first.
(defun test-1 (y)
(mapcar (lambda (x) (expt x 2))
'(1 2 3)))
(defun test-2 (y)
(mapcar (lambda (x) (expt x y))
'(1 2 3)))
(defun test-3 (y)
(mapcar #'(lambda (x) (expt x 2))
'(1 2 3)))
(defun test-4 (y)
(mapcar #'(lambda (x) (expt x y))
'(1 2 3)))
(defun test-5 (y)
(mapcar '(lambda (x) (expt x 2))
'(1 2 3)))
(defun test-6 (y)
(mapcar '(lambda (x) (expt x y))
'(1 2 3)))
I am using the free version of Franz Industries Allegro Common Lisp. The following are the outputs:
(test-1 2) ; --> (1 4 9)
(test-2 2) ; --> (1 4 9)
(test-3 2) ; --> (1 4 9)
(test-4 2) ; --> (1 4 9)
(test-5 2) ; --> (1 4 9)
(test-6 2) ; --> Error: Attempt to take the value of the unbound variable `Y'. [condition type: UNBOUND-VARIABLE]
For a start, you should be aware that your tests 1-4 are conforming Common Lisp, while your tests 5 and 6 are not. I believe Allegro is perfectly well allowed to do what it does for 5 and 6, but what it is doing is outside the standard. The bit of the standard that talks about this is the definition of functions like mapcar, which take function designators as argument, and the definition of a function designator:
function designator n. a designator for a function; that is, an object that denotes a function and that is one of: a symbol (denoting the function named by that symbol in the global environment), or a function (denoting itself). The consequences are undefined if a symbol is used as a function designator but it does not have a global definition as a function, or it has a global definition as a macro or a special form. [...]
From this it is clear that a list like (lambda (...) ...) is not a function designator: it's just a list whose car happens to be lambda. What Allegro is doing is noticing that this list is in fact something that can be turned into a function and doing that.
Well, let's just write a version of mapcar which does what Allegro's does:
(defun mapcar/coercing (maybe-f &rest lists)
(apply #'mapcar (coerce maybe-f 'function) lists))
This just uses coerce which is a function which knows how to turn lists like this into functions, among other things. If its argument is already a function, coerce just returns it.
Now we can write the two tests using this function:
(defun test-5/coercing (y)
(mapcar/coercing '(lambda (x) (expt x 2))
'(1 2 3)))
(defun test-6/coercing (y)
(mapcar/coercing '(lambda (x) (expt x y))
'(1 2 3)))
So, after that preamble, why can't test-6/explicit work? Well the answer is that Common Lisp is (except for for special variables) lexically scoped. Lexical scope is just a fancy way of saying that the bindings (variables) that are available are exactly and only the bindings you can see by looking at the source of the program. (Except, in the case of CL for special bindings, which I'll ignore, since there are none here.)
So, given this, think about test-6/coercing, and in particular the call to mapcar/coercing: in that call, coerce has to turn the list (lambda (x) (expt z y)) into a function. So it does that. But the function it returns doesn't bind y and there is no binding for y visible in it: the function uses y 'free'.
The only way that this could work is if the function that coerce constructs for us were to dynamically look for a binding for y. Well, that's what dynamically-scoped languages do, but CL is not dynamically-scoped.
Perhaps a way of making this even clearer is to realise that we can lift the function creation right out of the function:
(defun test-7 (y f)
(mapcar f '(1 2 3)))
> (test-7 1 (coerce '(lambda (x) (expt x y)) 'function))
It's clear that this can't work in a lexically-scoped language.
So, then, how do tests 1-4 work?
Well, firstly there are only actually two tests here. In CL, lambda is a macro and (lambda (...) ...) is entirely equivalent to (function (lambda (...) ...)). And of course #'(lambda (...) ...) is also the same as (function (lambda (...) ...)): it's just a read-macro for it.
And (function ...) is a magic thing (a special form) which says 'this is a function'. The important thing about function is that it's not a function: it's a deeply magic thing which tells the evaluator (or the compiler) that its argument is the description of a function in the current lexical context, so, for instance in
(let ((x 1))
(function (lambda (y) (+ x y))))
The x referred to by the function this creates is the x bound by let. So in your tests 2 and 4 (which are the same):
(defun test-4 (y)
(mapcar (function (lambda (x) (expt x y)))
'(1 2 3)))
The binding of y which the function created refers to is the binding of y which is lexically visible, which is the argument of test-4 itself.
Let's add a y parameter to avoid closing over variables and see what kind of values we are manipulating:
USER> (type-of #'(lambda (x y) (expt x y)))
FUNCTION
USER> (type-of (lambda (x y) (expt x y)))
FUNCTION
USER> (type-of '(lambda (x y) (expt x y)))
CONS
As you can see, the two first lambda-like forms are evaluated as functions, while the third is evaluated as a cons-cell. As far as Lisp is concerned, the third argument is just a tree of symbols with no meaning.
Reader macros
I thought that quoting a lambda with ' and using #' in front of a lambda both returned pointers to the function with the only difference being that the #' will compile it first.
Let's go back to the definitions, ' and #' are reader macros, respectively Single-Quote and Sharpsign Single-Quote. They are found in front of other forms, for example 'f is read as (quote f) and #'f is read as (function f). At read-time, f and the resulting forms are just unevaluated data.
We will see below how both special operators are interpreted, but what matters really is the lexical scope, so let's open a parenthesis.
Lexical environment
Lexical environments are the set of bindings in effect at some point of your code. When you evaluate a let or an flet it enriches the current environment with new bindings. When you call EVAL on an expression, you start evaluating from a null lexical environment, even if the call to eval itself is in a non-null environment.
Here x is just unbound during eval:
(let ((x 3)) (eval '(list x))) ;; ERROR
Here we build a let to be evaluated by eval:
(eval '(let ((x 3)) (list x)))
=> (3)
That's all for the crash course on lexical environments.
Special operators
FUNCTION
Special operator FUNCTION takes an argument that is either the name of a function (symbol or setf), or a lambda expression; in particular:
The value of function is the functional value of name in the current lexical environment.
Here the lambda expression is evaluated in the current lexical environment, which means it can refer to variable outside the lambda expression. That's the definition of closures, they capture the surrounding bindings.
NB. you do not need to prefix lambda with #', because there is a macro named (lambda ...) that expands into (function (lambda ...)). It looks like this could expand recursively forever, but this is not the case: at first the macro is expanded so that (lambda ...) becomes (function (lambda ...)), then the special operator function knows how to evaluate the lambda expression itself.
This means that (lambda ...) and #'(lambda ...) are equivalent. Note in particular that there is nothing about whether one form is compiled or not at this point, the compiler will see the same expression after macroexpansion.
QUOTE
Special operator QUOTE evaluates (quote f) as f, where f itself is unevaluated. In test-5 and test-6, there is no function, just an unevaluated structured expression that can be interpreted as code.
Type coercion
Now, certain functions like MAPCAR are used to apply functions. Notice how the specification says that the function parameter is a function designator:
function --- a designator for a function that must take as many arguments as there are lists.
A designator for a type is not necessarily a value of that type, but can be a value that can be coerced to that type. Sometimes a user wants to specify a pathname, and enters a string, but a string is not a value of type pathname: the system has to converts the string into a pathname.
Common Lisp defines a COERCE function with rules regarding how values can be converted to other values. In you case, mapcar first does (coerce (lambda ...) 'function). This is defined as follows:
If the result-type is function, and object is a lambda expression, then the result is a closure of object in the null lexical environment.
The value is thus evaluated in a null lexical environment, so it does not have access to the surrounding bindings; y is a free variable in your lambda expression, and since it is evaluated in a null environment, it is unbound. That's why test-5 pass but test-6 fails.
Name resolution, compilers and late binding
There is a difference whether you write #'f or 'f when referring to a function f where f is a symbol: in the first case, the expression evaluated to an object of type function, and in the second case, you only evaluate a symbol.
Name resolution for this function can change depending and how the compiler works. With a symbol as a function designator, the function does not even need to be defined, the name is resolved when the symbols has to be coerced as a function.
When you write #'f, some compilers may remove one level of indirection and directly make your code jump to the code associated with the function, without having to resolve the name at runtime.
However, this also means that with such compilers (e.g. SBCL), you need to recompile some call sites on function redefinition, as-if the function was declared inline, otherwise some old code will still reference the previous definition of #'f. This is something that is not necessarily important to consider at the beginning, but it can be a source of confusion to keep in mind when you are live coding.

Mutability of core functions in Racket

I came across a rather unexpected behavior today, and it utterly contradicted what (I thought) I knew about mutability in Racket.
#lang racket
(define num 8)
;(define num 9)
Uncommenting the second line gives back the error "module: duplicate definition for identifier in: num", which is fine and expected. After all, define is supposed to treat already defined values as immutable.
However, this makes no sense to me:
#lang racket
(define num 8)
num
(define define 1)
(+ define define)
It returns 8 and 2, but...
define is not set!, and should not allow the redefinition of something already defined, such as define itself.
define is a core language feature, and is clearly already defined, or I should not be able to use num at all.
What gives? Why is define, which is used to create immutable values, not immutable to itself? What is happening here?
(define define 1)
This example shows shadowing, which is different from mutation.
Shadowing allocates new locations in memory. It does not mutate existing ones.
Concretely, the new define shadows the define from Racket.
All languages with a notation of local scope allow shadowing, eg:
> (define x 10)
> (define (f x) ; x shadowed in function f
(displayln x)
(set! x 2) ; (local) x mutated
(displayln x))
> (f 1)
1
2
; local x is out of scope now
> (displayln x) ; original x unmutated
10
For the other example,
(define num 8)
;(define num 9)
this demonstrates that you cant shadow something within the same scope, which is also standard in other languages, eg:
> (define (g x x) x) ; cant have two parameters named x
When a top-level definition binds an identifier that originates from a macro expansion, the definition captures only uses of the identifier that are generated by the same expansion due to the fresh scope that is generated for the expansion.
In other words, the transformers (macros) that are required from other modules can be re-defined because they're expanded out by the macro expander (so the issue is not quite about mutability) Moreover, since racket is all about extensibility, there are no reserved keywords and additional functionality can be added to the current define through a macro (or a function) - that's why it can be redefined.
define is defined as macro of this form - see here.
#lang racket
(module foo1 racket
(provide foo1)
(define-syntaxes (foo1)
(let ([trans (lambda (syntax-object)
(syntax-case syntax-object ()
[(_) #'1]))])
(values trans))))
; ---
(require 'foo1)
(foo1)
; => 1
(define foo1 9)
(+ foo1 foo1)
; => 18

Using special variables as macro input?

I want to make a macro for binding variables to values given a var-list and a val-list.
This is my code for it -
(defmacro let-bind (vars vals &body body)
`(let ,(loop for x in vars
for y in vals
collect `(,x ,y))
,#body))
While it works correct if called like (let-bind (a b) (1 2) ...), it doesn't seem to work when called like
(defvar vars '(a b))
(defvar vals '(1 2))
(let-bind vars vals ..)
Then I saw some effects for other of my macros too. I am a learner and cannot find what is wrong.
Basic problem: a macro sees code, not values. A function sees values, not code.
CL-USER 2 > (defvar *vars* '(a b))
*VARS*
CL-USER 3 > (defvar *vals* '(1 2))
*VALS*
CL-USER 4 > (defmacro let-bind (vars vals &body body)
(format t "~%the value of vars is: ~a~%" vars)
`(let ,(loop for x in vars
for y in vals
collect `(,x ,y))
,#body))
LET-BIND
CL-USER 5 > (let-bind *vars* *vals* t)
the value of vars is: *VARS*
Error: *VARS* (of type SYMBOL) is not of type LIST.
1 (abort) Return to top loop level 0.
You can see that the value of vars is *vars*. This is a symbol. Because the macro variables are bound to code fragments - not their values.
Thus in your macro you try to iterate over the symbol *vars*. But *vars* is a symbol and not a list.
You can now try to evaluate the symbol *vars* at macro expansion time. But that won't work also in general, since at macro expansion time *vars* may not have a value.
Your macro expands into a let form, but let expects at compile time real variables. You can't compute the variables for let at a later point in time. This would work only in some interpreted code where macros would be expanded at runtime - over and over.
If you’ve read the other answers then you know that you can’t read a runtime value from a compiletime macro (or rather, you can’t know the value it will have at runtime at compiletime as you can’t see the future). So let’s ask a different question: how can you bind the variables in your list known at runtime.
In the case where your list isn’t really variable and you just want to give it a single name you could use macroexpand:
(defun symbol-list-of (x env)
(etypecase x
(list x)
(symbol (macroexpand x env))))
(defmacro let-bind (vars vals &body body &environment env)
(let* ((vars (symbol-list-of vars env))
(syms (loop for () in vars collect gensym)))
`(destructuring-bind ,syms ,vals
(let ,(loop for sym in syms for bar in vars collect (list var sym)) ,#body))))
This would somewhat do what you want. It will symbol-macroexpand the first argument and evaluate the second.
What if you want to evaluate the first argument? Well we could try generating something that uses eval. As eval will evaluate in the null lexical environment (ie can’t refer to any external local variables), we would need to have eval generate a function to bind variables and then call another function. That is a function like (lambda (f) (let (...) (funcall f)). You would evaluate the expression to get that function and then call it with a function which does he body (but was not made by eval and so captures the enclosing scope). Note that this would mean that you could only bind dynamic variables.
What if you want to bind lexical variables? Well there is no way to go from symbol to the memory location of a variable at runtime in Common Lisp. A debugger might know how to do this. There is no way to get a list of variables in scope in a macro, although the compiler knows this. So you can’t generate a function to set a lexically bound symbol. And it would be even harder to do if you wanted to shadow the binding although you could maybe do it with some symbol-macrolet trickery if you knew every variable in scope.
But maybe there is a better way to do this for special variables and it turns out there is. It’s an obscure special form called progv. It has the same signature that you want let-bind to have except it works. link.

Why is my variable "undefined"?

Why is my variable nodes undefined in the vector-push-extend line?
(defun make_graph (strings)
(defparameter nodes (make-array 0))
(loop for x in strings do
(vector-push-extend (make-instance 'node :data x) nodes))
n)
The short answer is that you should use let instead of defparameter to introduce your variable. For instance:
(defun make_graph (strings)
(let ((nodes (make-array 0)))
(loop for x in strings do
(vector-push-extend (make-instance 'node :data x) nodes))
;; your code says N here, but I assume that's a typo...
nodes))
The defparameter form is useful for creating "special" variables, which are somewhat similar to global variables in other programming languages. (There are some differences, e.g., the special variables introduced by defparameter aren't exactly global---instead, they are dynamically scoped, and can be let bound, etc...)
At any rate, the let form will instead create a local variable.
DEFPARAMETER is used at toplevel to define global special variables.
Toplevel:
(defparameter *foo* 42)
Still at toplevel, because forms inside PROGN are still at toplevel (by definition):
(progn
(defparameter *foo* 42)
(defparameter *bar* 32))
Not at toplevel:
(defun baz ()
(defparameter *foo* 42))
Above last form is not recognized by the compiler as a variable declaration. But when one calls (baz) and the function is running, the variable is defined and initialized.
A non-toplevel use of DEFPARAMETER will not be recognized by the compiler, but at runtime it will create a special global variable.
(defun make_graph (strings)
(defparameter nodes (make-array 0))
(loop for x in strings do
(vector-push-extend (make-instance 'node :data x) nodes))
n)
The compiler warns:
;;;*** Warning in MAKE_GRAPH: NODES assumed special
;;;*** Warning in MAKE_GRAPH: N assumed special
Thus in above code, the compiler does not recognize nodes as a defined variable, if it wasn't defined somewhere else already. The use of nodes in the function creates a warning.
Still the code might work, since at runtime the variable is created and initialized - but for every function invocation. Over and over. This compiler also assumes that nodes is just this: some kind of special variable. Still I would not count on it for all compilers.
n is also not defined anywhere.
Notes:
the correct way to introduce local lexical variables is to use LET and LET* (and other binding forms)
use DEFPARAMETER as a toplevel form. It is unusual when it's not a toplevel form. Typically the author makes a mistake then.