Unit 3
Unit 3
SUBPROGRAMS:
Introduction
Subprograms are the fundamental building blocks of programs and are therefore
among the most import concepts in programming language design.
The reuse results in several different kinds of savings, including memory space and
coding time.
Fundamentals of Subprograms
General Subprogram Characteristics
a. A subprogram has a single entry point.
b. The caller is suspended during execution of the called subprogram, which implies
that there is only one subprogram in execution at any given time.
c. Control always returns to the caller when the called subprogram’s execution
terminates
Basic Definitions
A subprogram definition is a description of the actions of the subprogram
abstraction.
A subprogram call is an explicit request that the called subprogram be executed.
A subprogram is said to be active if, after having been called, it has begun
execution but has not yet completed that execution.
The two fundamental types of the subprograms are:
o Procedures
o Functions
A subprogram header is the first line of the definition, serves several definitions:
o It specifies that the following syntactic unit is a subprogram definition of some
particular kind.
o The header provides a name for the subprogram.
o May optionally specify a list of parameters.
Consider the following header examples:
Fortran
Subroutine Adder(parameters)
Ada
procedure Adder(parameters)
C
void Adder(parameters)
No special word appears in the header of a C subprogram to specify its kind.
The parameter profile (sometimes called the signature) of a subprogram is the
number, order, and types of its formal parameters.
The protocol of a subprogram is its parameter profile plus, if it is a function, its return
type.
A subprogram declaration provides the protocol, but not the body, of the
subprogram.
A formal parameter is a dummy variable listed in the subprogram header and used
in the subprogram.
An actual parameter represents a value or address used in the subprogram call
statement.
Function declarations are common in C and C++ programs, where they are called
prototypes.
Java and C# do not need declarations of their methods, because there is no
requirement that methods be defined before they are called in those languages.
Parameters
Subprograms typically describe computations. There are two ways that a non-local
method program can gain access to the data that it is to process:
1. Through direct access to non-local variables.
The only way the computation can proceed on different data is to
assign new values to those non-local variables between calls to the
subprograms.
Extensive access to non-locals can reduce reliability.
2. Through parameter passing “more flexible”.
Data passed through parameters are accessed through names that are
local to the subprogram.
A subprogram with parameter access to the data it is to process is a
parameterized computation.
It can perform its computation on whatever data it receives through its
parameters.
A formal parameter is a dummy variable listed in the subprogram header and
used in the subprogram.
Subprograms call statements must include the name of the subprogram and a list of
parameters to be bound to the formal parameters of the subprogram.
An actual parameter represents a value or address used in the subprogram call
statement.
Actual/Formal Parameter Correspondence:
1. Positional: The first actual parameter is bound to the first formal parameter
and so forth. “Practical if list is short.”
2. Keyword: the name of the formal parameter is to be bound with the actual
parameter. “Can appear in any order in the actual parameter list.”
SORT(LIST => A, LENGTH => N);
Advantage: order is irrelevant
Disadvantage: user must know the formal parameter’s names.
Default Values:
procedure SORT(LIST : LIST_TYPE;
LENGTH : INTEGER := 100);
...
SORT(LIST => A);
In C++, which has no keyword parameters, the rules for default parameters are
necessarily different.
The default parameters must appear last, for parameters are positionally associated.
Once a default parameter is omitted in a call, all remaining formal parameters must
have default values.
float compute_pay(float income, float tax_rate,
int exemptions = 1)
An example call to the C++ compute_pay function is:
pay = compute_pay(20000.0, 0.15);
Procedures and Functions
There are two distinct categories of subprograms, procedures and functions.
Procedures: provide user-defined parameterized computation statements.
The computations are enacted by single call statements.
Procedures can produce results in the calling program unit by two methods:
o If there are variables that are not formal parameters but are still visible in both
the procedure and the calling program unit, the procedure can change them.
o If the subprogram has formal parameters that allow the transfer of data to the
caller, those parameters can be changed.
Functions: provide user-defined operators which are semantically modeled on
mathematical functions.
o If a function is a faithful model, it produces no side effects.
o It modifies neither its parameters nor any variables defined outside the
function.
The methods of Java, C++, and C# are syntactically similar to the functions of C.
Design Issues for Subprograms:
1. What parameter passing methods are provided?
2. Are parameter types checked?
3. Are local variables static or dynamic?
4. Can subprogram definitions appear in other subprogram definitions?
5. What is the referencing environment of a passed subprogram?
6. Can subprograms be overloaded?
7. Are subprograms allowed to be generic?
Generic Subprograms:
A programmer should not need to write four different sort subprograms to sort four
arrays that differ only in element type.
A generic or polymorphic subprogram takes parameters of different types on
different activations.
Overloaded subprograms provide a particular kind of polymorphism called ad hoc
polymorphism.
Parametric polymorphism is provided by a subprogram that takes a generic
parameter that is used in a type expression that describes the types of the
parameters of the subprogram.
Generic Functions in C++
Generic functions in C++ have the descriptive name of template functions.
The following is the C++ version of the generic sort subprogram.
template <class Type>
void generic_sort (Type list [ ], int len) {
int top, bottom;
Type temp;
for (top = 0, top < len –2; top ++)
for (bottom = top + 1; bottom < len – 1; bottom++)
if (list [top] > list [bottom]) {
temp = list [top];
list[top] = list[bottom];
} // end for bottom
} // end for generic
The instantiation of this template function is:
float flt_list [100];
…
generic_sort (flt_list, 100);
The subprogram call and return operations of a language are together called its subprogram
linkage
• General semantics of subprogram calls
Parameter passing methods
Stack-dynamic allocation of local variables
Save the execution status of calling program
Transfer of control and arrange for the return
If subprogram nesting is supported, access to nonlocal variables must be arranged
• General semantics of subprogram returns:
In mode and in out mode parameters must have their values returned
Deallocation of stack-dynamic locals
Restore the execution status
Return control to the caller
• Call Semantics
- Save the execution status of the caller
- Pass the parameters
- Pass the return address to the callee
- Transfer control to the callee
• Return Semantics:
If pass-by-value-result or out mode parameters are used, move the current values of those
parameters to their corresponding actual parameters
If it is a function, move the functional value to a place the caller can get it
Restore the execution status of the caller
Transfer control back to the caller
• Required storage:
Status information, parameters, return address, return value for functions
• Two separate parts: the actual code and the non-code part (local variables and data
that can change)
• The format, or layout, of the non-code part of an executing subprogram is called an
activation record
• An activation record instance is a concrete example of an activation record (the
collection of data for a particular subprogram activation)
• More complex activation record
The compiler must generate code to cause implicit allocation and deallocation of local
variables
Recursion must be supported (adds the possibility of multiple simultaneous activations of
a subprogram)
• The activation record format is static, but its size may be dynamic
• The dynamic link points to the top of an instance of the activation record of the caller
• An activation record instance is dynamically created when a subprogram is called
• Activation record instances reside on the run-time stack
• The Environment Pointer (EP) must be maintained by the run-time system. It always
points at the base of the activation record instance of the currently executing program
unit
• void sub(float total, int part)
• {
• int list[5];
• float sum;
• …
• }
void A(int x) {
int y;
...
C(y);
...
}
void B(float r) {
int s, t;
...
A(s);
...
}
void C(int q) {
...
}
void main() {
float p;
...
B(p);
...
}
The call and return actions require storage for the following:
•Status information about the caller
•Parameters
•Return address
•Return value for functions
•Temporaries used by the code of the subprograms
These, along with the local variables and the subprogram code, form the complete collection
of information a subprogram needs to execute and then return control to the caller.
The question now is the distribution of the call and return actions to the caller and the called.
For simple subprograms, the answer is obvious for most of the parts of the process. The last three
actions of a call clearly must be done by the caller. Saving the execution status of the caller could be
done by either. In the case of the return, the first, third, and fourth actions must be done by the called.
Once again, the restoration of the execution status of the caller could be done by either the caller or
the called. In general, the linkage actions of the called can occur at two different times, either at the
beginning of its execution or at the end. These are sometimes called the prologue and epilogue of the
subprogram linkage. In the case of a simple subprogram, all of the linkage actions of the callee occur
at the end of its execution, so there is no need for a prologue.
A simple subprogram consists of two separate parts: the actual code of the subprogram, which
is constant, and the local variables and data listed previously, which can change when the subprogram
is executed. In the case of simple subprograms, both of these parts have fixed sizes.
The format, or layout, of the noncode part of a subprogram is called an activation record,
because the data it describes are relevant only during the activation, or execution of the subprogram.
The form of an activation record is static. An activation record instance is a concrete example of an
activation record, a collection of data in the form of an activation record.
Because languages with simple subprograms do not support recursion, there can be only one
active version of a given subprogram at a time. Therefore, there can be only a single instance of the
activation record for a subprogram. One possible layout for activation records is shown in Figure
10.1. The saved execution status of the caller is omitted here and in the remainder of this chapter
because it is simple and not relevant to the discussion.
Figure 10.1 Local variables
An activation record for Parameters
simple subprograms
Return address
Figure 10.2 shows a program consisting of a main program and threesubprograms: A, B, and
C. Although the figure shows all the code segmentsseparated from all the activation record instances,
in some cases, the activationrecord instances are attached to their associated code segments.The
construction of the complete program shown in Figure 10.2 is not doneentirely by the compiler. In
fact, if the language allows independent compilation,the four program units—MAIN, A, B, and
C—may have been compiled on differentdays, or even in different years. At the time each unit is
compiled, the machinecode for it, along with a list of references to external subprograms, is written to
a file. The executable program shown in Figure 10.2 is put together by the linker, which is part of the
operating system. (Sometimes linkers are called loaders, linker/loaders, or link editors.) When the
linker is called for a main program, its first task is to find the files that contain the translated
subprograms referenced in that program and load them into memory. Then, the linker must set the
target addresses of all calls to those subprograms in the main program to the entry addresses of those
subprograms. The same must be done for all calls to subprograms in the loaded subprograms and all
calls to library subprograms. In the previous example, the linker was called for MAIN. The linker had
to find the machine code programs for A, B, and C, along with their activation record instances, and
load them into
STACK AND DYNAMIC LOCAL VARIABLES:
A variable is a bundle of six attributes: name, scope, address, lifetime, type, and value.
An attribute may be bound to a variable (or other program entity) at various times. Sebesta mentions:
language design time; language implementation time; compile time; link time; load time; and run
time.
We will just be concerned with a two-way distinction:
Static Binding
Static binding happens at compile time (including link and load time), and usually remains the
same throughout the run of the program.
Dynamic Binding
Dynamic binding happens during run time, and may change during run time.
Name
Most variables have a name. Names were discussed in the previous web page As an example of a
variable without a name, consider this interaction with the Java BeanShell:
bsh % string = "This is a string.";
bsh % set = new HashSet();
bsh % set.add(string);
bsh % print(string);
This is a string.
bsh % print(set);
[This is a string.]
There are two named variables, string and set. The variable string is bound to a word of memory that
contains a reference to a string object. This reference is string's value. The variable set is bound, as its
value, to a reference to an instance of the HashSet class, and that instance includes a word of memory
which contains a copy of the string reference bound, as a value, to string. That element of the HashSet
is as much a variable as string is, but it doesn't have a name.
On the other hand, more than one variable might have the same name. Consider the Java code,
for (int i = 0; i < 10; i++) {
a[i] = i;
}
for (int i = 0; i < 100; i++) {
squares[i] = i*i;
}
The two for loops each have a variable named i, yet they are different variables.
Also, when a subroutine, that has a local variable x, calls itself recursively, each instance of
the subroutine will have a separate variable named x. This issue of subroutine management will be
discussed in more detail later.
The two variables named i in the for loops are statically bound to their names at compile time.
The variables named x, in all recursive activations except the top one, are bound to their names
dynamically, during run time.
When using an interpreted language (such as bash, Erlang, Haskell, the Java BeanShell, Lisp,
Python, or Ruby), variables such as string and set, above, may be created and bound to their names
dynamically, at run time.
Scope
The scope of a variable is the amount of program within which the variable's name refers to it (as
opposed to another variable of the same name). If the "amount" of program is determined spatially,
that is static (sometimes called "lexical") scope. If it is determined temporally, that is dynamic scope.
To discuss scope, we need another 3-way distinction
Local variable
A local variable is a variable declared in the same program subunit (program, subprogram, function,
etc.) in which it is used.
Nonlocal variable
A nonlocal variable is a variable not declared in the same program subunit in which it is used, but not
available to every subunit in the program.
Global variable
A global variable is a variable not declared in the same program subunit in which it is used, and
available to every subunit in the program.
This discussion of scope also applies to names other than variable names, for example, names of
subprograms.
Python displays all the traditional characteristics of static scoping:
<timberlake:CSE305:1:29> cat scope.py
#! /util/bin/python
# Illustration of Nested Scopes
# Stuart C. Shapiro
# February 4, 2005
def A():
print x # global x
def B():
def C():
x = "C's x"
print x # local x
D()
def D():
print x # nonlocal x
x = "B's x"
print x # local x
A()
C()
x = "Global x"
B()
<timberlake:CSE305:1:30> scope.py
B's x
Global x
C's x
B's x
Static and dynamic scope may be clearly compared in Common Lisp and Emacs-Lisp.
Here's an interaction with Common Lisp:
cl-user(1): (setf x 1)
1
cl-user(2): (defun outer (x)
(inner))
outer
cl-user(3): (defun inner ()
x)
inner
cl-user(4): (outer 2)
1
cl-user(5): (inner)
1
Common Lisp's variables are statically scoped. (Although variables may be declared to be
dynamically scoped.) Since x is not local to the function inner, it refers to the variable with the same
name that has most recently been declared looking up the static spatial area of the program. There, the
most recent declaration of x is the global one implicitly declared in the setf expression. So the x of
inner is in the scope of the global x, and they refer to the same variable. However, the x of the
function outer is a formal parameter, and so is in different scope, and so refers to a different variable.
Now here's the apparently same interaction with Emacs-Lisp
(setf x 1)
1
(defun outer (x)
(inner))
outer
(defun inner ()
x)
inner
(outer 2)
2
(inner)
1
Emacs-Lisp is dynamically scoped (like pre-Common Lisp Lisps). Since x is not local to the function
inner, it refers to the variable with the same name that has most recently been declared looking up the
dynamic chain of function calls. When inner was called from outer, that would be outer's x, but when
inner was called from the top-level, that would be the top-level, global x, the one assigned by the setf.
The programming languages that descend from Algol 60 allow blocks where variables may be
declared, giving them smaller scopes than subprograms (methods). This Java for loop
Allocation
The process of taking "the memory cell to which a variable is bound ... from a pool of available
memory." [Sebesta, p. 221]
Deallocation
"The process of placing a memory cell that has been unbound from a variable back to the pool of
available memory." [Sebesta, p. 221]
Lifetime:
The time during which the variable is bound to a specific memory location." [Sebesta, p. 221]
Variable categories by lifetime and memory location:
Static Variables
Static variables are bound to memory cells before execution begins, and remain so until program
termination. That is, the lifetime of a static variable is the entire running time of the program.
Fortran77 and earlier versions of Fortran use only static variables. One implication is that recursion is
not possible. This can be demonstrated by a subroutine that keeps count of the number of times it has
been called:
Program Count
C Demonstration of static variables in Fortran.
print *, 'Starting Test Program'
Call CountingRoutine()
Call CountingRoutine()
Call CountingRoutine()
Call CountingRoutine()
Call CountingRoutine()
End
Subroutine CountingRoutine ()
C Keeps track of the number of times it has been called
C and prints that count each time.
Integer count
Data count/0/
count = count + 1
Print *, 'count = ', count
Return
End
-------------------------------------------------------
<cirrus:Programs:1:152> f77 -o count.fout count.f
NOTICE: Invoking /opt/SUNWspro/bin/f90 -f77 -ftrap=%none -o count.fout count.f
count.f:
MAIN count:
countingroutine:
<cirrus:Programs:1:153> count.fout
Starting Test Program
count = 1
count = 2
count = 3
count = 4
count = 5
Notice that, even though count is a local variable of CountingRoutine, its lifetime exceeds the running
time of CountingRoutine.
C can achieve this effect by declaring a variable to be static:
/*
* Count
* Stuart C. Shapiro
*
* This program demonstrates static variables
* with a function that counts the number of times that it is called.
*
*/
#include <stdio.h>
void counting_function() {
/* Prints the number of times that it has been called. */
static int count = 0;
printf("count = %d\n", ++count);
}
int main() {
/* Demonstrates counting_function by calling it 5 times. */
counting_function(); counting_function(); counting_function();
counting_function(); counting_function();
return 0;
}
-------------------------------------------------------
<cirrus:Programs:1:140> gcc -Wall count.c
<cirrus:Programs:1:141> ./a.out
count = 1
count = 2
count = 3
count = 4
count = 5
This can also be done in Java:
/**
* Counter.java
*
*
* Created: Mon Sep 15 16:47:41 2003
*
* @author Stuart C. Shapiro
*/
public class Counter {
public static int count;
Heap memory must be used for any dynamically allocated object or data structure that can be
allocated in a subroutine and then have a pointer (reference) to it assigned to a variable which is
outside the dynamic scope of the subroutine, so that its lifetime must extend to the time after the
subroutine terminates and deallocates its stack memory. Consider the Java program,
import java.util.*;
public class HeapDemo {
public static HashSet singleton(Object obj) {
HashSet set;
set = new HashSet();
set.add(obj);
return set;
}
public static void main (String[] args) {
HashSet myset;
myset = singleton("element");
System.out.println(myset);
myset = singleton("another");
System.out.println(myset);
}
}// HeapDemo
-------------------------------------------------------
<cirrus:Programs:2:101> javac HeapDemo.java
<cirrus:Programs:2:102> java HeapDemo
[element]
[another]
Although the memory for the HashSet is allocated in the singleton method, it cannot be allocated on
the stack, because it must survive the termination of singleton.
Heap memory should be returned to the heap when it is no longer needed (about to be no longer
reachable from any named variable), like the HashSet [element] was no longer needed (reachable)
after myset was reassigned above. Otherwise, a program that runs long enough might use up the heap
and abnormally terminate. (The process of heap memory becoming increasingly unreachable but
unavailable for reallocation is called "memory leakage".) In C and C++, heap memory must be
explicitly deallocated with the operator free(p) or delete p, respectively, where p is a pointer to the
object or data structure whose memory is no longer needed.
Requiring the programmer to explicitly deallocate heap memory allows for these programmer
mistakes:
For example, let ClassA be a class in which methodA() is defined, let ClassA1, ClassA2, and ClassA3
extend ClassA and specialize methodA(), and let varA be a variable declared to be of type ClassA.
UML diagram by Dan Schlegel
The expression varA.methodA() is syntactically legal regardless of whether the current value of varA
is an object of type ClassA, ClassA1, ClassA2, or ClassA3, but the value of varA.getClass() will
determine the specific version of methodA() used.
On the other hand, if varB is declared to be of some superclass of ClassA for which methodA() is not
defined, varB.methodA() will produce a compiler error, unless a cast is used, such as
((ClassA2)varB).methodA().
Languages with typed values include Common Lisp, Haskell, Java, JavaScript, Ruby and Python.
Usually, in languages with typed values, the programmer may write code to test the types of values
and give reasonable error messages if they are not what was expected, but, without doing this, a type
error might only be caught many levels of function calls below where the error actually occurred.
Static type-checking generally makes program debugging easier.
Value
The value of a variable, also called its r-value, is the contents of the memory cell at the variable's
address.
This might lead to confusion with pointer or reference variables.
For example, after the assignment set = new HashSet();, above, should we say that the value of set is a
HashSet or a reference to a HashSet? The latter is the more careful way to speak; the former is more
informal. We will discuss this more when we discuss pointer types in Chapter 6.
Now we will discuss when a variable is first bound to a value, and whether its value binding is
allowed to change.
A formal Parameter is initially bound to a value when its subprogram is called. We will discuss this in
Chapter 9.
The remainder of this discussion will concern variables that are not parameters---global and local
variables.
When a variable is bound to an address (memory cell), its value might be whatever bit settings were
left in that cell, interpreted according to the variable's type, for example this C program:
#include <stdio.h>
int main() {
int x;
double y;
printf("x = %d y = %e\n", x, y);
return 0;
}
-------------------------------------------------------
<cirrus:Programs:1:103> gcc -Wall leftover.c -o leftover.out
<cirrus:Programs:1:104> ./leftover.out
x = -4264396 y = 8.485876e-314
or it might be initialized, either to a default value, or to an value explicitly specified in a declaration.
Variable Initialization might be done at compile-time, or at run-time.
If variable initialization is done at compile-time, the initialization expression will usually be limited.
NESTED SUBPROGRAMS:
•Some non-C-based static-scoped languages (e.g., Fortran 95, Ada, JavaScript)
use stack-dynamic local variables and allow subprograms to be nested
•All variables that can be non-locally accessed reside in some activation record
instance of enclosing scopes in the stack
•A reference to a non-locally variable in a static-scoped language with nested
subprograms requires a two step access process:
1.Find the correct activation record instance
2.Determine the correct offset within that activation record instance
I
The static link in an activation record instance for subprogram A points to the bottom of the
activation record instances of A's static parent
Implementing Blocks
1.Treat blocks as parameter-less subprograms that are always called from the same
location.
– Every block has an activation record; an instance is created every time the block is
executed
void main(){
int x, y, z;
while ( … ){
int a, b, c;
……
while ( …){
int d, e;
…… }
}
while ( … ) {
int f, g;
…… } ……}
2. Since the maximum storage
required for a block can be
statically determined, this amount
of space can be allocated after the
local variables in the activation record
Implementing Dynamic Scoping:
Scoping itself is how you search for a variable with a given name. A variable has
a scope which is the whole area in which that variable can be accessed by name. If
there is a reference to a variable "a" then how does the compiler or interpreter find
it?
In lexical scoping (and if you're the interpreter), you search in the local function
(the function which is running now), then you search in the function (or scope) in
which that function was defined, then you search in the function (scope) in
which that function was defined, and so forth. "Lexical" here refers to text, in that
you can find out what variable is being referred to by looking at the nesting of
scopes in the program text.
In dynamic scoping, by contrast, you search in the local function first, then you
search in the function that called the local function, then you search in the function
that called that function, and so on, up the call stack. "Dynamic" refers
to change, in that the call stack can be different every time a given function is
called, and so the function might hit different variables depending on where it is
called from.
Dynamic scoping is useful as a substitute for globally scoped variables. A function
can say "let current_numeric_base = 16; call other functions;" and the other
functions will all print in hexadecimal. Then when they return, and the base-setting
function returns, the base will return to whatever it was.
It can also have a use similar to a ContextObject, as in JavaServlets and EJB APIs,
holding state for a series of subroutines.
The two most popular methods for
implementing DynamicScoping are DeepBinding and ShallowBinding.
I find that dynamic scoping makes spaghetti code out of modular code. Everything
depends on order of execution, and it quickly becomes impossible to understand
what's going on without a debugger open. Just an opinion, however... --Anthony
Lexical scoping is easier to program with, and is necessary for safe closures,
callbacks, CallWithCurrentContinuation, HigherOrderFunctions etc.
Consider variables like *standard-output*, *print-base*, *break-on-signals*, etc...
Dynamic scoping has the potentiality of turning code into a mess, but, hey, all
powerful constructs are like that. It's certainly a much less dangerous feature than,
say, call-cc, which is so powerful it prevents a reasonable implementation of
UNWIND-PROTECT. -- AlainPicard
Hmm. I was under the impression that 'dynamic-wind' solved these issues,
although it is a fairly recent addition if I'm not mistaken. -- WilliamUnderwood
Richard Stallman makes a good case for dynamic scoping in his paper on
emacs: http://www.gnu.org/software/emacs/emacs-paper.html#SEC17
Incidentally, emacs ships with macros that support lexical scoping in emacs lisp
(emacs lisp variabels are dynamically scoped by default, though lexical scoping
turned on on a per-file basis is supported since version 24) This would suggest that
dynamic scoping is the more general of the two - or is it possible to implement
dynamic scoping in a lexically scoped language in a similar way?
Possibly. A trivial, but non-threasafe, implementation follows in pseudocode
(because I don't know lisp) which is based on lisp and perl six and should probably
be rewritten. Also, since Assembly has neither, yet is used to implement both
several times, your argument may be irrelevant.
define-macro-variable dynamic-names = []
defmacro "defdyn" (a : Name, (optional "=", value = NULL))
defvar code = q:code{
defvar #{a}-dynamic-list = ["value"];
}
dynamic-names += a
(type-of a) = Dynamic[(err (type-of value) (infer-type-of-slot a))]
return code
defmacro [X] (a : Slot[Dynamic[X]])
return {(head #{a}-dynamic-list)}
defmacro "enter-dynamic-scope"
return (reduce dynamic-names q:code{} (lambda (a : Code, b : Name)
a + q:code { #{b}-dynamic-list = (cons (head #{b}-dynamic-list)
#{b}-dynamic-list);}))
defmacro "leave-dynamic-scope"
return (reduce dynamic-names q:code{} (lambda (a : Code, b : Name)
a + q:code { #{b}-dynamic-list = (tail #{b}-dynamic-list)}))