Principles of Programming Languages www.cs.bgu.ac.il/~ppl172 Lesson 6 - Defining a Programming Language Bottom Up Collaboration and Management - Elements of Programming Dana Fisman 1
What we accomplished so far Different programming languages encourage different programming practices (and make other difficult) o IP (imperative programming) vs. FP (functional programming) o FPs tools include: - first class citizen functions - immutability - functional abstractions 2
What we accomplished so far Two aspects in program definition: o The syntactic domain: Expressions - created inductively from atoms using combination operators o The semantic domain: Values - computed inductively when evaluating expressions o Operational semantics: give meaning to expressions by describing the evaluation process 3
What we accomplished so far Data types denotes sets of values on which operations can be preformed uniformly o Primitive types o User-defined types Data types can be related to one another using set relations (subsumed-in, disjoint, ) Data types can be created from other data types using set operations (union, intersection, product) 4
What we accomplished so far We can use a type-language to associate datatypes to expressions othe type-language is build inductively using the type-system) Some programs allow programmers to annotate variables and functions with type annotations A type checker can establish that a given program satisfies the type declaration constraints. 5
Our next mission Learn a small functional programming language with a formal definition of its syntax a formal definition of its operational semantics build an interpreter - a program computing the value of expressions following the inductive definition provided by the operational semantics Semantics: <expr> -> <value> interpreters provide 6 Interpreter: a program implementing the semantics function a clarification mechanism an Illustration mechanism
Which language? What properties should a language that is good for learning principles of programming languages have? 7
Why Scheme In one word: Minimalistic a small language - a small number of primitives, - a small number of constructs - a small number of data-types common syntax to all composite expressions common semantics to all composite expression Compare to Javascript Yet: it is expressive: Turing complete allows powerful functional abstractions which make programming productive 8
Plan Introduce syntactic constructs and their semantics in a gradual manner L0 L1 L2 L3 9 Scheme
Specifying a programming language How do we specify a programming language? What are the elements that together define a language? The key elements are: Primitives Primitive literal values (numbers, booleans) Primitive operations (arithmetic ops, comparison ops) Combination means Means to create compound values Means to create compound expressions Abstraction Expressions Means to manipulate compound objects as standalone units Two aspects Values 10
L0 - Syntax Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, = Compound expressions 4. compound expressions (op expr 1 expr n ) 11
L0 - examples 8 (+ 1 7) 42 (- 45 3) 14 (* 2 7) 9 (/ 45 5) #f (> 2 7) 12
L0 - examples 16 (* (+ 1 1) (- 9 1)) #t (= (+ 1 5) (- 9 3)) (* (+ 1 1) (- 9 1)) 16 (= (+ 1 5) (- 9 3)) #t Why prefix notation? 10 (+ 1 2 3 4) 13
L0 - examples 4 4 #t #t + #<procedure:+> 14
L0 - Semantics Atomic expressions 1. Number literal expression 0, 1, 2, Evaluates to the corresponding numeric value 2. Boolean literal expression #t, #f Evaluates to the corresponding boolean value The atomic expressions evaluate to themselves 3. Primitive operation expression +, -, *, /, <, >, = Evaluates to the corresponding operation <procedure:op> Compound expressions 4. compound expressions (op expr 1 expr n ) 1. Let (val 0,val 1,,val n ) = (evaluate(op), evaluate(exp n ),, evaluate(exp n )) 2. Apply the procedure val 0 to the values val 1,,val n Recursive application 15
L0 - Syntax Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, = Compound expressions 4. compound expressions (op expr 1 expr n ) What is missing from L0? (+ (- (* 2 37) 18) (* 9 (- (* 2 37) 18)))) abstraction - one of the key components! 16
Naming 17 We will add to L0 a means to name expressions. 6 (define size 6) size (* 2 size) 12 (define <name> <expr>) (define area (* size size)) area 36 size in this context is called a variable a variable as in math rather than as in IP The relation between a variable and the value it denotes is called a binding
Evaluating the define form (define <var> <expr>) The interpreter maintains a global environment: a table recording the bindings between variables and their values The evaluation rule for (define <var> <expr>) is 1. Let val = Evaluate(<expr>) 2. Add the binding (<var> val) to the global environment 3. Return void (= has no return value) 18 global env. variable value milion 1000000 pi 3.14159 size 6 global env. : variable => value
L1 - Syntax Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, = 4. Variable expression size, my-secret-num, Compound expressions 5. special compound expressions (define <var> <expr>) 6. Non-special compound expressions (expr 0 expr 1 expr n ) 19
L1 - examples (define size 6) (define pi 3.14) (define area (* (* size size) pi)) area 113.04 (+ area (* 2 3)) 119.04 In the evaluation of define the expr is evaluated variable value size 6 pi 3.14159 area 113.04 20
L1 - Semantics Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, = 4. Variable expression size, secret-num, Compound expressions 5. special compound expressions (define <var> <expr>) Lookup the variable in the env. Return the corresponding value special form 6. Non-special compound expressions (expr 0 expr 1 expr n ) non-special form 21 Evaluate: variable => value Need an evaluation rule for each of our constructs
special vs. non-special forms Two types of compound expressions (expr 0 expr 1 expr n ) non-special form all expressions are evaluated (keyword ) special form not all expressions are evaluate first expr is assumed to be a procedure first expr is a keyword 22 the evaluation rule is the fixed the evaluation rule depends on the keyword
Evaluation of Non-Special Forms (expr_0 expr_1 expr_2 expr_n) 1. Evaluate all subexpressions expr_i. The value of expr_0 must be of type Procedure, otherwise the evaluation fails (run-time error). 2. Apply the procedure which is the value of expr_0, to the values of the other subexpressions. 23
Evaluation of Non-Special Forms (expr_0 expr_1 expr_2 expr_n) The evaluation rule is recursive (* 3 (+ 2 (+ 1 100))) 309 (* 3 (+ 2 101)) (* 3 103) 24
L1 - Syntax Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, = 4. Variable expression size, my-secret-num, Compound expressions 5. special compound expressions (define <var> <expr>) 6. Non-special compound expressions (expr 0 expr 1 expr n ) 25 What is missing from L1? No conditional expressions No way to define functions (that receive parameters)
Defining procedures In Typescript they are called functions, in Scheme procedures. What defines a function? Domain Range Giving it a name or not is a different issue a function associates an element d of the domain to an element r of the range a computable function associates with an element d of the domain some transformation (computation) on d 26
Defining procedures Syntax of procedure definition (lambda <parameters> <func-body>) where the syntax for <parameters> is And the syntax for <func-body> is (<param_1> <param_2> <param_n>) <expr_1> <expr_2> <expr_n> 27
Defining procedures (lambda <parameters> <func-body>) 28 (lambda (x) (* x x)) (lambda (x y z) (+ x y z)) Such expressions result in a procedure definition Not a procedure application (lambda (x y) (+ (* x x) (* 2 x y) (* y y))) We can think of lambda as the constructor of functions
Applying procedures (<proc> <parameters>) ((lambda (x) (* x x)) 3) 9 my-proc ((lambda (x y z) (+ x y z)) 1 2 3) my-proc 6 ((lambda (x y) (+ (* x x) (* 2 x y) (* y y))) 1 2) my-proc 9 In the same way as we apply primitive procedures (+ x y) (proc x y) can be a user-defined procedure 29
Note the difference The special lambda form The non-special form (lambda (x) (* x x)) ((lambda (x) (* x x)) 3) #procedure 9 user defined procedure Declaration of an anonymous procedure (same as x => x*x in Typescript) Note the syntactic differences The common way to apply procedure on parameters 30 The result is the value procedure The result is a value
Evaluating procedure applications ((lambda (<params>) <expr1> <expr2> <exprn>) <act-params>) The <func-body> can be made of multiple expressions The evaluation rule evaluates all the expressions and returns the value of the last one. What is this useful for? 31
What is it useful for? 9 ((lambda (x) (+ x 100) (* x (+ x 329)) (* x x)) 3) ((lambda (x) (display x) (* x x)) 3) 39 Only the last expression affects the returned value Multiple expressions in the body are useful when side-effects are desired 32 3 9 ((lambda (x) (display x) (newline) (* x x)) 3) The expression for the returned value should always come last!
Naming procedures Every expression in Scheme can be named using the following syntax (define <name> <expr>) (define ten 10) ten 10 (define square (lambda (x) (* x x))) square name expr #<procedure:square> 16 (square 4) 33
Naming procedures 34 16 For naming functions we also have the following (syntactic sugaring) form (define (<name> <parameters>) <body-expr>) (define (square x) (* x x))) (define square square (lambda (x) (* x x))) #<procedure:square> These two forms have exactly the (square 4) same semantics! (define area (* (* size size) pi)) 113.04 Note the differences with area Had the function referred to a variable which is not local or a parameter, its current value would have been recorded as well! variable value size 6 pi 3.14159 area 113.04 square #(Closure (x) (* x x)) The closure
The if special form (if <predicate> <consequent> <alternative>) (if (< 1 100) "then-part" "else-part") "then-part" (if (< 100 1) "then-part" "else-part") "else-part" (if (< 1 100) (+ 2 2) (* 7 7)) 4 (if (< 100 1) (+ 2 2) (* 7 7)) 35 49
The if special form Is the alternative (else-part) evaluated when the condition is (evaluated to) true? (if (< 1 100) (/ 1 0) (* 7 7)) /: division by zero (if (< 1 100) (+ 2 2) (/ 1 0)) 4 36
Evaluating the if special form (if <predicate> <consequent> <alternative>) Let p = Evaluate (<predicate>) If p is #t, Evaluate(<consequent>) and return it Otherwise Evaluate(<alternative>) and return it 37 In the if special form one of the expressions is not evaluated Unlike the non-special form in which all expressions are evaluated
The cond special form -Examples (cond ((> 1 2) first" ) ((> 2 2) "second") ((> 3 2) third" )) "third" (cond ((> 1 2) first" ) ((> 2 2) "second") (else third" )) "third" (cond ((> 1 2) "first" "FIRST") ((> 2 2) "second" "SECOND") ((> 3 2) "third" "THIRD")) "THIRD" (cond ((> 1 2) (display "first") "FIRST") ((> 2 2) (display "second") "SECOND") ((> 3 2) (display "third") "THIRD")) 38 third"third"
The cold special form (cond (<pred 1 > <expr-1 1 > <expr-2 1 > <expr-n 1 >) (<pred 2 > <expr-1 2 > <expr-2 2 > <expr-k 2 >) (<pred n > <expr-1 n > <expr-2 n > <expr-m n >) (else <expr-1 e > <expr-2 e > <expr-l e >)) A syntactic sugaring of if Thus, the only expressions that are evaluated are those corresponding to the first predicate to evaluate to true Only the predicates until the first predicate to evaluate to true are evaluated 39
L2 - Example Implementing Newton's method for computing square roots Newton's method is stated as this algorithm: If g is non-zero guess for a square root of x, then (g + x/g) / 2 is a better approximation of x. This algorithm is iterative! Interestingly, we can implement it although we have no construct for iteration 40
L2 - Example Auxiliary functions 41
L2 - Example As long as the guess is not good enough, improve it improved guess 42 First guess is set to 1 Check the difference between guess*guess and x is small enough Improve the guess according to Newton s method
L2 - Syntax Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, = 4. Variable expression size, my-secret-num, Compound expressions 5. special compound expressions I. (define <var> <expr>) II. (lambda <parameters> <func-body>) III. (if <predicate> <consequent> <alternative>) IV. (cond ) 6. Non-special compound expressions (expr 0 expr 1 expr n ) 43
L2 - Semantics not all expressions are evaluated Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, = 4. Variable expression size, my-secret-num, Compound expressions 5. special compound expressions I. (define <var> <expr>) II. (lambda <parameters> <func-body>) III. (if <predicate> <consequent> <alternative>) IV. (cond ) all are 6. Non-special compound expressions (expr 0 expr 1 expr n ) 44
Review of Last Lecture L3 L2 L1 if define cond only primitive ops and types lambda recursive comb. of primitive ops. supports recursive procedures Scheme 45
What is missing from L2? Compound values! In Javascript we have: array [] map {} What are the minimal constructs that we need to add in order to obtain array and map capabilities? 46
The pair compound data type The pair compound data type combines two values into a single unit. To support this the language provides: A value constructor (called cons) Accessors to take apart a compound pair value: for the first (called car) for the second (called cdr) A type predicate to check whether a value belongs to the set of pair values (called pair?) An equality predicate for pairs (called equal?) 47 To support pairs we add 5 primitive functions: cons, car, cdr, pair?, equal?
Pair, and associated types Types of the pair constructor and accessors: cons: [T 1 *T 2 -> Pair(T 1,T 2 )] car: [Pair(T 1,T 2 ) -> T 1 ] cdr: [Pair(T 1,T 2 ) -> T 2 ] pair?: [Any -> boolean] equal?: [Any * Any -> boolean] 48
L3 - pair examples (define foo (cons 1 2)) (car foo) 1 (cdr foo) 2 (define bar (cons 1 2)) (equal? foo bar) #t (pair? foo) #t 49 (pair? 2) #f
L3 - nesting pairs (define p12 (cons 1 2)) (define p123 (cons p12 3)) (define p1234 (cons p123 4)) (define p12345 (cons p1234 5)) p12345 '((((1. 2). 3). 4). 5) (car p12345) '(((1. 2). 3). 4) 3 (cdr (car (car p12345))) (cdr p12345) 5 Syntactic sugaring 3 (cdaar p12345))) 50 Can we use this to implement lists? What about the empty list???
Lists Using literals A list is an inductive data type. The base case is the empty list The inductive case creates a non-empty list by combining an element with a list (define L1234 '(1 2 3 4))) L1234 E1234 '(1 2 3 4) '(1 2 3 4) Using the list constructor (define E1234 (list 1 2 3 4))) 51 (define Lemp '()) Lemp '() (define Eemp (list)) Eemp '() (define C1234 (cons 1 (cons 2 (cons 3 (cons 4 ())))))) C1234 '(1 2 3 4) Using the pair constructor
Lists Lists can indeed be implemented using pairs and the empty list (and in fact are implemented this way in scheme) But the language also offers special constructs to deal with lists: A list constructor : (list <el1> <eln>) A literal list : (<el1> <eln>) Accessors first, second, third, A type predicate to check whether a value belongs to the set of list values (called list?) An emptiness predicate for list (called empty?) 52
functions on list : length (define length (lambda (l) (if (empty? l) 0 (+ 1 (length (cdr l)))))) Recursive functions operating on inductive data types follow their inductive definition 53
functions on list : n-th (define nth (lambda (n l) (cond [(empty? l) '()] [(= n 0) (car l)] [else (nth (- n 1) (cdr l))]))) 54 (nth 2 '(1 2 3 4)) 3 What happened here? (nth 8 '(1 2 3 4)) '() (nth 0 '(1 2 3 4)) 1
functions on list : n-th (define nth (lambda (n l) (cond [(empty? l) '()] [(= n 0) (car l)] [else (display (list "n:" n " l:" l)) (newline) (nth (- n 1) (cdr l))]))) Recall: the debug printing must precede the last expression, which is the returned value (nth 8 '(1 2 3 4)) 55 (n: 8 l: (1 2 3 4)) (n: 7 l: (2 3 4)) (n: 6 l: (3 4)) (n: 5 l: (4)) '()
L3 - Syntax Atomic expressions 1. Number literal expression 0, 1, 2, 2. Boolean literal expression #t, #f 3. Primitive operation expression +, -, *, /, <, >, =, cons, car, cdr, pair?, list?, empty? 4. Variable expression size, my-secret-num, 5. The empty list literal expression `() Compound expressions 6. special compound expressions I. (define <var> <expr>) II. (lambda <parameters> <func-body>) III. (if <predicate> <consequent> <alternative>) IV. (cond ) 7. Non-special compound expressions (expr 0 expr 1 expr n ) 56