The pragmatics of a programming language includes issues such as ease of implementation, efficiency in application, and programming methodology. -- Slonneger & Kurtz
Keywords and phrases: strict, non-strict, eager evaluation, lazy evaluation, normal-order evaluation, binding time, passing by value, passing by reference, passing by name, passing by value, passing by result, passing by value-result, aliasing
Treating procedure and function abstractions as first-class values is another potential cause of dangling references. (Watt)
A heap variable is one that can be created and deleted at any time. Heap variables are anonymous and are accessed through pointers. A heap is a block of storage within which pieces are allocated and freed in some relatively unstructured mannner.
Initially the elements of the heap are linked together in some fashion to form a free-space list. The creation of a heap variable is requested by an operation called an allocator which returns a pointer to the newly created variable. To allocate an element, the first element on the list is removed from the list and a pointer to it is returned to the operation requesting the storage.
The heap variable's lifetime extends from the time it is created until it is no longer accessable.
Often there is an operation called a deallocator which forcibly deletes a given heap variable. When an element is deallocated (freed), it is simply linked back in at the head of the free-space list. If all references to heap variable are destroyed, the heap variable is inaccessable and becomes garbage. When a variable becomes garbage, its memory space is unusable by other variables since a means of referencing it must exist in order to return the space to the free-space list.
When deallocation is under the control of the programmer, it is a potential source of problems. If a programmer deallocates a variable, any remaining pointers to the deleted heap variable become dangling references.
Garbage and dangling references are potentially troublesome for the programmer. If garbage accumulates, available storage is gradually reduced until the program may be unable to continue for lack of known free space (this is also called a memory leak). If a program attempts to modify through a dangling reference a structure that has been deallocated (destroyed), the contents of an element of free-space may be modified. This may cause the remainder of the free-space to become garbage or a portion of the program to become linked to free-space. The deallocated space could be reallocated to some other structure resulting in similar problems.
The problem of dangling references can be eliminated.
One solution is to restrict assignment so that references to local variables may not be assigned to variables with a longer lifetime. This restriction may require runtime checks and sometimes restrict the programmer.
Another solution is to maintain reference counts with each heap variable. An integer called the reference count is associated with each heap element. The reference count indicates the number of pointers to the element that exist. Initially the count is set to 1. Each time a pointer to the element is created the reference count is increased and each time a pointer to the element is destroyed the reference count is decreased. Its space is not deallocated until the reference count reaches zero. The method of reference counting results in substantial overhead in time and space.
Another solution is to provide garbage collection. The basic idea is to allow garbage to be generated in order to avoid dangling references. When the free-space list is exhausted and more storage is needed, computation is suspended and a special procedure called a garbage collector is started which identifies garbage and returns it to the free-space list.
There are two stages to garbage collection a marking phase and a collecting phase.
This unuseable space may be reclamed by a garbage collector. A heap variable is alive as long as any reference to it exists.
Coroutines are used in discrete simulation languages and, for some problems, provide a control structure that is more natural than the usual hierarchy of subprogram calls.
Coroutines may be thought of as subprograms which are not required to terminate before returning to the calling routine. At a later point the calling program may ``resume'' execution of the coroutine at the point from which execution was suspended. Coroutines then appear as equals with control passing from one to the other as necessary. From two coroutines it is natural to extend this to a set of coroutines.
From the description given of coroutines, it is apparent that coroutines should not be recursive. This permits us to use just one activation record for each coroutine and the address of each activation record can be statically maintained.
Each activation record is extended to include a location to store the CI for the corresponding coroutine. It is initialized with the location of the first instruction of the coroutine. When coroutine encounters a resume operation, it stores the address of its next instruction in it own activation record. The address of the CI for the resumed coroutine is obtained from the activation record of the resumed coroutine.
The purpose of declarations is two fold. The requirement that all names be declared is essential to provide a check on spelling. It is not unusual for a programmer to mispell a name. When declarations are not required, there is no way to determine if a name is new or if it is a misspelling of a privious name.
The second purpose of declarations is assist the type checking algorithm. The type checker can determine if the intended type of a variable matches the use of the variable. This sort of type checking can be performed at compile time permitting the generation of more efficient code since run time type checks need not be performed.
type checking--static, dynamic
import/export
Declarations and strong type checking facilitate safety by providing redundancy. When the programmar has to specify the type of every entity, and may declare only one entity with a given identifier within a given scope; the compiler then simply checks each the usage of each entity against rigid type rules. With overloading or type inference, the compiler must deduce information not supplied by the programmer. This is error prone since slight errors may radically affect what the compiler does.
Overloading and type inference lack redundancy.