Programming Language Theory Takashi Hattori January 14, 2016 1 Introduction 1.1 Some questions • What is a programming language? • Why do we have so many programming languages? • Why do you study the theory of programming languages? 1.2 Software development process 1. Requirements specification 2. Software design 3. Implementation <—You write code here!! 4. Testing and debugging 5. Maintenance • Writing a program is a small portion of the entire process. • Other parts are affected by the language chosen for implementation. (cf. Year 2000 problem1 ) 1.3 Relation between application fields and languages • Scientific computation—FORTRAN, ALGOL, PL/I • Business—COBOL • Artificial intelligence—LISP, Prolog • System software—C • Scripting—Shell, Awk, Tcl, Perl, Ruby • Web—Java, PHP, Javascript 1 http://www.fluffycat.com/COBOL/Y2K/ 1 1.4 Differences among languages 1.4.1 Character • Unique symbols (cf. APL2 ) • Japanese characters (cf. Mind3 , Kotodama4 , Nadesiko5 ) • Graphics (cf. Marten6 , ToonTalk7 , Stagecast Creator8 , Viscuit9 ) 1.4.2 Grammar Example: Just joking • whitespace10 • Befunge11 Example: Various syntax for iteration • Old Fortran DO 10 I = 1,100 ... 10 CONTINUE • Pascal for i := 1 to 100 do begin ... end; • C for (i = 1, i <= 100, i++) { ... } 2 http://en.wikipedia.org/wiki/APL_%28programming_language%29 3 http://www.scripts-lab.co.jp/mind/whatsmind.html 4 http://garuda.crew.sfc.keio.ac.jp/kotodamaCommunity/ 5 http://nadesi.com/ 6 http://www.andescotia.com/ 7 http://www.toontalk.com/ 8 http://www.stagecast.com/ 9 http://www.viscuit.com/ 10 http://www.99-bottles-of-beer.net/language-whitespace-154.html 11 http://en.wikipedia.org/wiki/Befunge 2 1.4.3 Paradigm Philosophically speaking, programming paradigm means “What is computation?”. Imperative(Procedural) Computation is a sequence of changing state. Functional Computation is a reduction of expression. Logical Computation is a proof of proposition. There are other kinds of paradigms. For example, object oriented paradigm covers other steps of the software development process such as requirements specification as well as implementation. 1.5 Definition of “good” programming language • readability / writability – simple – expressive – high abstarction level • performance – fast execution – small memory usage – fast compilation – efficient in development process • easy to find bugs • low learning cost (time and effort) • low development cost of compiler or interpreter 1.6 Reasons to study the theory of programming language • You can select a language suitable for a task in your hand. • You can learn a new language easily. • You can understand the background of software better • Different languages may well introduce different ideas and solutions. • It is an important base of the computer science. 3 1.7 History of programming languages evolution diagram12 • machine language / assembly language – craftsmanship • high level language – performance deterioration in some extent – dramatic improvement of efficiency in development process – very good readability and portability • structured programming language – maintenance of a large program is problematic – “goto statement considered harmful” by Dijkstra • object oriented language – everything is an object • lightweight language / dynamic language – handy tool for programming 2 Syntax and Semantics 2.1 Definition of a language It is important to define a new language in a rigorous and understandable way. • Scheme gives a mathematically precise definition13 , which is hard to understand. • Languages such as BASIC and C did not give rigid definitions at first. As a result, we had several slightly different language processors. (cf. Announcing a specification for PHP14 ) We need two things to define a language. • Syntax—how you write a program • Semantics—what will happen when executing a program 2.2 2.2.1 Brief introduction to the formal language theory Definition of formal languages Alphabet a set of characters Sentence a sequence of characters 12 http://merd.sourceforge.net/pixel/language-study/diagram.html 13 http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-10.html#%_chap_7 14 http://hhvm.com/blog/5723/announcing-a-specification-for-php 4 Grammar a set of rules to determine if a sentence is correct or not Language a set of correct sentences We often use tokens instead of alphabets because of simplicity. A token is something like a word composed of several letters. Example: An example of tokens Sentence total = total + x; can be decomposed into a sequence of characters t, o, t, a, l, *space*, =, *space*, t, o, ..., which is cumbersome. Instead, we usually use a sequence of tokens total, =, total, +, x, ;. Generative Grammar15 defines a set of correct sentences as a set of ‘sentences which can be generated in the following way’. • Terminal symbols (Tokens), Non-terminal symbols and Production rules are given. • Starting from a initial non-terminal symbol, we can generate a sentence by repeatedly applying production rules. There are several classes of grammars such as context-free grammar and regular grammar. Most of programming languages can be defined using context-free grammar16 . 2.2.2 BNF notation BNF (Backus-Naur Form) is a convenient notation to write a context-free grammar for practical use. • Terminal symbols are written as they are. • Non-terminal symbols are enclosed by < >. • Production rules are in the form ‘ non-terminal ::= symbols ’ where we can write multiple alternatives by separating with ‘|’ in the right side. Example: Simple arithmetic expression <var> ::= A | B | C <term> ::= <var> | ( <expr> ) <expr> ::= <term> | <expr> + <term> | <expr> * <term> 2.2.3 Parse tree To parse is to find how a sentence is generated by production rules. A parse tree is a tree structure that represents the result of parsing. Example: Parse tree of A*(B+C) 15 http://en.wikipedia.org/wiki/Generative_grammar 16 http://en.wikipedia.org/wiki/Formal_grammar 5 Exercise 1 Draw a parse tree of A+B*C with the grammar shown above. Exercise 2 In the grammar shown above, there is no precedence between + (add) and * (multiply). Change the grammar so that * takes precedence over + ? 2.2.4 Ambiguous grammar We say a grammar is ambiguous if more than one parsing is possible for one sentence. Example: An example of ambiguous grammar We have two different parse trees for A*B+C with the following grammar. 6 <var> ::= A | B | C <expr> ::= <var> | <expr> + <expr> | <expr> * <expr> Example: Dangling else Languages such as C and Java have dangling-else problem since else is optional in if statements. <if-statement> ::= if ( <expr> ) <statement> | if ( <expr> ) <statement> else <statement> Exercise 3 Explain dangling-else problem in case of the following statement. if ( x > 0) if ( y > 0 ) z = 1; else z = 2; Exercise 4 Explain that the following grammar is ambiguous. <s> ::= <a> <a> ::= <a> <a> | A | B | C 2.3 Glance at the semantics theory It is very difficult to define semantics of languages precisely. In C and Java, we usually say the meaning of ++ operator is “first evaluate the variable, then increment it”. However, we cannot figure out the result of x = 1; x = x++; with this definition. As long as we use a natural language, ambiguity is inevitable. Exercise 0 Write a program to show the value of x after x = 1; x = x++; in C and Java. What are the results? There are several formal semantics theories that can give rigorous definition of languages. 2.3.1 Operational semantics The operational semantics17 describes how a program is executed as a sequence of computational steps. • A computational step is a change of state in the computer. • The state in the computer is, for example, a mapping from variables to values (a model of the memory device). • By logical inference rules, we can define what kind of computational steps are possible. 2.3.2 Axiomatic semantics The axiomatic sematnics is based on Hoare logic18 . The meaning of a sentence is determined by a pair of precondition (which is true before the execution of the statement) and postcondition (which is true after the execution). It is closely related to verification of a program. 17 http://en.wikipedia.org/wiki/Operational_semantics 18 http://en.wikipedia.org/wiki/Hoare_logic 7 2.3.3 Denotational semantics The denotational semantics19 is based on recursive functions and domain theory. It constructs mathematical objects that describes the meaning of a program. Unfortunately, the result is often very hard to read20 . 3 Names and Bindings 3.1 Names in a program We need names in order to indicate various entities in a program. • Syntax of names differs depending on the language. In most languages, a name is a sequence of alphabets, numbers and some symbols. • In some languages, there is a limit on the length of names. • There are case-sensitive languages and case-insensitive languages. A Keyword is a name which plays some designated role in syntax. A reserved word is a keyword which cannot be used with other meaning. Example: Fortran has no reserved word The following Fortran program is syntactically correct. INTEGER REAL REAL INTEGER REAL = 3 INTEGER = 1.2 3.2 Namespace We need long names to avoid conflict when we use many names. It is not convenient, however, to read and write long names. A namespace is a name given to a set of names, which allows us to make the hierarchy of names, in other words, to use concatenated short names instead of a single long name. Example: Namespace in C++ :: is the operator to concatenate names. #include <iostream> main() { std::cout << "Hello, world!\n"; } We can set the default namespace by using namespace. #include <iostream> using namespace std; main() { cout << "Hello, world!\n"; } 19 http://en.wikipedia.org/wiki/Denotational_semantics 20 http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-10.html#%_sec_7.2 8 3.3 3.3.1 Bindings Definition of bindings Binding a link between a name (more precisely, an occurence of a name) and a certain semantic entity. Environment a set of bindings currently used during execution. We call a binding static when it can be determined before execution (usually compile-time). Otherwise, we call it dynamic. Example: TinyBASIC Very small BASIC interpreter called TinyBASIC has only 26 variables: ‘A’ to ‘Z’. In this case, we always use the same environment. Example: Variable declaration in Java In Java, a variable declaration int i; means we will bind a name i to a newly created integer variable. Exercise 5 There are two types of languages as to varibale declarations. Languages such as C and Java require declarations before variables are used, while languages such as Perl and Ruby do not. What are merits and demerits of these two types? 3.3.2 Anonymous and alias Bindings are not necessarily one-to-one association. We call an entity (e.g. variable, function, etc.) anonymous if it is not bind to any name. Example: Anonymous function in Lisp (lambda (x) (+ x 1)) Example: Anonymous funciton in Ruby lambda { |x| x+1 } Proc.new { |x| x+1 } proc { |x| x+1 } On the contrary, one entity can be bound to more than one names. In such case, names which bound to the same entity are called alias of each other. Example: EQUIVALENCE statement in Fortran DIMENSION A(10), B(5) EQUIVALENCE(A(6), B(1)) 9 3.4 Scope A scope is a part of program where a binding is used (in other words, we can see an entity through its name). 3.4.1 Static scoping In Algol-based languages, the scope of a name is a block in which the name is declared. If the same name is declared more than once, the most inner one takes precedence over outer ones. This type of scoping is also called lexical scoping because it is determined by the lexical stuructres. Example: Static scoping in Pascal procedure main; var x : integer; { declaration 1 } procedure sub1; begin ... x ... end; procedure sub2; var x : integer; begin ... x ... end; { declaration 2 } { body of main } begin ... x ... end; 3.4.2 Dynamic scoping In ancient Lisp and APL, if a name is declared in a certain function, its scope is the time-period during which the function is executed. If the same name is declared more than once, the most recent one takes precedence over older one. Example: Dynamic scoping in pseudo code procedure main; var x : integer; { declaration 1 } procedure sub1; begin ... x ... end; procedure sub2; var x : integer; begin sub1 end; { declaration 2 } 10 { body of main } begin sub1; sub2 end; Exercise 6 In the following Pascal-like psuedo code, what are the outputs respectively if we use static scoping and dynamic scoping? program main; var x : integer; procedure sub1; begin writeln(x) { output the value of x } end; procedure sub2; var x : integer; begin x := 10; sub1 end; { body of main } begin x := 5; sub2 end. 3.4.3 Closure In languages such as Lisp and Ruby, a function is a first-class object (i.e. it can be assigned into a variable, passed as a parameter, etc.). Suppose we have a function stored in a variable. When the function is called, the scope of names are determined in the place the function is defined (not the place the function is called). In other words, the stored function remembers the environment at the place of definition. This feature is called a closure. Example: Closure in Ruby def foo x = 1 proc { x += 1 } end bar = foo x = 100 p bar.call p bar.call p bar.call 11 proc { x += 1 } produces a closure that increments the variable x. It is returned by the function foo and then assigned into the variable bar. Even if the closure is called outside the scope of x, it increments x defined in foo. Example: Closure in JavaScript function foo() { var x = 1; return function() { return x += 1; }; } var bar = foo(); var x = 100; console.log(bar()); console.log(bar()); console.log(bar()); Exercise 7 Suppose we want to create 10 buttons, each of which will show its id number when clicked. But the following JavaScript code does not work. Explain why. for (i = 1; i <= 10; i++) { button[i].onclick = function(){ alert(i); }; } Supplementary Explanation21 4 4.1 Variables What is a Variable? A variable in mathematics represents a possibility of a value. If a range is designated to a variable, the variable may take one of values in the range. The selection of the value must be consistent throughout a proof. A variable in functional and logical programming languages is similar to that in mathematics. A variable name is bound to a value. Once bound, it never changes. Note that the same name may be bound to a different value in the different environment. A variable in imperative (procedural) programming languages is a named box to store a value. A variable name is bound to a certain memory area. The value in the memory area may change during execution of the program. In this section, we mainly deal with variables in imperative (procedural) languages. 4.2 Type Many languages such as Fortran, Pascal, C and Java have a type as a property of a variable. In these languages, a type must be declared for a variable before its use, and the variable can only store values of that type. On the other hand, languages such as Lisp and Ruby have no types. A variable can store any value. One of the merits of type declaration is that the required amount of memory can be calculated at compile time based on the type declared. If we have no type declaration, the memory area for a variable usually contains a pointer which refers to a ?? where we can allocate required amount of memory at run time. 21 exercise7.html 12 4.3 Memory A memory area is the essential part of a variable where we store a data during program execution. If a variable name occurs in a program, it is evaluated as follows: 1. Get an address of a memory area which is bound to the name. 2. Get a value from the memory area. If a variable name occurs in the left side of an assignment statement, however, the result of evaluation should be an address to store a new value. Sometimes we call an address as a left value. There are several memory management methods as described below. 4.3.1 Static data area A static variable is a variable that exists across the entire execution of the program. It is usually allocated in the static data area at compile time. 4.3.2 Stack A stack is a first-in-last-out data structure. It is often used for the area to allocate local variables. When a fuction is called, parameters and local variables are allocated at the top of the stack. When that function returns, the allocated area is discarded. • Recursive call can be easily implemented. • When using dynamic scoping, we can find the valid bindings by searching downwards from the top of the stack. It is more difficult when using static scoping. • A variable which occurs in a closure should not be allocated in the stack because the variable will be used when the closure is called afterwards (see ??). Exercise 8 Explain why we can find valid bindings by searching downwards from the top of the stack, using the code in exercise 7 as a concrete example. (Sorry, I made a mistake. Exercise 7 should be Exercise 6.) 4.3.3 Heap A heap is a data structure in which we can allocate some amount of memory when required and release it when not necessary any more. The released area will be reused for other purposes. Some languages require the programmers to write a code for releasing unnecessary area. They often (very often) forget to write the code, resulting a situation where we have no new area to allocate even if the actual used area is small. We call this situation as a memory leak. In order to avoid the memory leak, some languages provide garbage collection which automatically releases unnecessary area. We can determine a certain area is unnecessary if there is no refrence to the area. Followings are the typical garbage collection methods: Reference count A counter is attached to each memory area. When someone starts to refer the area, increment the counter. When stopping to refer, decrement it. If the counter becomes 0, release that area. Mark and sweep First, we put a mark on every area reachable by following references from areas obviously necessary (e.g. an area bound to a variable name). Next, we sweep the entire heap from end to end, and release areas that are not marked. 13 Copying We divide the heap into two, and use only half. When there is no available area to allocate, we copy the necessary area to the other half by following references starting from obviously necessary areas. This method achieves compaction as well as garbage collection. Garbage collection usually requires to stop the execution of the program during it takes place. Since it is inconvenient for real-time systems, there are devised garbage collection methods that can be done in parallel with the execution of the program. Exercise 9 Suppose we use reference count garbage collection. In some cases, the counter of unnecessary area will not become 0. Give an example. Exercise 10 Explain why we should stop the execution of the program when using mark-and-sweep garbage collection. 4.4 Extent An extent is a time period when a variable holds its value. Note that a scope and an extent are different in general. Example: Static local variables In languages such as C and PHP, a local variable declared as static has a scope of that function, but its extent is the entire execution of the program. function test() { static $a = 0; echo $a; $a++; } Example: Variables in a closure In the ??, we need the value of x after the function foo returns. def foo x = 1 proc { x += 1 } end Exercise 11 In what kind of situation, are static local variables convenient? Give a concrete example. 5 Mid-term paper <literal style=“docbook”><formalpara role=“exercise”><title>Exercise 12</title></literal> Exercise 12 Pick one programming language (except for C, C++, Java, JavaScript), and answer the following question. 1. Explain features of the language, comparing with other languages. 14 2. Give a sample program that fully shows the features of the language. (It is not necessary to write it by yourself. It should be a complete program without any omission.) 3. Explain why you choose the sample program above. Note that: • The less popular language you pick, the higer score you get. • Features of a language means syntactic or semantic properties of the language. It does not mean, for example, there are plenty of libraries, or there is a very good development environment. • It is not necessary to run the sample program. It is all right if a compiler (or an interpreter) is not available as long as specification of the language is clear enough. • You are strongly requested to cite references. Otherwise you are considered to commit plagiarism. • There is no restriction about the length of the paper. Normally 2 or 3 pages in A4. • You should give a presentation in class. 6 Types 6.1 What is a type? A type consists of a set of values and designated operations on the values. A type system is a set of rules to associate expressions with their types. Some languages provide sophisticated type systems while others do not use types at all. Major reasons why we use types are as follows: • More informative for human readers. • Compiler can detect errors when types mismatch. • Compiler can generate more efficient code. Exercise 13 An integer is used for conditional branch (0 is false, otherwise true) in C, while boolean and int are different types in Java. What are merits and demerits of these two methods. 6.2 Various types Primitive types Hardware-defined or indecomposable types. (e.g. integer, floating point, character) Structured types Types composed of some other types. Many languages allow programmers to define new types. 6.2.1 Enumerated type We can define a new enumerated type by writing all the possible values for that type. • More readable. • Usually implemented by integer, but only comparison is allowed. 15 Example: Colors In Pascal, we can define a type representing colors whose value is one of red, blue, green. type colortype = (red, blue, green); var x : colortype; begin ... x = blue; case x of red : ... blue : ... green : ... end; end; In Pascal, red < blue is syntactically correct while red + blue is not. 6.2.2 Subrange type A subrange type is an extraction of a certain ordinal type by specifying its highest and lowest values. • Same operations are allowed as the original type. • We can detect errors when values are out of range. Example: Upper case letters In Pascal, we can define a new uppercase type from the char type. type uppercase = ’A’ .. ’Z’; Example: Range of an array In Pascal, we can use any ordinal type as an index of an array. Usually a subrange of integer is used. var x : array [1..100] of char; 6.2.3 Union type A union type is a type whose value may be one of several different types. In C, union allows programmers to access the same memory location with more than one types. • It is up to programmers which type is used to read and write. • Some programmers intentionally use it for evil purposes. In Pascal, record with variant parts provides more sophisticated way. • A tag field indicates the type currently stored in a variant part. • An error occurs when an operation mismatches with the type in the tag. 16 • Still, some may intentionally change the tag value. . . Example: Circles and rectangles In Pascal, we can define a new record type figure which can store either a circle or a rectangle. A circle has fields x, y and radius, and a rectangle has x, y, width and height. { ‘shape’ is an enumerated type whose value is ‘circle’ or ‘rectangle’ } type shape = (circle, rectangle); { ‘figure’ is a record type } type figure = record { there are ‘x’ and ‘y’ for all cases } x : real, y : real { variant parts whose tag is ‘form’ } case form : shape of { in case of the value of ‘form’ is ‘circle’ } circle: (radius : real); { in case of the value of ‘form’ is ‘rectangle’ } rectangle: (width: real; height: real); end; var myfigure : figure; begin .... myfigure.x = 2.0; myfigure.y = 3.0; myfigure.form = circle; myfigure.radius = 1.0; ... end Exercise 14 What is the intention of the following C program? union { unsigned int z:8; struct { unsigned int h:4; unsigned int l:4; } x; } a; unsigned char tmp; a.z = 0x12; tmp = a.x.h; a.x.h = a.x.l; a.x.l = tmp; printf("%X\n", a.z); 6.2.4 Pointer type A pointer type is a type whose value is a reference to another variable. By applying dereference operation, we can get a value of the referred variable. By using pointers, we can dynamically allocate variables that are not bound to any names. It may cause the following two problems: 17 Dangling Pointer If a memory area is released during a pointer is referring to it, the pointer will refer to a garbage or a reused area. Lost Heap-dynamic Variable If values of pointers are changed, the memory area referred by the pointers may lose all the references, which means we can never access that area. • In case of Pascal, a pointer can only refer to an area allocated by new. We must explicitly release the allocated area by dispose. Pascal has the above two problems. • In case of C, pointers are virtually same as the hardware memory addresses. C has many problems including the above two. • In case of Java, objects are implicitly referred to by pointers. Automatic garbage collection prevents the above two problems. Example: Linked list in Pascal type link = ^cell; cell = record info : integer; next : link; end; var p, front : link; begin ... new(p); p^.info := i; p^.next := front; front := p; ... end 6.2.5 Function type Functions have their own types in languages such as Haskell where a function is a first-class object. Example: Function type in Haskell A function type is composed of types of parameters and return values of the function. add :: Int -> Int -> Int add x y = x + y 18 6.3 6.3.1 Type conversion Implicit conversion In some cases, a value of a certain type is automatically converted into another type for convenience. Example: Arithmetic operation In most languages, when an arithmetic operation applies to an integer and a floating point number, the integer is converted into a floating point number. For example, 3 / 2.0 is converted into 3.0 / 2.0. Example: String concatenation In Java, when ‘+’ operator applies to a number and a string, the number is converted into a string. For example, 2 + "3" is converted into "2" + "3". 6.3.2 Explicit conversion In some languages, programmers can change types of certain values. Example: Cast In C and Java, writing a type name before an expression instructs a conversion to that type. (int)(3.0 / 1.5) 6.4 Type checking The process of verifying consistency of types in a program is called type checking. Type checking at compile time is very useful for debugging. 6.4.1 Type declaration • In many languages, a type is explicitly declared for a variable. – In Fortran, an implicit declaration is possible where a variable is typed according to the initial letter of its name: integer if beginning with I, . . . , N, and real if beginning with other letters. • In ML and Haskell, a type can be declared for any expression in a program. 6.4.2 Type compatibility We need a rule to determine compatibility of two user-defined types. Name compatibility Types are different if their names are different. Easy to implement, but inconvenient in many cases. Structure compatibility Types are different if their structures are different. Example: Type compatibility in Ada In the following Ada code, FLOAT, celsius, and fahrenheit are different types with each other since Ada uses name compatibility. 19 type celsius is new FLOAT; type fahrenheit is new FLOAT; 6.5 Type inference In languages such as ML and Haskell, every expression in a program should have its type. It is too painful to write types for all expressions, the compiler makes an inference about proper typing if we give minimum declarations. Example: Type inference in ML We need no type declaration for the function inc. fun inc x = x + 1; val inc = fn : int -> int We need at least one type declaration for the function add. fun add (x, y) = x + y; Error: overloaded variable ’+’ cannot be resolved fun add (x, y) = x + (y : int); val add = fn : int * int -> int fun add (x, y) : int = x + y; val add = fn : int * int -> int Note: In SML ver0.5 or after, the + operator is an overloaded function that has default type int, which means fun add(x, y) = x + y; has the type fn: int * int -> int. 6.6 Polymorphism When a single operator or function can deal with values of more than one type, we call it polymorphism. ad hoc polymorphism Definitions for each type are independent. Also called overloading. parametric polymorphism Definitions include formal parameters that receive types of data currently processed. Example: ‘+’ operator in Java In Java, ‘+’ operator has two meanings: addition of numbers and concatenation of strings. Example: Template in C++ We can write a function max in C++ as follows: 20 template <class Type> Type max(Type first, Type second) { return first > second ? first : second; } int a, b, c; char d, e, f; c = max(a, b); f = max(d, e); Example: Polymorphic function in Haskell In Haskell, the type of function length is [a]->Int where a is a type variable and [ ] is an array type. expression type value length [a]->Int function [3::Int, 4] [Int] [3, 4] "abc" [Char] "abc" length [3::Int, 4] Int 2 length "abc" Int 3 Exercise 15 In Haskell, an expression map f x applies the function f to each element of the array x, and then make an array collecting their results. For example, the result of map length ["abc", "de"] is [3, 2]. Write the type of function map, using type variables a and b. 7 7.1 Expressions Precedence and associativity of operators When given an expression, we can investigate its structure by making its ??. We have learned ??, but we prefer precedences and associativities for writing grammars of expressions, which is much simpler. The higher the precedence of an opeator is, the smaller the parsed sub-tree is (or, the earlier it connects its arguments). When there are more than one operator of the same precedence in an expression, if they are leftassociative, the leftmost operator connects its arguments first. If right-associative, the rightmost connects first. Example: Precedences of arithmetic operators In most languages, arithmetic operators have the same precedences as mathematics. For example, a + b * c is equivalent to a + ( b * c ). Example: Associativities of arithemtic operators In most languages, arithmetic operators are left-associative. For example, a - b + c - d is equivalent to (( a - b ) + c ) - d. Example: Precedence and associativity in APL 21 In APL, all operatos have the same precedence, and are right-associative. For example, A × B + C is equivalent to A × ( B + C ). Exercise 16 Suppose precedences and associativities of operators are given by the following table. Put parentheses at proper positions in expressions below so that their structures are explicit. operators precedences associativities #, $ high left-associative !, ? low right-associative 1. a # b $ c 2. a ! b ? c 3. a # b ! c $ d 4. a # b ! c ? 5. a # b $ c ! 7.2 d d ? e Order of evaluation The process of computing the value of an expression is called evaluation. Note that the order of evaluation is a different concept from precedence and associativity. Example: Evaluation order of arithmetic operations In C, given an expression a*b+c*d, it is undefined that either a*b or c*d is evaluated first. Example: Side effect and evaluation order In the following C program, the result is different depending on that either a or fun1() in the statement (1) is evaluated first. int a = 1; int fun1() { a = 2; return 3; } void fun2() { a = a + fun1(); } /* (1) */ void main() { fun2(); } As shown above, side effect causes a problem if the order of evaluation is undefined. We may think of several solutions as follows: 1. Do nothing.—The result is compiler dependent. 2. Prohibit side effect.—It is not practical for imperative languages. 22 3. Compiler generates a warning if there is a risk.—It is hard to detect. 4. Define the order of evaluation.—It obstructs optimization. Java adopts (4). It evaluates an expression from left to right (See ‘What are the rules for evaluation order in Java?’22 for detail). C/C++ introduces a sequence point23 , which is a mixture of (1) and (4). Functional languages such as ML and Haskell receive benefit from (2). 7.3 Short-circuit evaluation Short-circuit evaluation is an evaluation strategy where some subexpressions may not be evaluated if the value of the entire expression can be determined without it. Example: Logical operators When the value of a is less than 0, the following expression can be evaluated to false without evaluating b < 10. ( a >= 0 ) && ( b < 10 ) Exercise 17 Java has two types of logical operators: && and || are short-circuting and & and | are not. Explain why we need both types of operators. 7.4 Lazy evaluation Generalizing short-circuit evaluation, lazy evaluation is an evaluation strategy where any subexpression is not evaluated until it is necessary for computation of other parts. Example: ‘range’ in Python3.x >>> for i in range(10000): ... if i > 10: break ... print(i) ... In Python2.x, range(10000) generates 10000 elements and returns them. In Python3.x, range(10000) returns a list where each element is generated one by one when print(i) is executed. 8 8.1 Control structures Structured programming Languages in early days such as Fortran and COBOL use goto statement extensively, which results in obscure program structure. In 1968, Dijkstra insisted a program structure is more readable if it is a hierarchical composition of sequences, iterations and conditional branches. Example: Loop in early Basic and Pascal 22 http://stackoverflow.com/questions/6800590/what-are-the-rules-for-evaluation-order-in-java 23 http://en.wikipedia.org/wiki/Sequence_point 23 10 20 30 40 50 60 X=1 IF X>=1000 THEN GOTO 60 PRINT X X=X*2 GOTO 20 END x = 1; while x<1000 do begin writeln(x); x = x*2 end; 8.2 Non-local exits In many languages, return statement immediately terminates the function even if it is in the middle of iteration. Non-local exits is an enhanced version of return that terminates nested function calls at once and goes back to an outer level function. Example: Catch and throw in Ruby def bar throw :a, 3 end def foo bar end catch(:a) do foo end 8.3 8.3.1 Exceptions What is exception? In early languages, if something unusual occurs during execution, the program has no choice but to stop with an error message. Recent languages can deal with such situation more gracefully. An exception is an unusual event in a program for which we need some extra processing. • Detected by hardware: division by zero, to read or write protected memory, end of file, etc. • Detected by software: index of an array is out of range, etc. • Defined by programmers: anything you wish. 24 8.3.2 Exception handling An exception handler is a part of program that is invoked when an exception occurs. The merits of exception handling is as follows: • No need to write codes for detecting exceptions. Imagine if you need to check that the divisor is not equal to 0 before every division! • Cleaner code for dealing with an unusual event, especially when it requires non-local exits. • We can manage several exceptions in one place. • Languages such as Java force programmers to write exception handlers so that their programs are robust in unexpected situation. Example: Exception handling in PL/I An exception handler can be written in anywhere in the program with the ON statement. For example, the following is a handler for an exception SUBSCRIPTRANGE, which occurs when an index of an array is out of range. ON SUBSCRIPTRANGE BEGIN; PUT LIST (’ERROR’) SKIP; END; Note that the ON statement is an executable statement (not a declaration), which means bindings between exceptions and exception handlers are dynamic. An exception handler is valid after an ON statement is executed, and until another ON statement with the same exception name is executed. In addition, in order to make an execption handler valid, we need to write an exception name before a part of the program in which the exception may occur. For example, the following code checks the index I if it is out of range or not. (SUBSCRIPTRANGE): BEGIN; A = B(I); END; Example: Exception handling in Java • An exception is represented by an instance of an exception class, which is passed to a handler as an argument. • Exception classes are hierarchical. An execption handler can deal with many kinds of exceptions by specifying a high level exception class in the hierarchy. • Bindings between exceptions and exception handlers are lexical. try-catch-finally statement makes the scope of handlers. • By declaring throws at the head of a method, an exception can be propagated to the calling method, which allows dynamic bindings between exceptions and exception handlers. • If there is a possibility of an exception, programmers must either write an exception handlers for it or propagate it to the calling method. Exercise 18 Java forces programmers to handle exceptions, but that is not the case for Error and RunTimeException. Guess what the reason is. 25 9 Subprograms 9.1 What is a subprogram? A subprogram is a chunk of code that is independent from other parts to some extent. In most languages, subprograms have the following properties. • There is a single entry point. (Fortran provides multiple entry points.) • When a subprogram A calls another subprogram B, A will pause until B terminates. Namely, there is only one subprogram which is executed at a time. (This is not the case for concurrent languages.) In some languages, a subprogram that returns a value is called a function and one that does not is called a procedure or a subroutine, but both types of subprograms are called functions in other languages such as C. In addition, object-oriented languages usually use the term methods for them. 9.2 Parameters A parameter is used in order to apply a single subprogram to various data. Actual parameters Data specified when a subprogram is called. Formal parameters Variable names used in a subprogram to store actual parameters. In many languages, acutal parameters are associated to formal parameters by their position (positional parameters). It is error-prone when a subprogram has many parameters. In languages such as Ada and Fortran90, the association between actual parameters and formal parameters is explicitly specified (keyword parameters). In addition, any actual parameter can be omitted if a default value is declared for the associated formal parameter. Example: Parameters in Ada Suppose we have the following declaration in Ada. procedure Draw_Rect( X , Y : Integer; Width : Integer := 10; Height : Integer := 10; Color : Integer := 0 ) We may write any of the following procedure calls. Draw_Rect( Width => 20, X => 100, Y => 50, Color => 1 ) Draw_Rect( 100, 50, Width => 20, Color => 1 ) 9.3 Passing parameters 9.3.1 Direction of passing Input Data are passed from actual parameters to formal parameters when a subprogram is called. Output Data are passed from formal parameters to actual parameters when a subprograum finishes its execution. 26 In case of output, the actual parameter must be a mutable object (which has a ??). Example: Passing parameters in Ada procedure Foo(A : in Integer; B : in out Integer; C : out Integer) is begin B := B + 1; C := A + B; end Foo; 9.3.2 Evaluation strategy of parameters Pass by value (call by value) First, all the actual parameters are evaluated. Then, the values are copied to formal parameters. It is usually used for input direction. If it is used for both input and output, it is also called pass by copy-restore. Pass by reference (call by reference) References of actual parameters are passed to formal parameters. In other words, formal parameters become ?? of associated actual parameters. Its direction is inherently both input and output. Example: Passing parameters in Pascal In Pascal, a foraml parameter declared with var is pass-by-reference, otherwise pass-by-value (input only). For example, the following procedure does nothing at all. procedure swap(a, b : integer) temp : integer; begin temp := a; a := b; b := temp end; If we change the first line as follows, it will swap two parameters as intended. procedure swap(var a, b : integer) Pass by name (call by name) Formal parameters are replaced by actual parameters. Actual parameters are evaluated each time formal parameters are used. When they are evaluated, the environment used is the one at the place where the subprogram is called. This is a form of ?? since actual parameters are not evaluated until they are used. Example: Side effect and pass-by-name Suppose the following pseudo code uses pass-by-name. 27 main() { int x = 1; output foo(bar(x)); } int foo(y) { int x = 10; return x + y + y; /* bar(x) is substituted for y */ /* bar is called twice here */ } int bar(z) { z = z + 1; return z; } /* x in main is substituted for z */ Exercise 19 When pass-by-value (input only), pass-by-reference, and pass-by-name are used, answer the value of variables value and list after execution of the following code, respectively. void main() { int value = 2, list[5] = {1, 3, 5, 7, 9}; swap(value, list[0]); swap(list[0], list[1]); swap(value, list[value]); } void swap(int a, int b) { int temp; temp = a; a = b; b = temp; } 9.4 Coroutine When a subprogram can suspend itself in the middle of its execution, and make another subprogram resume from its suspended state, we call them coroutine. Example: Fiber in Ruby fib = Fiber.new { a,b = 0,1 loop { Fiber.yield a a,b = b,a+b } } puts puts puts puts puts fib.resume fib.resume fib.resume fib.resume fib.resume 28 Coroutine is a form of non-preemptive concurrent programming. We will study about concurrent programming in chapter 11. 10 10.1 Object Oriented Programming Abstraction and Modularization When writing a large program, the following concepts are very important. Abstarction Only essential functions and properties are taken into consideration by hiding other parts. Modularization A program is divided into several parts so that modification of one part does not affect other parts. Abstraction and modularization of control flow is mainly done by subprograms. In addition, we need abstraction and modularization of data structures. 10.2 Abstract data type An abstarct data type is a type that has following properties: • From outside, we cannot say how data are actually represented and stored. • The only available operations from outside are subprograms given by the abstract data type. The two properties above provide abstarction and modularization of data structures. Since we do not know actual representation of data, modificatin inside an abstract data type does not affect outside. This is also called encapsulation or information hiding. Example: Floating point numbers Floating point numbers can be considered as an abstarct data type. • We usually do not take care of bit-patterns of numbers. • Some CPUs do not have floating point instructions. When using such CPUs, floating point operations are done by software libraries. 10.3 Package in Ada • package is the unit of encapsulation. • A package consists of a specification package and a body package of the same name. • A specification package contains interface information. When compiling, only specification package is required to use that package. Though a specification package is basically a public information, a private part can be declared in a specification package. Example: Stack package 29 package Stack_Pack is -- public part type Stack_Type is limited private; function Empty(S : in Stack_Type) return Boolean; procedure Push(S : in out Stack_Type; Element : in Integer); procedure Pop(S : in out Stack_Type); function Top(S : in Stack_Type) return Integer; -- private part private Max_Size : constant := 100; type Stack_Type is record List : array ( 1 .. Max_Size ) of Integer; Top_Index : Integer range 0 .. Max_Size := 0; end record; end Stack_Pack package body Stack_Pack is function Empty(S: in Stack_Type) return Boolean is begin return S.Top_Index = 0; end Empty; procedure Push(S : in out Stack_Type; Element : in Integer) is begin if S.Top_Index >= Max_Size then -- error handling omitted else S.Top_Index := S.Top_Index + 1; S.List ( Top_Index ) := Element; end if; end Push; procedure Pop(S : in out Stack_Type) is begin if S.Top_Index = 0 then -- error handling omitted else S.Top_Index := S.Top_Index - 1; end if; end Pop; function Top(S : in Stack_Type) return Integer is begin if S.Top_Index = 0 then -- error handling omitted else return S.List(S.Top_Index) ; end if; end Top; end Stack_Pack; We need to declare details of a type Stack_Type in the private part of the specification package because the compiler needs to know how much memory is required to allocate a new Stack_Type data while that information should be hidden from users of the package. with Stack_Pack; use Stack_Pack; 30 procedure Foo is X : Stack_Type; Y : Integer; begin Push(X, 42); Push(X, 17); Y := Top(X); Pop(X); ... end Foo; 10.4 -- allocate memory for a new Stack_Type Object oriented languages Many people use the term ‘object oriented languages’ in various different ways. One of the broadest definitions is the following two conditions: • There is a unit called object that performs some calculation and stores some data. • A message is passed from one object to another. This definition requires neither class nor dynamic creation of objects. Other (narrower) definitions include more conditions, some of which are: • Objects are created dynamically. • Objects are abstract data types. • Objects can inherit functions of other objects. 10.5 Designing object oriented languages 10.5.1 How pure the language is object oriented? It is possible to represent every data as an object and every operation as a message. However, it may be inefficient to implement operations such as 1+2 with objects and messages. In languages such as C++ and Java, primitive parts are not object oriented. On the other hand, in languages such as Smalltalk and Ruby, everything is an object. Example: New methods for integers in Ruby class Fixnum def foo self + 100 end end 1.foo 10.5.2 # => 101 Class-based or prototype-based? In many object oritented languages, a class is a description of objects. All the instances of a certain class share the description. In languages such as JavaScript, an object has a set of properties and a prototype object. Some properties are shared by objects using the prototype chain. 31 10.5.3 What kind of information hiding is possible? It is usually a good idea to encapsulate data stored in an object. However, it is sometimes convenient to allow access from outside. Example: Controlling Access in Smalltalk • No Instance variables can be accessed by other objects. • All methods are accessible from any other object. Example: Controlling Access in C++ We can choose one of following access modifiers for each variable and method. public accessible from any class. protected accessible from subclasses and friends. private accessible from friends only. Example: Controlling Access in Java • In addition to C++, Package is another unit of information hiding. 10.5.4 Single inheritance or multiple inheritance? Suppose a language has a inheritance mechanism. If there is only one parent class, it is called single inheritance. If there may be more than one, it is called multiple inheritance. Obviously, multiple inheritance is more powerful than single. However, some people think multiple inheritance is hard to use because its semantics is very complicated. Example: Inheritance in Java • Single inheritance for implementation. There is only one superclass whose methods can be used in a subclass. • Multiple inheritance for specification. There may be many ‘interfaces’ that specify what kind of methods a subclass should have (names of methods, types of parameters and return values). 32 10.5.5 How types are checked? In most object oriented languages, a class is a type. Since a subclass represents a subset of its superclass, it is natural to define a subclass as a subtype of its superclass (i.e. the subclass type can be substituted for the superclass type). Example: Type checking in Java class Parent {} class Child extends Parent {} void some_method { Parent x; Child y; x = y; //OK y = x; //NG } Exercise 20 In object oriented languages with static type checking such as Java, we can add new methods or override parent’s methods in a subclass, but we cannot delete parent’s methods. Guess the reason. 11 11.1 Concurrent Programming Concurrent and parallel Sequential Execution progresses in a predefined order, one step at a time. Concurrent The execution order of more than one component is not defined. Also called logically concurrent. Parallel More than one step is executed by hardware at the same time. Also called physically concurrent. Concurrent does not necessarily mean parallel. Single CPU can execute concurrent tasks by switching tasks. Preemptive A task has a chance to be switched at any time. Switching usually occurs on timer interrupts or I/O events. Non-preemptive A task will not be switched against its will. See ??. 33 11.2 Synchronization Thread An abstract flow of control. Or, a unit of execution that holds its context (CPU registers, contents of stack, etc.). It is sometimes called a process, a task, or a job, but be careful that these words may be used for different concepts depending on situations. Synchronization To put a constraint on execution of threads at a certain point of a program. Cooperation synchronization When we simply say synchronization, it usually means cooperation synchronization. It includes several cases: for example, a fast thread waits for another slow thread, a thread sends data to another thread, etc. Competetion synchronization It is also called mutual exclusion. When a certain resource cannot be accessed by more than one thread at the same time, a constraint should be put so that only one thread access it at a time. Critical section A part of program that needs mutual exclusion. Example: Competition Suppose the initial value of a variable x is 1. Both threads A and B will increment x by 1. 1. A reads 1 (the value of x). 2. B reads 1 (the value of x). 3. A computes 1+1 (result of increment) 4. B computes 1+1 (result of increment) 5. A writes 2 to x 6. B writes 2 to x 11.3 Deadlock and starvation If a thread locks a shared resource, other threads cannot access it until the thread unlocks it. Deadlock All threads are waiting for some shared resources to be unlocked. No threads can proceed. Starvation A certain thread cannot access a shared resource forever while others can do. Example: Dining philosophers problem See Wikipedia24 There is a chance that deadlock occurs. 24 http://en.wikipedia.org/wiki/Dining_philosophers_problem 34 loop Pick up the left folk. Pick up the right folk. Eat for a while. Put the both folks down. Think for a while. end Now, philosphers get smarter. Still, there is a chance that starvation occurs. loop Pick up the left folk. if (the right folk is not available) then Put the left folk down. else Pick up the right folk. Eat for a while. Put the both folks down. Think for a while. end end Exercise 21 If philosophers sit in a row, instead of a circle, deadlock will not occur. Explain why. 11.4 Semaphore A semaphore is an abstract data type used for managing synchronization. It consists of a counter and a thread queue. P operation If the counter is greater than 0, decrement it by 1. Otherwise, suspend the current thread, and put it into the queue. V operation If the thread queue is empty, increment the counter by 1. Otherwise, pick a thread from the queue, and let it continue. Using semaphores is error-prone. Mistakes easily cause deadlock. Example: Producers-consumers problem (without mutual exclusion) See Wikipedia25 In Ada, wait and release conrrespond to P operation and V operation respectively. semaphore fullspots, -- Number emptyspots; -- Number fullspots.count := 0; -emptyspots.count := BUFLEN; -- of filled spots in the buffer of empty spots in the buffer Initially no spots are filled Initially all spots are empty task producer; 25 http://en.wikipedia.org/wiki/Producers-consumers_problem 35 loop -- Produce a data wait(emptyspots); -- Wait if no spot is empty -- Put the data into the buffer release(fullspots); -- Increment the number of filled spots end loop end producer; task consumer; loop wait(fullspots); -- Wait if no spot if filled -- Get a data from the buffer release(emptyspots); -- Increment the number of empty spots -- Consume the data end loop end consumer; Example: Producer-consumer problem (with mutual exclusion) Accessing the shared buffer should be a critical section. A semaphore access is used to lock the buffer. semaphore access; -- 0: unlocked, 1: locked access.count := 1; -- Initially the buffer is unlocked semaphore fullspots, emptyspots; fullspots.count := 0; emptyspots.count := BUFLEN; task producer; loop -- Produce a data wait(emptyspots); wait(access); -- Lock the buffer -- Put the data into the buffer release(access); -- Unlock the buffer release(fullspots); end loop end producer; task consumer; loop wait(fullspots); wait(access); -- Lock the buffer -- Get a data from the buffer release(access); -- Unlock the buffer release(emptyspots); -- Consume the data end loop end consumer; 11.5 Monitor A monitor is a syntactical structure of mutual exclusion. 36 • Inside a monitor, only one thread can be executed at a time. This enforces mutual exclusion. • A thread can sleep in a monitor, which enables another thread to enter the monitor. • A thread can wake up another thread sleeping in a monitor. It is less likely to make a mistake about mutual exclusion, compared to semaphore. Still, it is difficult to write cooperation synchronizations. Example: Producer-comsumer problem in Concurrent Pascal type databuf = monitor const bufsize = 100; var buf : array [1..bufsize] of integer; next_in, next_out : 1..bufsize; filled : 0..bufsize; sender_q, receiver_q : queue; procedure entry deposit(item : integer); begin if filled = bufsize then delay(sender_q); buf[next_in] := item; next_in := (next_in mod bufsize) + 1; filled := filled + 1; continue(receiver_q) end ; procedure entry fetch(var item : integer) ; begin if filled = 0 then delay(receiver_q); item := buf[next_out]; next_out := (next_out mod bufsize) + 1; filled := filled - 1; continue(sender_q) end; begin filled := 0; next_in := 1; next_out := 1 end; type producer = process(buffer: databuf); var newvalue : integer; begin cycle { produce newvalue } buffer.deposit(newvalue); end end; 37 type consumer = process(buffer: databuf); var stored_value : integer; begin cycle buffer.fetch(stored_value); { consume stored_value } end end; var new_producer : producer; new_consumer : consumer; new_buffer : databuf; begin init new_buffer, new_producer(new_buffer), new_consumer(new_buffer); end; Exercise 22 Which features in Java correspond to the following concepts? • monitor • sleeping in a monitor • waking up another thread 11.6 Channel Since it is error-prone to use shared memory with mutual exclusion, there is an idea that communication should be used for synchronization. It is theoretically based on process algebras. 11.6.1 Occam Occam was developed for Transputer, a microprocessor architecture for parallel computing of the 1980s. Concepts of Occam are taken from the communicating sequential processes (CSP) which is one of process algebras. Its program is a collection of processes communicating with each other through channels. A channel is a one-to-one, uni-directional, and synchronous (i.e. sending and receiving must take place simultaneously) communication devise. See An Introduction to the occam Language26 for detail. 11.6.2 Go Go was first announced by Google in 2009, and Version 1 was released in March 2012. A goroutine is a function executing concurrently with others. Two goroutines can communicate via a channel. A channel may be buffered (asynchronous) or unbuffered (synchronous). See The Go Programming Language27 for detail. 26 http://frmb.org/occtutor.html 27 http://golang.org 38 12 Functional Programming 12.1 Referential transparency Functional programming is a paradigm where computation is done by reduction via function application. Its most essential property is the following: Referential transparency the value of an expression solely depends on its subexpressions (i.e. literaly same expressions always result in the same value). Referential transparency guarantees that there is no need to take states into consideration. In contrast, values of expressions vary in the course of execution in imperative (procedural) languages because values of variables can be changed by assignments Variable names are directly bound to values in functional languages (cf. ??). Once it is bound, it will never change. However, the same name may be bound to another value in another environment. You may think switching environments is practically same as assigning values. However, environments are usually associated with syntactic structures and switched in a nesting manner, while assignments are non-structured and destructive operations. 12.2 Lisp Lisp is a multi-paradigm language. It has functional features as well as imperative features such as assignments and iterations. A subset of Lisp that has only functional features is sometimes used for educational or research purpose. • Extremely simple syntax (S-expression). • List is the main data structure. • No type declaration. • Functions are first-class objects. • Suitable for reflection because both programs and data are represented by lists. Example: Higher order function (mapcar (lambda (b) (* b b)) ’(1 2 3 4)) 12.3 12.3.1 Haskell Static type checking and type inference See ??. Type system is not directly related to functional paradigm, but many functional languages have powerful type systems. The reason is that we can find many bugs by investigating types because higher-order functions have complex types which bring rich information. 39 12.3.2 Lazy evaluation See ??. Lazy evaluation is also not directly related to functional paradigm. It is difficult for imperative languages to use lazy evaluation since we cannot tell when side-effects occur. Example: Infinite list Suppose we want to check if a certain integer is equal to the square of another integer. First, we define an infinite list squares as follows: squares = [ n * n | n <- [ 0 .. ] ] Function member searches an element in a list. For example, to check if 16 is equal to the square of some integer, we can write as follows: member squares 16 However, if the definition of member is as follows, it does not work. member [] b = False member (a:x) b = (a == b) || member x b The proper definition of member is as follows, and we need an assumption that the list is sorted in ascending order. member (a:x) b | a < b = member x b | a == b = True | otherwise = False 12.3.3 Monad If we rigorously stick to referential transparency, it is impossible to implement input/output or random number. Therefore, Haskell provides monad to simulate the notion of state. See Wikibooks28 for detail. 13 13.1 Logic Programming Features of logic programming • Based on formal logic (usually first-order predicate logic). • Execution of a program corresponds to a proof of a theory. • Declarative programming (i.e. the order of program components does not affect the semantics of the program). • Variables are bound to values (same as functional languages). 28 http://en.wikibooks.org/wiki/Haskell/Understanding_monads 40 13.2 Prolog 13.2.1 Definition of programs Program is a set of Horn clauses. A Horn clause is a logical formula of the form: P ← Q1∧Q2∧Q3∧...∧Qn Intuitively, it can be read as ‘In order to prove P, prove Q1∧Q2∧Q3∧. . . ∧Qn’. We call it a rule if n>0, a fact if n=0. Example: Appending lists append([], Y, Y). append([H | X], Y, [H | Z]) :- append(X, Y, Z). 13.2.2 Query Action of Prolog interpreter is as follows: 1. Input a query. 2. Determine whether the query is provable from the program. 3. If there is no variable in the query, output “yes” or “no”. 3. If there are variables in the query, output values of variables that conform one of provable instances. Example: Append two lists Is it true that appending [a,b] and [c,d] results in [a,b,c,d] ? ?- append([a, b], [c, d], [a, b, c, d]). yes What is the result of appending [a,b] and [c,d] ? ?- append([a, b], [c, d], Z). Z = [a, b, c, d] Appending [a,b] and what results in [a,b,c,d] ? ?- append([a, b], Y, [a, b, c, d]). Y = [c, d] Appending what and [c,d] results in [a,b,c,d] ? ?- append(X, [c, d], [a, b, c, d]). X = [a, b] 13.2.3 Program execution Prolog interpreter searches a proof of the given query using unification and resolution. 1. Make a negation ¬Q (called a goal clause) of the given query Q . 2. Try to find a contradiction between ¬Q and the program P . 3. If a contradiction is found between ¬Q’, an instance of ¬Q, and P, Q’ is provable by contradiction. 41 13.3 Problems of Prolog 13.3.1 Order of resolution In order to do declarative programming, the interpreter should non-deterministically select literals used in resolutions. However, most of existing Prolog interpreters select literals in deterministic order (from top to bottom, left to right). This is because 1) to make interpreters simpler, and 2) to write efficient programs that take advantage of this order. In exchange, programs may fall into infinite loops depending on the order of literals. Example: Infinite loop Provided that a relation parent is defined, we want to define a transitive relation ancestor. The following program will fall into an infinite loop. ancestor(X, X). ancestor(X, Y) :- ancestor(Z, Y), parent(X, Z). The following will not. ancestor(X, X). ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y). 13.3.2 Negation If a query Q is not provable, it does not necessarily mean Q is false. However, most of Prolog interpreter assumes ¬Q is true (Q is false) if it cannot find a proof of Q. This rule is called negation as failure. It is different from logical negation. 42