Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Lectures 5 and 6 Imperative languages Iztok Savnik, FAMNIT November, 2015. 1 Literature • John Mitchell, Concepts in Programming Languages, Cambridge Univ Press, 2003 (Chapters 5 and 8) • Michael L. Scott, Programming Language Pragmatics (3rd ed.), Elsevier, 2009 (Chapters 6 and 8) • Lecture by Jan Karabaš, Concepts of imperative programming, Programming II, FAMNIT, 2015 • Iztok Savnik, Koncepti programskih jezikov, Skripta (in Slovene), FAMNIT, 2013 (Chapter 4) Outline 1. Imperative vs. functional lanuages 2. Program memory and variables 3. Sequential control 4. Procedures and functions 5. Patterns 6. Arrays Functional vs. imperative approach • Functional programming – Program is a function – Outcome is done by the evaluation of the function – Does not matter how the result is produced • Imperative programming – Program is sequence of instructions (states of the machine) – Outcome is build in the execution of instructions – Instruction changes the contents of main memory Functional vs. imperative approach • Two abstract computation models – Functional – Imperative • Functional programming – λ-calculus, recursively-enumerable functions – Abstract machine (program) is represented by a λ-term, the outcome is obtained by its reduction • Imperative programming – The abstract machine is a Turing machine (finite state automaton), – The outcome is obtained when the final state is reached Example Let us compute the greatest common divisor of two integers C++ OCaml let rec gcd x y = if y = 0 then x else gcd y (x mod y);; Functional: values, recursion Imperative: variables, loop, sequence int gcd(int x, int y) { x = abs(x); y=abs(y); while (y != 0) { int t = x % y; x = y; y = t; } return x; } Imperative programming • Machine has read-write memory for storing variables • (Turing) machine consists of the states and instructions, which define computations and transitions between states • Execution means the changes of states of the machine rather than the evaluation (reduction) • The (way of) transitions are shaped (controlled) by the values of variables • Execution of instruction can change the values of variables - side-effects Concepts of imperative languages • • • • • Read-write memory, variables Instructions and sequences of instructions Blocks Conditional statements Loops - conditional loops, iterations through ranges or through containers • Procedures and functions Program memory Variables • Memory of a program is organised into memory cells • Any cell can be accessed via its address; an integer, usually in range 0 – (262 − 1) • Variable is a symbolic name for a memory space of given size, which can be accessed by using the name instead the address of the memory cell • Every program has symbol table, which stores the information about variables – Every record consists of: name (identifier), the address of the beginning of memory space, the size of allocated memory – Table changes during the execution of the program. Operations with variables • Program must allocate the memory space before the variable is used • The allocation can be either static or dynamic • Program read the contents of the memory in the moment we refer to the identifier (name) • The contents of the memory referred by a variable is changed by assignment • The variable is freed either on the end of execution or on demand • Possible problems: – read/write to unallocated memory, concurrent write, memory leak. Variables in C • Two important operators – Operator »&«: returns address of variable – Operator »*«: returns value of valiable at given address int x = 1, y = 2, z = 3; int *ip; ip = &x; y = *ip; *ip = 0; *ip = *ip + 10; y = *ip + 1; *ip += 1; ++*ip; (*ip)++; swap(&a, &b); ... void swap(int *px, int *py) { int temp; /* interchange *px and *py */ temp = *px; *px = *py; *py = temp; } Pointers and arrays in C • C has pointer arithmetic int a[10]; // a[0], a[1],..., a[9] pa = &a[0]; /* strlen: return length of string s */ int strlen(char *s) { int n; for (n = 0; *s != '\0', s++) n++; return n; } Variables in OCaml type ’a ref = {mutable contents:’a} • OCaml has weaker, but safer model of a variable – They are realised as the values of special reference type – Reference is initialised on creation by the referenced value – Memory space is automatically allocated using the type of referenced value – Function cannot be referenced – Assignment is a special function with resulting type unit – Reading has the type of the referenced variable – We do not have full access to the program’s memory – no pointers, no pointer arithmetics • Variables are similar to reference concept in C++ Example # let x = ref 2 ;; (* declaration and allocation val x : int ref = {contents=2} # !x;; (* reading, notice operator ‘!‘ - : int = 2 # x ;; (* reading of the reference - : int ref = {contents=2} # x := 5;; (* assignment - : unit = () # !x;; - : int = 5 # x := !x * !x;; - : unit = () # !x;; - : int = 25 (* reading, operation, and assignment Sequential control Sequential control is one of the basic principles of imperative programming languages 1. Sequences 2. Condition statements 3. Loops 4. Patterns Sequence • Sequence is foundamental abstraction used to describe algorithms – Instruction cycle of Von Neumann model LOOP: execute *PC; PC++; goto LOOP; // execute instruction referenced by PC // increment programm counter (PC) by 1 // loop • Sequence of instructions change the state of variables (memory) int t = x % y; x = y; y = t; Sequences in OCaml let t = ref 0 let x = ref 42 let y = ref 28 in t:= x mod y; x:=!y; y:=!x ;; • Syntax of OCaml sequences <expr1>; <expr2>; <expr3> (* list of expressions *) • Every expression in a OCaml sequence must be of type unit. # print_int 1; 2; 3;; Warning 10: this expression should have type unit. 1- : int = 3 # print_int 2; ignore 4; 6;; 2- : int = 6 Blocks • Imperative languages are typicaly block-structured languages • Block is a sequence of statements enclosed by some structural language form – Begin-end blocks, loop blocks, function body, etc. • Each block is represented using activation record – – – – Includes parameters and local variables Includes memory location for return value Includes control pointers to be detailed in next lectures Control pointers are used to control computation • Activation records are allocated on program stack Conditional statements • Machine languages use instructions for conditional jumps – Initial imperative approach • OCaml syntax if <cond_expr> then <expr_true> else <expr_false> ;; • Conditional statements is concept shared between imperative and functional languages – Both branches must agree on type in Ocaml – Conditional statement in Ocaml has value # (if 1 = 0 then 1 else 2) + 10;; - : int = 12 Loops – while-do • Repeat a block of commands while (or until) a condition is satisfied – Loop body changes the state of program • Statement while in OCaml while <cond_expr> do <sequence> done # let x = ref 42;; val x : int ref = {contents = 42} # let y = ref 28;; val y : int ref = {contents = 28} # let t = ref 0 in while !y != 0 do t := !x mod !y; x := !y; y := !t done;; - : unit = () # x;; - : int ref = {contents = 14} Loops – for statement • Statement for is classical construct of imperative # for i=1 to 10 do prog. language print_int i; Statement for in OCaml • print_string " " for <sym> = <exp1> to <exp2> do <exp3> done for <sym> = <exp1> downto <exp2> do <exp3> done done; print_newline ();; 1 2 3 4 5 6 7 8 9 10 - : unit = () # for i=10 downto 1 do print_int i; print_string " " done; print_newline () ;; 10 9 8 7 6 5 4 3 2 1 - : unit = () Loops – do-while statement • Loop condition is at the end of loop block do-while syntax /* itoa: convert n to characters in s */ do <sequence> while <cond_expr> void itoa(int n, char s[]) { int i, sign; if ((sign = n) < 0) /* record sign */ n = -n; /* make n positive */ i = 0; • Statement repeat-until • Example: do { /* generate digits in reverse order */ s[i++] = n % 10 + '0'; } while ((n /= 10) > 0); if (sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); } /* get next digit */ /* delete it */ Loop control • Loop control in C programming language • Jumping out of loop – break • Jumping to loop condition – continue for (i = 0; i < n; i++) if (a[i] < 0) /* trim: remove trailing blanks, tabs, newlines */ int trim(char s[]) { int n; /* skip negative elements */ for (n = strlen(s)-1; n >= 0; n--) continue; if (s[n] != ' ' && s[n] != '\t' && s[n] != '\n') ... break; /* do positive elements */ s[n+1] = '\0'; return n; } Procedures and functions • Abstraction is a process by which the programmer can associate a symbol or a pattern with a programming language construct – Control and data abstractions • Subroutines are the principal mechanism for control abstraction – Part of program with well defined input and output is abstracted as subrutine, procedure, function – Subrutine performs operation on behalf of caller – Caller passes arguments to subrutine by using parameters – Subrutine that returns values is function – Subrutine that does not is called procedure Structured control • Structured and unstructured control flow – Unstructured: goto statements – Structured: syntactical constructs direct computation • Structured programming, 1970 – – – – »Revolution« in software engineering Top-down design (i.e., progressive refinement) Modularization of code Structured types (records, sets, pointers multidimensional arrays) – Descriptive variable and constant names – Extensive commenting conventions Structured control • Most structured programming ideas were implemented in imperative languages – Pascal, C, Modula, Ada, Oberon, ... • Most of modern algorithms can be elegantly expressed in imperative languages Procedures and functions • Most subroutines are parameterized • Procedure was first abstraction in Algol-family of programming languages – Formal and actual parameters of procedure procedure Proc(First : Integer; Second: Character); Proc(24,’h’); – Actual parameters are mapped to formal parameters • The most common parameter-passing modes – Some languages define a single set of rules that apply to all parameters (C, Fortran, ML, and Lisp) – Others have more modes of parameter passing (Pascal, C++, Ada, ...) Parameter passing • Input and output of procedure is realized by means of parameter passing – Passing values • C (only cbv), Java, Ocaml, C++, Pascal, ... – Passing references • Pascal, Fortran, ... – – – – Procedure Square(Index : Integer; Var Result : Integer); Begin Result := Index * Index; End Passing arrays, structures Missing and default parameters Named parameters Variable-length argument lists Passing values • The most commonly used method – Values of actual parameters are copied to formal parameters – Java uses only this method (arrays and structures are identified by reference) • Parameter is seen as local variable of procedure – It is initialized by the value of actual parameter int plus(int a, int b) { a += b; Java return a; } int f() { int x = 3; int y = 4; return plus(x, y); } Ocaml # let plus (a,b) = let a = a + b in a;; val plus : int * int -> int = <fun> # let f = let x = 3 and y = 4 in plus (x,y);; val f : int = 7 Passing references • Reference to variable is passed to procedure – Code of procedure is changing passed variable – All changes are retained after the call – Passed variable and formal parameter are aliases • Older method used in Fortran, Pascal, ... • Best method for larger structures Procedure plus(a: integer, var b: integer) { Begin b = a+b; End; Pascal Procedure p() { C void plus(int a, int *b) { *b += a; Var x: integer; } Begin void p() { x = 3; int x = 3; plus(4, x); plus(4, &x); End; } Variations on value and reference parameters • C++ references – In C++ references are made explicit – C++ implements call-by-reference void swap(int &a, int &b) { int t = a; a = b; b = t; } • Call-by-sharing – – – – Barbara Liskov, CLU (also Smalltalk) Objects (identifiers) are references No need to pass reference (to references) Just pass reference Variations on value and reference parameters • call-by-value/result – Actual params are copied to formal params initially – Result is copied back to actual parameter before exit • Read-only parameters – Modula-3 provided read-only params – Parameter values can not be changed – Read-only params are available also in C (const) • Parameters in Ada – in, out, in out (also in Oracle PL/SQL) – Named parameters / position parameters Variations on value and reference parameters • Default values of parameters – Ada, Oracle PL/SQL • Variable length argument lists – Programming language C, Perl, ... – No type-checking, no control, may be dangerous Example of functions in C #define MAXVAL 100 /* maximum depth of val stack */ int sp = 0; /* next free stack position */ double val[MAXVAL]; /* value stack */ /* push: push f onto value stack */ void push(double f) { if (sp < MAXVAL) val[sp++] = f; else /* pop: pop and return top val from stack */ double pop(void) { if (sp > 0) printf("error: stack full, can't push %g\n", f); return val[--sp]; } else { printf("error: stack empty\n"); return 0.0; } } Pattern matching • A special feature of languages of ML family – Haskell, Erlang, SML, ... – Close to Prolog unification – Very complex case statement • Declarative language construct ! – Logic based languages? – Not functional and not imperative? • Allows simple access to the components of complex data structures • Functions can be defined by cases – Pattern matching over over an argument Pattern matching • Patterns – Structure comprised of tuples, records, unions and lists including constants (of predefined types) and variables – Variables are »hooks« that catch the values – The symbol _ is called the wildcard pattern: matches to any data (as in Prolog) • The evaluation is parametrised by data: – when pattern in data is recognised, the corresponding expression is evaluated. Syntax and usage match <expression> with | <pattern_1> -> <expression_1> | <pattern_2> -> <expression_2> .... | <pattern_k> -> <expression_k> • Patterns in match must be of same type • Expressions in match must be of same type • Pattern must be linear - a variable can appear just once in a pattern • Patterns in match are tested sequentially (!) • The expression with the first match is evaluated • List of patterns in match must be exhaustive - every value must bematched with a pattern in the list • First pipe (character | on the first line) is optional Examples • Simple match: • With variable: # let imply v = match v with | (false,false) -> true | (false, true) -> true | (true, false) -> false | (true, true) -> true;; val imply : bool * bool -> bool = <fun> let imply2 a b = match (a,b) with | (true, x) -> x | (false,x) -> true;; Val imply2 : bool -> bool -> bool = <fun> • With wildchard pattern: let imply3 a b = match (a,b) with | (true,false) -> false | _ -> true;; let imply3 : bool -> bool -> bool = <fun> Linearity and completeness • Every pattern must be linear: let equal a b = match (a,b) with | (x,x) -> true | _ -> false;; Error: Variable x is bound several times in this matching # equal 1 2;; Error: Unbound value equal • Every pattern must be exhaustive: # let iszero x = match x with 0 -> true;; Warning 8: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: 1 val iszero : int -> bool = <fun> # iszero 0;; - : bool = true # iszero 10;; Exception: "Match_failure //toplevel//:3:-34". Combining patterns Pattern matching is sequential: 1. 'A' is »Consonant« 2. 'A' matched by »Vowel« # let char_discriminate c = match c with |’a’ | ’e’ | ’i’ | ’o’ | ’u’ | ’y’ | ’A’| ’E’ | ’I’ | ’O’ | ’U’ | ’Y’ -> "Vowel" | ’a’..’z’ | ’A’..’Z’ -> "Consonant" | ’0’..’9’ -> "Digit" | _ -> "Other";; val char_discriminate : char -> string = <fun> # val char_discriminate ’A’;; - : string = "Vowel" # val char_discriminate ’z’;; - : string = "Consonant" # val char_discriminate ’$’;; - : string = "Other" Matching on arguments • Pattern matching is used in an essential way for defining (unary) functions by cases. • Syntax: function | <pattern_1> -> <expression_1> .... | <pattern_k> -> <expression_k> • Indeed, the construction function <x> -> <expression>, is a definition by pattern matching using a single pattern reduced to one variable. # let rec sigma = function # let f = function (x,y) -> 2*x + 3*y;; val f : int * int -> int = <fun> # let f (x,y) = 2*x + 3*y + 4 ;; val f : int * int -> int = <fun> 0 -> 0 | x -> x + sigma (x-1) ;; val sigma : int -> int = <fun> # sigma 10 ;; - : int = 55 Named patterns • During pattern matching, it is sometimes useful to name part or all of the pattern. This is useful when one needs to take apart a value while still maintaining its integrity. # let less_rat pr = match pr with | ((_,0),p2) -> p2 | (p1,(_,0)) -> p1 | (((n1,d1) as r1), ((n2,d2) as r2)) -> if (n1*d2) < (n2*d1) then r1 else r2;; val min_rat : (int * int) * (int * int) -> int * int = <fun> • As a result, the value matched by the named pattern can be returned. Pattern guards • Guard is a conditional expression applied immediately after the pattern is matched. • Syntax: match <expression> with .... | <pattern_i> when <condition> -> <expression_i> .... • Example: # let eq_rat cr = match cr with ((_,0),(_,0)) -> true | ((_,0),_) -> false | (_,(_,0)) -> false | ((n1,1), (n2,1)) when n1 = n2 -> true | ((n1,d1), (n2,d2)) when ((n1 * d2) = (n2 * d1)) -> true | _ -> false;; val eq_rat : (int * int) * (int * int) -> bool = <fun> Examples • Size of a list: # let rec length = function | [] -> 0 | _::tl -> 1 + length tl;; val length : ’a list -> int = <fun> # length [1;2;3;4;5];; - : int = 5 • Appending of two lists: # let rec append = function | [], l -> l | hd::tl, l -> hd :: append (tl,l);; val append : ’a list * ’a list -> ’a list = <fun> # append ([1;2;3;4], [5;6;7]);; - : int list = [1; 2; 3; 4; 5; 6; 7] Examples • Membership test: • Intersection of two lists (as sets): # let rec contains e lst = match lst with | [] -> false | hd::_ when e = hd -> true | _::tl -> member e tl;; val contains : ’a -> ’a list -> bool = <fun> # contains 1 [1;2;3;4;5;6];; - : bool = true # contains 10 [1;2;3;4;5;6];; - : bool = false # let rec meet l1 l2 = match l1 with | [] -> [] | hd::tl when (contains hd l2) -> hd :: meet tl l2 | _::tl -> meet tl l2;; val meet : ’a list -> ’a list -> ’a list = <fun> # meet [1;2;3;4;5] [3;4;5;6];; - : int list = [3; 4; 5] Examples • Union of two lists (as sets): # let rec union l1 l2 = match l1 with | [] -> l2 | hd::tl when (contains hd l2) -> union tl l2 | hd::tl -> hd :: union tl l2;; val union : ’a list -> ’a list -> ’a list = <fun> # union [1;2;3;4;5] [4;5;6;10];; - : int list = [1; 2; 3; 4; 5; 6; 10] Arrays • Arrays are data structures holding the finite number of elements of certain data type • In imperative languages array is important data structure – Pascal, C, Algol, ... – Similar relationship as between functional PL and lists • An array is from definition mutable, but its size is fixed • Syntax in Ocaml: – [| elt_0; elt_1; ...; elt_{n-1} |] Arrays in OCaml # let v = [| 3.14; 6.28; 9.42 |] ;; val v : float array = [|3.14; 6.28; 9.42|] • Elements can be enumerated between [|...|] • Arrays are also integrated in Ocaml, but not so profoundly as lists • Similarly to lists there is a module Array that includes all necessary operations • Operations for # let v = Array.create 3 3.14;; val v : float array = [|3.14; 3.14; 3.14|] accessing array are integrated in Ocaml – Accessing an element – Setting new value expr1.( expr2 ) expr1.( expr2 ) <- expr3 Arrays in OCaml • Example: • Array index must not go accross the borders # v.(1) ;; - : float = 3.14 # v.(0) <- 100.0 ;; - : unit = () # v ;; - : float array = [|100; 3.14; 3.14|] # v.(-1) +. 4.0;; Uncaught exception: Invalid_argument("Array.get") • Checking that the index is not used outside borders is expensive – Some languages do not check this by default (C) # for i=0 to (n-1) do v.(i)<-i done;; Functions on arrays - : unit = () # let u = [|2;3|];; # v;; val u : int array = [|2; 3|] - : int array = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|] # let m = 2;; # let n = 10; val n : int = 10 # let v = Array.create n 0;; val v : int array = [|0; 0; 0; 0; 0; 0; 0; 0; 0; 0|] # let reverse v = let tmp=ref 0 val m : int = 2 # let subarray u v = let found = ref false and i = ref 0 in while ((!i<=(n-m)) && not !found) do found := true; for j=0 to (m-1) do if v.(!i+j) != u.(j) then and n = Array.length(v) found := false in for i=0 to (n/2-1) do tmp := v.(i); done; v.(i) <- v.(n-i-1); i := !i+1 v.(n-i-1) <- (!tmp); done;; done; !found;; - : unit = () val subarray : ’a array -> ’a array -> bool = <fun> # reverse(v);; # subarray u v;; - : int array = [|9; 8; 7; 6; 5; 4; 3; 2; 1; 0|] - : bool = true Example: subarray() # let prefix u v i = # let subarray u v = let found = ref true let found = ref false and m = Array.length(u) and i = ref 0 in for j=0 to (m-1) do and m = Array.length(u) if v.(i+j) != u.(j) then found := false done; !found;; val prefix : ’a array -> ’a array -> int -> bool = <fun> # prefix u v 0;; - : bool = false # prefix u v 3;; - : bool = true and n = Array.length(v) in while ((!i<=(n-m)) && not !found) do found := prefix u v !i; i := !i+1 done; !found;; val subarray : 'a array -> 'a array -> bool = <fun> Implementation of array • Array is represented in subsequent memory words – It is divided into n elements – Each element uses m memory words, depending on the size of element type – Elements are accessible via indexes 0 1 2 3 4 5 n-1 e0 e1 e2 e3 e4 e5 en-1 array-name Implementation of array v # let v = Array.create 3 0;; val v : int array = [|0; 0; 0|] # let m = Array.create 3 v;; 0 0 1 2 1 1 0 0 2 val m : int array array = [|[|0; 0; 0|]; [|0; 0; 0|]; [|0; 0; 0|]|] # v.(0) <- 1;; - : unit = () # m;; - : int array array = [|[|1; 0; 0|]; [|1; 0; 0|]; [|1; 0; 0|]|] # let v2 = Array.copy v ;; val v2 : int array = [|1; 0; 0|] # let m2 = Array.copy m ;; val m2 : int array array = [|[|1; 0; 0|]; [|1; 0; 0|]; [|1; 0; 0|]|] # v.(1)<- 352;; - : unit = () # v2;; - : int array = [|1; 0; 0|] # m2 ;; - : int array array = [|[|1; 352; 0|]; [|1; 352; 0|]; [|1; 352; 0|]|] m Matrices • Matrix has n rows and k columns • Implementation – One bock of memory (Fortran, Pascal, ...) – Each row in separate block (C, Ocaml, ...) # let m = Array.create_matrix 4 4 0;; val m : int array array = [|[|0; 0; 0; 0|]; [|0; 0; 0; 0|]; [|0; 0; 0; 0|]; [|0; 0; 0; 0|]|] # for i=0 to 3 do m.(i).(i) <- 1; done;; - : unit = () # m;; - : int array array = [|[|1; 0; 0; 0|]; [|0; 1; 0; 0|]; [|0; 0; 1; 0|]; [|0; 0; 0; 1|]|] # m.(1);; - : int array = [|0; 1; 0; 0|] Operations on matrices # let add_mat a b = let r = Array.create_matrix n m 0.0 in for i = 0 to (n-1) do for j = 0 to (m-1) do r.(i).(j) <- a.(i).(j) +. b.(i).(j) done done ; r;; val add_mat : float array array -> float array array -> float array array = <fun> # a.(0).(0) <- 1.0; a.(1).(1) <- 2.0; a.(2).(2) <- 3.0;; - : unit = () # b.(0).(2) <- 1.0; b.(1).(1) <- 2.0; b.(2).(0) <- 3.0;; - : unit = () # add_mat a b;; - : float array array = [|[|1.; 0.; 1.|]; [|0.; 4.; 0.|]; [|3.; 0.; 3.|]|]