To save content items to your account,
please confirm that you agree to abide by our usage policies.
If this is the first time you use this feature, you will be asked to authorise Cambridge Core to connect with your account.
Find out more about saving content to .
To save content items to your Kindle, first ensure no-reply@cambridge.org
is added to your Approved Personal Document E-mail List under your Personal Document Settings
on the Manage Your Content and Devices page of your Amazon account. Then enter the ‘name’ part
of your Kindle email address below.
Find out more about saving to your Kindle.
Note you can select to save to either the @free.kindle.com or @kindle.com variations.
‘@free.kindle.com’ emails are free but can only be saved to your device when it is connected to wi-fi.
‘@kindle.com’ emails can be delivered even when you are not connected to wi-fi, but note that service fees apply.
A symbol is an atomic datum with no internal structure. Whereas a variable is given meaning by substitution, a symbol is given meaning by a family of operations indexed by symbols. A symbol is therefore just a name, or index, for an instance of a family of operations. Many different interpretations may be given to symbols according to the operations we choose to consider, giving rise to concepts such as fluid binding, dynamic classification, mutable storage, and communication channels. With each symbol is associated a type whose interpretation depends on the particular application. The type of a symbol influences the type of its associated operations under each interpretation. For example, in the case of mutable storage, the type of symbol constrains the contents of the cell named by that symbol to values of that type. It is important to bear in mind that a symbol is not a value of its associated type, but only a constraint on how that symbol may be interpreted by the operations associated with it.
In this chapter we consider two constructs for computing with symbols. The first is a means of declaring new symbols for use within a specified scope. The expression v a:ρ in e introduces a “new” symbol a with associated type ρ for use within e. The declared symbol a is new in the sense that it is bound by the declaration within e, and so may be renamed at will to ensure that it differs from any finite set of active symbols.
The semantics of many control constructs (such as exceptions and coroutines) can be expressed in terms of reified control stacks, a representation of a control stack as an ordinary value. This is achieved by allowing a stack to be passed as a value within a program and to be restored at a later point, even if control has long since returned past the point of reification. Reified control stacks of this kind are called continuations; they are values that can be passed and returned at will in a computation. Continuations never “expire,” and it is always sensible to reinstate a continuation without compromising safety. Thus continuations support unlimited “time travel”—we can go back to a previous point in the computation and then return to some point in its future, at will.
Why are continuations useful? Fundamentally, they are representations of the control state of a computation at a given point in time. Using continuations we can “checkpoint” the control state of a program, save it in a data structure, and return to it later. In fact, this is precisely what is necessary to implement threads (concurrently executing programs)—the thread scheduler must be able to checkpoint a program and save it for later execution, perhaps after a pending event occurs or another thread yields the processor.
Lazy evaluation refers to a variety of concepts that seek to defer evaluation of an expression until it is definitely required and to share the results of any such evaluation among all instances of a single deferred computation. The net result is that a computation is performed at most once among all of its instances. Laziness manifests itself in a number of ways.
One form of laziness is the by-need evaluation strategy for function application. Recall from Chapter 8 that the by-name evaluation order passes the argument to a function in unevaluated form so that it is evaluated only if it is actually used. But because the argument is replicated by substitution, it may be evaluated more than once. By-need evaluation ensures that the argument to a function is evaluated at most once by ensuring that all copies of an argument share the result of evaluating any one copy.
Another form of laziness is the concept of a lazy data structure. As we have seen in Chapters 11, 12, and 16, we may choose to defer evaluation of the components of a data structure until they are actually required rather than when the data structure is created. But if a component is required more than once, then the same computation will, without further provision, be repeated on each use. To avoid this, the deferred portions of a data structure are shared so an access to one will propagate its result to all occurrences of the same computation.
In this chapter we integrate concurrency into the framework of Modernized Algol described in Chapter 35. The resulting language, called Concurrent Algol, ℒ{nat cmd ⇀∥}, illustrates the integration of the mechanisms of the process calculus described in Chapter 41 into a practical programming language. To avoid distracting complications, we drop assignables from Modernized Algol entirely. (There is no loss of generality, however, because free assignables are definable in Concurrent Algol using processes as cells.)
The process calculus described in Chapter 41 is intended as a self-standing model of concurrent computation. When viewed in the context of a programming language, however, it is possible to streamline the machinery to take full advantage of types that are in any case required for other purposes. In particular, the concept of a channel, which features prominently in Chapter 41, may be identified with the concept of a dynamic class as described in Chapter 34. More precisely, we take broadcast communication of dynamically classified values as the basic synchronization mechanism of the language. Being dynamically classified, messages consist of a payload tagged with a class, or channel. The type of the channel determines the type of the payload. Importantly, only those processes that have access to the channel may decode the message; all others must treat it as inscrutable data that may be passed around but not examined. In this way we can model not only the mechanisms described in Chapter 41, but also formulate an abstract account of encryption and decryption in a network using the methods described in Chapter 41.