4 Bindings and Scope Bindings and environments. Scope, block structure, and visibility. Declarations. Blocks. © 2004, D.A. Watt, University of Glasgow 4-1 Bindings and environments PL expressions and commands contain identifiers, which are declared elsewhere. Their meanings depend on these declarations. A binding is a fixed association between an identifier and an entity such as a value, variable, or procedure. Each declaration produces one or more bindings. An environment (or name space) is a set of bindings. Each expression or command is interpreted in a particular environment. Each identifier used in the expression or command must have a binding in that environment. 4-2 Example: Ada environments Ada program outline: procedure p is z: constant Integer := 0; c: Character; procedure q (x: in Float) is c: constant Float := 3.0e6; Environments: b: Boolean; { b a Boolean variable, begin c the floating-point number 3.0106, p a procedure with no parameter, … q a procedure with a Float parameter, end q; begin … end p; x a Float variable, z the integer 0 } { c a Character variable, p a procedure with no parameter, q a procedure with a Float parameter, 4-3 z the integer 0 } Scope The scope of a declaration (or of a binding) is the portion of the program text over which it has effect. In some early PLs (such as Cobol), the scope of each declaration was the whole program. In modern PLs, the scope of each declaration is controlled by the program’s block structure. 4-4 Block structure A block is a program construct that delimits the scope of any declarations within it. Each PL has its own forms of blocks: • C: block commands ({ … }), function bodies, compilation units. • Java: block commands, method bodies, class declarations, packages. • Ada: block commands (declare … begin … end;), procedure bodies, packages, tasks, etc. A PL’s block structure is the way in which blocks are arranged in the program text. 4-5 Example: Ada block structure Ada program outline: procedure P is … begin … declare … begin … end; … end P; procedure Q is … begin … end Q; 4-6 Monolithic block structure Some PLs (such as Cobol) have monolithic block structure: the whole program is a single block. The scope of every declaration is the whole program. declaration of x declaration of y declaration of z scope of declarations of x, y, z 4-7 Flat block structure Some PLs (such as Fortran) have flat block structure: the program is partitioned into non-overlapping blocks. declaration of x declaration declaration declarationofof ofyyy scope of declaration of x scope of declaration of y declaration declaration declarationofof ofzzz scope of declaration of z 4-8 Nested block structure Modern PLs have nested block structure: blocks may be nested within other blocks. declaration of x scope of declaration of x declaration declaration of of yy scope of declaration of y declaration declaration of of zz scope of declaration of z 4-9 Scope and visibility A binding occurrence of identifier I is an occurrence of I where I is bound to some entity X. An applied occurrence of identifier I is an occurrence of I where use is made of the entity X to which I is bound. At this applied occurrence, we say that I denotes X. If the PL is statically scoped (see later), every applied occurrence of I should correspond to exactly one binding occurrence of I. 4-10 Example: Ada binding and applied occurrences Ada program outline: procedure p is z : constant Integer := 0; c : Character; procedure q (x : in Float) is c : constant Float := 3.0e6; b : Boolean; begin … b … c … x … z … end q; begin … c … z … end p; 4-11 Hiding If identifier I is declared in two different blocks, I denotes a different entity in each block. If identifier I is declared in two nested blocks, I denotes one entity in the outer block and a different entity in the inner block. The inner declaration hides the outer one. declaration of x declaration of x declaration of x declaration of x Here the outer declaration of x is hidden. 4-12 Static vs dynamic scoping (1) A PL is statically scoped if the body of a procedure is executed in the environment of the procedure definition. • We can decide at compile-time which binding occurrence of an identifier corresponds to a given applied occurrence. A PL is dynamically scoped if the body of a procedure is executed in the environment of the procedure call. • We cannot decide until run-time which binding occurrence of an identifier corresponds to a given applied occurrence, since the environment may vary from one procedure call to another. 4-13 Example: static vs dynamic scoping (1) Program in Ada (which is statically scoped): s : constant Integer := 2; function f (x : Integer) return Integer is begin The value of s return s * x; here is always 2. end f; procedure q (y : Integer) is begin put(f(y)); end q; procedure q (z : Integer) is s : constant Integer := 3; begin put(f(z)); end q; 4-14 Example: static vs dynamic scoping (2) Same program in a hypothetical dynamically scoped PL: s : constant Integer := 2; function f (x : Integer) return Integer is begin The value of s return s * x; here depends on end f; the call site. procedure q (y : Integer) is begin put(f(y)); multiplies value end q; of y by 2 procedure q (z : Integer) is s : constant Integer := 3; begin put(f(z)); end q; multiplies value of z by 3 4-15 Static vs dynamic scoping (2) Dynamic scoping fits badly with static typing. • In the previous slide, what if the two declarations of s had different types? Nearly all PLs (including C, Java, and Ada) are statically scoped. Only a few PLs (such as Smalltalk and Lisp) are dynamically scoped. 4-16 Declarations (1) A declaration is a program construct that will be elaborated to produce binding(s). A declaration may have side effects (such as creating a variable). A definition is a declaration whose only effect is to produce binding(s). 4-17 Declarations (2) Forms of declarations: • type declarations • constant declarations • variable declarations • procedure definitions The repertoire of simple declarations depends on the PL. • … • collateral declarations • sequential declarations • recursive declarations. 4-18 Type declarations A type declaration binds an identifier to a type. A type definition binds an identifier to an existing type. Alternatively, a type declaration may introduce a new type, distinct from all existing types. 4-19 Example: C type declarations “typedef …” binds an identifier to an existing type: typedef char* Alpha; Alpha s; s = t; char* t; bound to existing type char* legal But “enum …”, “struct …”, or “union …” binds an identifier to a new type: struct Book {Alpha title, int edition}; struct Author {Alpha name, int age}; bound to Book b; Author a; new types a = b; illegal 4-20 Example: Ada type declarations Every Ada type declaration introduces a new and distinct type: type Alpha is array (1 .. 32) of Character; type Book is record title: Alpha; edition: Integer; end record; type Author is record name: Alpha; age: Integer; end record; b: Book; a := b; bound to new types a: Author; illegal 4-21 Constant declarations A constant declaration binds an identifier to a (constant) value. In Ada, a constant declaration has the form: I : constant T := E; This binds I to the value of the expression E, which must be of type T. Some PLs restrict the expression E to be a literal, or an expression that can be evaluated at compile-time. Some PLs restrict the constant’s type T. 4-22 Variable declarations A variable declaration binds an identifier to a variable. In Ada, ordinary variable declarations have the form: I : T := E; This creates a variable of type T, binds I to that variable, and initialises it to the value of the expression E. In Ada, renaming variable declarations have the form: I : T renames V; This binds I to the existing variable V. 4-23 Example: Ada renaming variable declaration Ada code: type Country is (UK, FR, …); pop : array (Country) of Natural; declare c : Country := …; p : Integer renames pop(c); begin bound to existing variable … pop(c) p := p + 1; … more efficient than end; pop(c) := pop(c) + 1; 4-24 Procedure definitions A procedure definition binds an identifier to a procedure. In most PLs, we can define both proper procedures and function procedures. 4-25 Example: Ada procedure definitions Function procedure definition: function even (n: Integer) return Boolean is begin bound to a function procedreturn (n mod 2 = 0); ure that tests whether its end; Integer argument is even Proper procedure definition: procedure double (n: in out Integer) is begin bound to a proper procedn := n * 2; ure that doubles its Integer end; argument 4-26 Collateral declarations A collateral declaration is a group of subdeclarations that are elaborated independently of one another. • A subdeclaration may not use bindings produced by any other subdeclaration. Collateral declarations are found in Java and functional PLs, but not in C or Ada. 4-27 Sequential declarations A sequential declaration is a group of subdeclarations that are elaborated one after another. • A subdeclaration may use bindings produced by previous subdeclarations, but not those produced by following subdeclarations. Sequential declarations are found in all imperative and OO PLs. 4-28 Recursive declarations A recursive declaration is one that uses the bindings it produces itself. Ada type declarations: • A single type declaration may be recursive. • A sequence of type declarations may be made mutually recursive, if preceded by “incomplete type declarations”. Ada procedure declarations: similarly. Java method definitions: • One or more method definitions may be (mutually) recursive. 4-29 Example: Ada recursive type declarations Mutually recursive type declarations: type IntNode; type IntList is access IntNode; type IntNode is record head: Integer; tail: IntList; end record; incomplete type declaration: binds IntNode to an unknown type completes the binding of IntNode 4-30 Example: Ada recursive procedure definitions (1) Recursive procedure definition: procedure print (n: Natural) is begin if n < 10 then put(digit(n)); else print(n/10); put(digit(n mod 10)); end if; end; 4-31 Example: Ada recursive procedure definitions (2) Mutually recursive procedure definitions: procedure parse_expression; procedure parse_primary is begin if acceptable('(') loop accept('('); parse_expression; accept(')'); else parse_variable; end if; end; binds parse_ expression to an unknown procedure 4-32 Example: Ada recursive procedure definitions (3) Mutually recursive procedure definitions (continued): procedure parse_expression is begin parse_primary; while acceptable('+') loop accept('+'); parse_primary; end loop; end; completes the binding of parse_ expression 4-33 Blocks A block is a program construct that delimits the scope of any declarations within it. Kinds of blocks: • block commands • block expressions. 4-34 Block commands A block command is a form of command that contains a local declaration (or group of declarations) D and a subcommand C. The bindings produced by D are used only for executing C. These bindings override any bindings for the same identifiers in the environment outside the block. Block commands are common in imperative and OO PLs. In C and Java: { D C } In Ada: declare D begin C end; 4-35 Example: Java block command To sort the values of int variables x and y into ascending order: if (x > y) { int z = x; x = y; y = z; } block command (scope of declaration of z) 4-36 Block expressions A block expression is a form of expression that contains a local declaration (or group of declarations) D and a subexpression E. The bindings produced by D are used only for evaluating E. These bindings override any bindings for the same identifiers in the environment outside the block. Block expressions are uncommon in imperative and OO PLs, but common in functional PLs. Form of block expression in Haskell: let D in E 4-37 Example: Haskell block expression To compute the area of a triangle whose sides have lengths a, b, c: let s = (a + b + c)/2.0 in sqrt(s*(s-a)*(s-b)*(s-c)) The only way to achieve the same effect in Ada (or C) is to declare and call a suitable function: function area (a, b, c: Float) return Float is s: constant Float := (a + b + c)/2.0; begin return sqrt(s*(s-a)*(s-b)*(s-c)); end; 4-38