Programming Language Theory

advertisement
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
Download