Essentials of programming languages friedman pdf
Software Images icon An illustration of two photographs. Images Donate icon An illustration of a heart shape Donate Ellipses icon An illustration of text ellipses. Essentials of programming languages Item Preview. EMBED for wordpress. Want more? The right-hand side of the new expression will be the translation of the right-hand side of the old expression.
This is in the same scope as the original, so we translate it in the same static environment senv. The body of the new expression will be the translation of the body of the old expression. But the body now lies in a new scope, with the additional bound variable var.
So we translate the body in the static environment extend-senv var senv. Every proc-exp is replaced by a nameless-proc-exp, with the body translated with respect to the new scope, represented by the static envi- ronment extend-senv var senv.
The procedure translation-of-program runs translation-of in a suitable initial static environment. There are only two things in our interpreter that use environments: procedures and value-of.
Most cases are the same as in the earlier interpreters except that where we used env we now use nameless-env. We do have new cases, however, that correspond to var-exp, let-exp, and proc-exp, which we replace by cases for nameless-var-exp, nameless-let-exp, and nameless-proc-exp, respectively.
A nameless-var-exp gets looked up in the environment. A nameless-let-exp evaluates its right-hand side exp1, and then evalutes its body in an environment extended by the value of the right-hand side.
This is just what an ordinary let does, but without the variables. A nameless-proc produces a proc, which is then applied by apply-procedure.
Do this by modifying the context argument to translation-of so that it keeps track of not only the name of each bound variable, but also whether it was bound by letrec or not. For a reference to a variable that was bound by a letrec, generate a new kind of reference, called a nameless-letrec-var-exp. You can then continue to use the nameless environment representation above, and the inter- preter can do the right thing with a nameless-letrec-var-exp.
Do this using a nameless version of the ribcage representation of environ- ments exercise 2. For this representation, the lexical address will consist of two nonnegative integers: the lexical depth, to indicate the number of contours crossed, as before; and a position, to indicate the position of the variable in the declaration. For this, you will need to translate the body of the procedure not extend-senv var senv , but in a new static environment that tells exactly where each variable will be kept in the trimmed representation.
Therefore we could avoid looking up f in the environment entirely. Therefore the procedure built by the expression proc y - y,x is never used.
Modify the translator so that such a procedure is never constructed. An effect is global: it is seen by the entire computation. An effect affects the entire computation pun intended. We will be concerned primarily with a single effect: assignment to a loca- tion in memory. How does assignment differ from binding? As we have seen, binding is local, but variable assignment is potentially global.
It is about the sharing of values between otherwise unrelated portions of the com- putation. Two procedures can share information if they both know about the same location in memory.
A single procedure can share information with a future invocation of itself by leaving the information in a known location. For historical reasons, we call this the store.
The storable values in a language are typically, but not always, the same as the expressed values of the language. This choice is part of the design of a language. A data structure that represents a location is called a reference. A location is a place in memory where a value can be stored, and a reference is a data structure that refers to that place.
Similarly, a reference denotes a location, and the location contains some data. Analogously, expressed values, such as the values of the right-hand side expressions of assignment statements, are known as R-values. We consider two designs for a language with a store. We call these designs explicit references and implicit references. We leave the binding structures of the language unchanged, but we add three new operations to create and use references.
Below are two procedures, even and odd. They each take an argument, which they ignore, and return 1 or 0 depending on whether the contents of the location x is even or odd. They communicate not by passing data explicitly, but by changing the contents of the variable they share. This program determines whether or not 13 is odd, and therefore returns 1. The procedures even and odd do not refer to their arguments; instead they look at the contents of the location to which x is bound.
A begin expression evaluates its subexpressions in order and returns the value of the last one. We pass a dummy argument to even and odd to stay within the frame- work of our unary language; if we had procedures of any number of argu- ments exercise 3. This style of communication is convenient when two procedures might share many quantities; one needs to assign only to the few quantities that change from one call to the next. Similarly, one procedure might call another procedure not directly but through a long chain of procedure calls.
They could communicate data directly through a shared variable, without the intermediate procedures needing to know about it. Thus communication through a shared variable can be a kind of information hiding. Another use of assignment is to create hidden state through the use of private variables. Here is an example. We can think of this as the different invocations of g sharing information with each other.
This technique is used by the Scheme procedure gensym to create unique symbols. Exercise 4. This means we can store a reference in a location. To specify these effects, we need to describe what store should be used for each evaluation and how each evaluation can modify the store. These are evaluated in order and the value of the last is returned. It extends the resulting store by allocating a new location l and puts the value val of its argument in that location. Then it returns a reference to a location l that is new.
The value of that argument should be a reference to a location l. The setref-exp then updates the resulting store by putting the value val of the second argument in location l. What should a setref-exp return? It could return anything. Because we are not interested in the value returned by a setref-exp, we say that this expression is executed for effect, rather than for its value.
Thus we model an effect as a Scheme effect. Instead, we keep the state in a single global variable, to which all the procedures of the implemen- tation have access. By using a sin- gle global variable, we also use as little as possible of our understanding of Scheme effects.
We still have to choose how to model the store as a Scheme value. We choose the simplest possible model: we represent the store as a list of expressed values, and a reference is a number that denotes a position in the list.
A new reference is allocated by appending a new value to the list; and updating the store is modeled by copying over as much of the list as neces- sary. Ordinary memory operations require approximately constant time, but in our representation these opera- tions require time proportional to the size of the store.
We add a new variant, ref-val, to the data type for expressed values, and we modify value-of-program to initialize the store before each eval- uation. We can instrument our system by adding some procedures that convert environments, procedures, and stores to a more readable form, and we can instrument our system by printing messages at key points in the code. We also use procedures that convert environments, procedures, and stores to a more readable form.
The resulting logs give a detailed picture of our system in action. This trace shows, among other things, that the arguments to the subtraction are evaluated from left to right. Initially set to a dummy value. What is lost by using this representation? In particular, it depends on us knowing when these effects take place in a Scheme program. We call this a store-passing interpreter.
Every procedure that might modify the store returns not just its usual value but also a new store. These are packaged in a data type called answer. Most programming languages take common patterns of allocation, deref- erencing, and mutation, and package them up as part of the language.
Then the programmer need not worry about when to perform these operations, because they are built into the language. In this design, every variable denotes a reference. Denoted values are ref- erences to locations that contain expressed values. References are no longer expressed values. They exist only as the bindings of variables. Figure 4. The contents of a location can be changed by a set expression. In this design, we say that variables are mutable, meaning changeable. Most programming languages, including Scheme, use some variation on this design.
As with setref, the value returned by a set expression is arbitrary. We choose to have it return the expressed value The rule for let-exp var exp1 body is similar. The right-hand side exp1 is evaluated, and the value of the let expression is the value of the body, evaluated in an environment where the variable var is bound to a new location containing the value of exp1. In value-of, we dereference at each var-exp, just like the rules say var-exp var deref apply-env env var and we write the obvious code for a assign-exp assign-exp var exp1 begin setref!
New locations should be allocated at every new binding. There are exactly four places in the language where new bindings are created: in the initial environment, in a let, in a procedure call, and in a letrec.
For let, we change the corresponding line in value-of to allocate a new location containing the value, and to bind the variable to a reference to that location. Since we are using multideclaration letrec exercise 3. The procedure location takes a variable and a list of variables and returns either the posi- tion of the variable in the list, or f if it is not present.
This is like exercise 3. Dereferencing occurs implicitly when the denoted value is a refer- ence. Very often such an assignment should only be temporary, lasting for the execution of a pro- cedure call.
The variable var must already be bound. Be sure to Follow the Grammar by writing separate procedures to handle programs, statements, and expressions.
Semantics A program is a statement. A statement does not return a value, but acts by modifying the store and by printing. Assignment statements work in the usual way. A print statement evaluates its actual parameter and prints the result.
The if statement works in the usual way. The scope of these bindings is the body. Example 4 illustrates the interaction between statements and expressions. A pro- cedure value is created and stored in the variable f. In the last line, this procedure is applied to the actual parameters 4 and x; since x is bound to a reference, it is dereferenced to obtain 3. This statement reads a nonnegative integer from the input and stores it in the given variable.
Add do-while statements to the language of exercise 4. In your solution, does the scope of a variable include the initializer for variables declared later in the same block statement? Consider restricting the language so that the variable declarations in a block are followed by the procedure declarations. In our usage a subroutine is like a procedure, except that it does not return a value and its body is a statement, rather than an expression.
Also, add subroutine calls as a new kind of statement and extend the syntax of blocks so that they may be used to declare both procedures and subroutines. How does this affect the denoted and expressed values? What happens if a procedure is referenced in a subroutine call, or vice versa? We arbitrarily choose to make setleft return 82 and setright return The two locations in a pair are indepen- dently assignable, but they are not independently allocated.
We know that they will be allocated together: if the left part of a pair is one location, then the right part is in the next location. So we can instead represent the pair by a reference to its left. Nothing else need change. However, a pointer does not by itself identify an area of memory unless it is supplemented by information about the length of the area see exercise 4. The lack of length information is a source of classic security errors, such as out-of-bounds array writes. Introduce new operators newarray, arrayref, and arrayset that create, dereference, and update arrays.
What should be the result of the following program? Make the array indices zero-based, so an array of size 2 has indices 0 and 1.
Your procedure should work in constant time. Make sure that arrayref and arrayset check to make sure that their indices are within the length of the array. Where does that value come from? It must be passed from the actual parameter in the procedure call.
This is the most commonly used form of parameter-passing. In this section, we explore some alternative parameter-passing mecha- nisms. With call-by-value, when a procedure assigns a new value to one of its parameters, this cannot possibly be seen by its caller. Of course, if the param- eter passed to the caller contains a mutable pair, as in section 4. But the effect of a set is not. Though this isolation between the caller and callee is generally desirable, there are times when it is valuable to allow a procedure to be passed loca- tions with the expectation that they will be assigned by the procedure.
This parameter- passing mechanism is called call-by-reference. The formal parameter of the procedure is then bound to this location. If the operand is some other kind of expression, then the formal parameter is bound to a new location containing the value of the operand, just as in call-by-value. Using call-by-reference in the above example, the assignment of 4 to x has the effect of assigning 4 to a, so the entire expression would return 4, not 3.
When a call-by-reference procedure is called and the actual parameter is a variable, what is passed is the location of that variable, rather than the con- tents of that location, as in call-by-value.
Similarly, when f is called, x becomes bound to that same location. Hence the value of the entire expression is A typical use of call-by-reference is to return multiple values. A procedure can return one value in the normal way and assign others to parameters that are passed by reference.
If this program were run with our existing call-by-value interpreter, however, it would return , because the assignments inside the swap procedure then have no effect on variables a and b.
Under call-by-value, a new location is created for every evaluation of an operand; under call-by-reference, a new location is created for every evaluation of an operand other than a variable.
This is easy to implement. The function apply-procedure must change, because it is no longer true that a new location is allocated for every proce- dure call. That responsibility must be moved upstream, to the call-exp line in value-of, which will have the information to make that decision. If it is, then the reference that the variable denotes is returned and then passed to the procedure by apply-procedure.
Otherwise, the operand is evaluated, and a reference to a new location containing that value is returned.
More than one call-by-reference parameter may refer to the same location, as in the following program. This phenomenon is known as variable aliasing. Here x and y are aliases names for the same location. This allows us to write familiar programs such as swap within our call-by-value lan- guage. What should be the value of this expression? What are the expressed and denoted values of this language? If an operand is an array reference, then the location referred to, rather than its contents, is passed to the called procedure.
This allows, for example, a swap procedure to be used in commonly occurring situations in which the values in two array elements are to be exchanged. Add array operators like those of exercise 4. What should happen in the case of swap arrayref a arrayref a i arrayref a j? In call-by- value-result, the actual parameter must be a variable. The procedure body is then executed normally. When the procedure body returns, however, the value in the new reference is copied back into the reference denoted by the actual parameter.
Implement call-by- value-result and write a program that produces different answers using call-by-value- result and call-by-reference. We now turn to a very different form of parameter passing, called lazy evaluation.
Under lazy evaluation, an operand in a procedure call is not evaluated until it is needed by the proce- dure body. If the body never refers to the parameter, then there is no need to evaluate it. This can potentially avoid non-termination. Under any of the mechanisms considered so far, this program will fail to terminate.
Under lazy evaluation, however, this program will return 11, because the operand infinite-loop 1 is never evaluated. We now modify our language to use lazy evaluation. Under lazy evalu- ation, we do not evaluate an operand expression until it is needed. There- fore we associate the bound variable of a procedure with an unevaluated operand. When the procedure body needs the value of its bound variable, the associated operand is evaluated.
We sometimes say that the operand is frozen when it is passed unevaluated to the procedure, and that it is thawed when the procedure evaluates it. Of course we will also have to include the environment in which that pro- cedure is to be evaluated. To do this, we introduce a new data type of thunks. A thunk consists of an expression and an environment. When a procedure needs to use the value of its bound variable, it will evalu- ate the associated thunk. We therefore let our denoted values be references to locations containing either expressed values or thunks.
Otherwise, we pass a reference to a new location containing a thunk for the unevaluated argument. If the location contains an expressed value, then that value is returned as the value of the var-exp. If it instead contains a thunk, then the thunk is evaluated, and that value is returned.
This design is called call by name. This arrangement is called call by need. An attraction of lazy evaluation in all its forms is that in the absence of effects, it supports reasoning about programs in a particularly simple way.
The effect of a procedure call can be modeled by replacing the call with the body of the procedure, with every reference to a formal parameter in the body replaced by the corresponding operand. If there are no effects, though, this is not a problem.
Thus lazy evaluation is popular in functional programming languages those with no effects , and rarely found elsewhere. Does the original program in exercise 3. What happens if the program below is run under call-by-value? Construct an example in which call-by-name and call-by-need give different answers. Here we will do the same for the control context in which each portion of a program is executed.
We will introduce the con- cept of a continuation as an abstraction of the control context, and we will write interpreters that take a continuation as an argument, thus making the control context explicit. Each call of fact is made with a promise that the value returned will be multiplied by the value of n at the time of the call.
Thus fact is invoked in larger and larger control contexts as the calculation proceeds. Compare this behavior to that of the following procedures. No promise is made to do anything with the returned value other than to return it as the result of the call to fact-iter-acc.
We call this a tail call. Thus each step in the derivation above has the form fact-iter-acc n a. When a procedure such as fact executes, additional control information is recorded with each recursive call, and this information is retained until the call returns.
Such a process is said to exhibit recursive control behavior. By contrast, no additional control information need be recorded when fact-iter-acc calls itself. In such cases the system does not need an ever-increasing amount of memory for control contexts as the depth of recursion the num- ber of recursive calls without corresponding returns increases.
A process that uses a bounded amount of memory for control information is said to exhibit iterative control behavior. Why do these programs exhibit different control behavior? In this chapter we will learn how to track and manipulate control contexts.
Our central tool will be the data type of continuations. Continuations are an abstraction of the notion of control context, much as environments are an abstraction of data contexts. We will explore continuations by writing an interpreter that explicitly passes a continuation parameter, just as our pre- vious interpreters explicitly passed an environment parameter.
Once we do this for the simple cases, we can see how to add to our language facilities that manipulate control contexts in more complicated ways, such as exceptions and threads. In chapter 6 we show how the same techniques we used to transform the interpreter can be applied to any program.
We say that a program trans- formed in this manner is in continuation-passing style. Chapter 6 also shows several other important uses of continuations.
This new parameter, the continuation, is intended to be an abstraction of the control context in which each expression is evaluated. Our goal is to rewrite the interpreter so that no call to value-of builds control context. When the control context needs to grow, we extend the continuation parameter, much as we extended the environment in the inter- preters of chapter 3 as the program builds up data context.
By making the control context explicit, we can see how it grows and shrinks, and later, in sections 5.
Now, we know that an environment is a representation of a function from symbols to denoted values. What does a continuation represent? The con- tinuation of an expression represents a procedure that takes the result of the expression and completes the computation.
What kind of continuation-builders will be included in the interface? We will discover these continuation-builders as we analyze the interpreter. To begin, we will need a continuation-builder for the context that says there is nothing more to do with the value of the computation. Because end-cont prints out a mes- sage, we can tell how many times it has been invoked.
In a correct completed computation, it should be invoked exactly once. We consider each of the alterna- tives in value-of in turn. It is easy to check that if the program consists of an expression of one of these forms, the value of the expression will be supplied to end-cont through apply-cont. The behavior of letrec is almost as simple: it creates a new environment without calling value-of, and then evaluates the body in the new envi- ronment. The value of the body becomes the value of the entire expression.
That means that the body is performed in the same control context as the entire expression. Therefore the value of the body should be returned to the continuation of the entire expression. Let us next consider a zero? In a zero? So we evaluate the argument in a new continuation that will look at the returned value and do the right thing. The right-hand side of a let is in operand position, because let var exp1 exp2 is equivalent to lambda var exp2 exp1.
As before, if we ran this code, the end-of-computation message would appear twice: once in the middle of the computation and once at the real end. His use of these techniques in a reconstruction of existing programming languages and in the design of new ones allows programmers and would-be programmers to see why existing languages are structured the way they are and how new languages can be built using variations on standard themes. The text is unique in its tutorial presentation of higher-order lambda calculus and intuitionistic type theory.
The latter in particular reveals that a programming language is a logic in which its typing system defines the propositions of the logic and its well-typed programs constitute the proofs of the propositions. The Structure of Typed Programming Languages is designed for use in a first or second course on principles of programming languages.
It assumes a basic knowledge of programming languages and mathematics equivalent to a course based on books such as Friedman, Wand, and Haynes': Essentials of Programming Languages.
As Schmidt covers both the syntax and the semantics of programming languages, his text provides a perfect precursor to a more formal presentation of programming language semantics such as Gunter's Semantics of Programming Languages. The book is appropriate for self-study or as a text for a course in programming in computational science. Readers will benefit from the author's tips, which provide insight and suggestions on small and large points.
He also provides more than exercises from novice through to advanced level with all of the solutions available online. Social responsibility Did you know that since editiom, Biblio has used its profits to build 12 public libraries in rural villages of South America?
Try adding this search to your want list. Eopk with confidence, excellent customer service! Did you know that sinceBiblio has used its profits to build 12 public libraries in rural villages of South America? Bookseller Completion Rate This reflects the percentage of orders the seller has received and filled. Are you a frequent reader or book collector? Add to want list. Light shelf wear on cover. Sign In Register Help Cart. Shows some signs of wear, and may have some markings on the inside.
If for any reason your order is not available to ship, you will not be charged. Search Results Results 1 -9 of 9. Find Rare Books Book Value.
0コメント