lecture 5: functional programming
Post on 10-May-2015
1.594 Views
Preview:
TRANSCRIPT
TI1220 2012-2013Concepts of Programming Languages
Eelco Visser / TU Delft
Lecture 5: Functional Programming
Around 1959, he invented so-called "garbage collection" methods to solve problems in Lisp. Based on the lambda calculus, Lisp soon became the programming language of choice for AI applications after its publication in 1960.
John McCarthy (September 4, 1927 – October 24, 2011) was an American computer scientist and cognitive scientist. He coined the term "artificial intelligence" (AI), developed the Lisp programming language family, significantly influenced the design of the ALGOL programming language, popularized timesharing, and was very influential in the early development of AI.
http://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)
OutlineFrom the lab:
Unit testingGraded assignment 1
Functional objects in ScalaPattern matchingRecursion and inductionAlgebraic data typesBinary treesAlgebraic data types in C
Messages from the Lab
Tests
• check that your code is correct
• regression testing: don’t make the same mistake twice
Coverage
• a test for each representative case
Test-driven development
• (1) define tests for representative cases
• (2) write code
• (3) test
import org.scalatest.Suite
class <NameOfTestClass> extends Suite { import <ClassUnderTest>._ def <testNameOfTest> { expect(<expected result>) { <computation> } }}
Unit Testing in Scala
/* import test framework .h */#include "solution.c"#include "CuTest.h"#include "CuJoin.h"
/* your imported libraries */#include <string.h>
/* signatures of all functions being tested */char* wc(char* data);
/* defined tests */void test_1(CuTest *tc) { char* wcout = wc("hello\n world"); char* expected = "2 2 10 12"; CuAssertTrue(tc, !strcmp(wcout,expected));}/* hook all your tests into the harness */void testHooker(CuSuite* intoSuite){ SUITE_ADD_TEST(intoSuite, test_1);}
Unit Testing in C
test("Changing properties", function() { var obj = {x : 3}; expect(5); ok(changeProp, "function exists"); equal(obj.x, 3); equal(obj.y, undefined); changeProp(obj); equal(obj.x, 42); equal(obj.y, 9);});
Unit Testing in JavaScript
Graded Assignment 1Algebraic datatypes in CDynamic dispatch in C
Important datesDeadline: April 2, 2013 23:59Extension: April 5, 2013 23:59
Submitting after extension date is not possibleMaximum penalty for submitting after deadline: 6 pointsMinimum grade needed: 4Grade: 70% unit tests, 30% check listsGrade for GAs: average of four assignments
abstract class XMLcase class Text(t: String) extends XMLcase class Elem(tag: String, elems: List[XML]) extends XML
object Solution {
def text(elems1: List[XML]): List[XML] = elems1.flatMap(_ match { case t@Text(_) => List[XML](t) case Elem(_, elems2) => text(elems2) }) }
Algebraic Datatypes in C
translate this Scala program to an equivalent C program
// values
abstract class Value { def value: Int def isFailure: Boolean def +(that: Value): Value def *(that: Value): Value}
object LookupFailure extends Value { def value: Int = 0 def isFailure: Boolean = true def +(that: Value) = LookupFailure def *(that: Value) = LookupFailure}
class IntValue(v : Int) extends Value { val value = v def isFailure: Boolean = false def +(that: Value) = that match { case v: IntValue => new IntValue(value + v.value) case _ => LookupFailure } def *(that: Value) = that match { case v: IntValue => new IntValue(value * v.value) case _ => LookupFailure }}
translate this Scala program to an equivalent C program
Dynamic Dispatch in C
// environments
abstract class Env { def lookup(x: String): Value}class MtEnv extends Env { def lookup(x: String): Value = LookupFailure}class Bind(key: String, value: Int, env: Env) extends Env { def lookup(x: String): Value = if(x == key) new IntValue(value) else env.lookup(x);}
Dynamic Dispatch in C
// expressions
abstract class Exp { def eval(env: Env): Value; override def toString: String}
class IntExp(value: Int) extends Exp { def eval(env: Env) = new IntValue(value) override def toString = value.toString}
class VarExp(name: String) extends Exp { def eval(env: Env) = env.lookup(name) override def toString = name}
class PlusExp(left: Exp, right: Exp) extends Exp { def eval(env: Env) = left.eval(env) + right.eval(env) override def toString = "(" + left.toString + " + " + right.toString + ")"}
class MulExp(left: Exp, right: Exp) extends Exp { def eval(env: Env) = left.eval(env) * right.eval(env) override def toString = "(" + left.toString + " * " + right.toString + ")"}
Dynamic Dispatch in C
Functional Programming (in Scala)
Ingredients of functional programming
Immutable objects
Pattern matching
Inductive (algebraic) data types
Recursive functions
First-class functions (next week)
Functional Objects in Scala
functional object: the fields of an object are immutable
Example: Rational Numbers
• Rational = Int x Int
• Notation: numerator/denominator
• Addition
• example: 1/2 + 2/3 = 3/6 + 4/6 = (3 + 4)/6 = 7/6
• general: n1/d1 + n2/d2 = (n1*d2 + n2*d1) / (d1*d2)
• Multiplication
• n1/d1 + n2/d2 = (n1 * n2) / (d1 * d2)
• Division
• n1/d1 / n2/d2 = n1/d2 * d2/n2
class Rational(n: Int, d: Int) { println("Created " + n + "/" + d)}
scala> new Rational(1, 2)Created 1/2res0: Rational = Rational@2d83e895
class parameters
Constructing a Rational
Immutable object trade-offs
Advantages
• easier reasoning
• pass around freely (no risk of undesired mutations)
• cannot be changed concurrently in two threads
• immutable object make safe hash table keys
Disadvantages
• copying large object graphs vs in-place update
class Rational(n: Int, d: Int) { override def toString = n + "/" + d}
Overriding Methods
scala> val half = new Rational(1, 2)half: Rational = 1/2
class Rational(n: Int, d: Int) { require(d != 0) override def toString = n + "/" + d}
scala> val half = new Rational(1, 0)java.lang.IllegalArgumentException: requirement failed
Checking Pre-conditions
Adding Rationals Functionally
class Rational(n: Int, d: Int) { require(d != 0) override def toString = n + "/" + d def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d)}
$ fsc Rational.scala Rational.scala:5: error: value d is not a member of Rational new Rational(n * that.d + that.n * d, d * that.d) ^Rational.scala:5: error: value d is not a member of Rational new Rational(n * that.d + that.n * d, d * that.d) ^two errors found
Visibility of Class Parameters
class Rational(n: Int, d: Int) { require(d != 0) override def toString = n + "/" + d def add(that: Rational): Rational = new Rational(n * that.d + that.n * d, d * that.d)}
class Rational(n: Int, d: Int) { require(d != 0) val numer: Int = n val denom: Int = d override def toString = numer + "/" + denom def add(that: Rational): Rational = new Rational( numer * that.denom + that.numer * denom, denom * that.denom)}
Functional Fields
scala> new Rational(1,2) add new Rational(2,3)res0: Rational = 7/6
class ImperativeRational(n: Int, d: Int) { require(d != 0) var numer: Int = n var denom: Int = d override def toString = numer + "/" + denom def add(that: ImperativeRational) { numer = numer * that.denom + that.numer * denom; denom = denom * that.denom; }}
scala> val half = new ImperativeRational(1, 2)half: ImperativeRational = 1/2
scala> val twothirds = new ImperativeRational(2,3)twothirds: ImperativeRational = 2/3
scala> half.add(twothirds)
scala> halfres1: ImperativeRational = 7/6
Non-functional Objects
Destructive Update
def lessThan(that: Rational) = this.numer * that.denom < that.numer * this.denom
def max(that: Rational) = if (this.lessThan(that)) that else this
this: optional when referring to fields
Self References
class Rational(n: Int, d: Int) { require(d != 0) val numer = n val denom = d def this(n: Int) = this(n, 1) // auxiliary constructor ...}
Auxiliary Constructors
scala> new Rational(6)res1: Rational = 6/1
class Rational(n: Int, d: Int) { require(d != 0) private val g = gcd(n.abs, d.abs) val numer = n / g val denom = d / g ... private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)}
Private Fields and Methods
scala> new Rational(6,42)res1: Rational = 1/7
def add(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom)
scala> new Rational(1,2).add(new Rational(2,3))res0: Rational = 7/6scala> new Rational(1,2) add new Rational(2,3) res0: Rational = 7/6
Using Functions as Infix Operators
def +(that: Rational): Rational = new Rational(numer * that.denom + that.numer * denom, denom * that.denom)
def *(that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom)
scala> val d = a + b * c d: Rational = 11/14scala> val d = a.+(b.*(c))d: Rational = 11/14scala> val d = a * b + c d: Rational = 16/21scala> val d = (a.*(b)).+(c)d: Rational = 16/21
Operator Identifiers
Invoking Operators
How many Rational objects are created while executing:
class Rational(n: Int, d: Int) { require(d != 0) val numer: Int = n val denom: Int = d override def toString = numer + "/" + denom def +(that: Rational): Rational = new Rational( numer * that.denom + that.numer * denom, denom * that.denom)}var half = new Rational(1,2)half = half + half + half
(a) 1(b) 2(c) 3(d) 4 Quiz
Alphanumeric identifier
• identifier: [$A-Za-z_][$A-Za-z_0-9]* ($ reserved for Scala compiler)
• camel-case convention: toString, HashSet
Operator identifier
• Unicode set of mathematical symbols(Sm) or other symbols(So), or to the 7-bit ASCII characters that are not letters, digits, parentheses, square brackets, curly braces, single or double quote, or an underscore, period,semi-colon, comma, or back tick character.
Literal Identifier
• arbitrary string enclosed in back ticks (` . . . `).Identifier Syntax
Method Overloading
scala> val c = new Rational(3,7)c: Rational = 3/7
scala> c * 2res1: Rational = 6/7
def *(that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom)
def *(i: Int): Rational = new Rational(numer * i, denom)
In a method call, the compiler picks the version of an overloaded method that correctly matches the
types of the arguments.
Method Overloading does not apply to this
def *(that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom)
def *(i: Int): Rational = new Rational(numer * i, denom)
scala> 2 * c <console>:7: error: overloaded method value * with alternatives: (Double)Double <and> (Float)Float <and> (Long)Long <and> (Int)Int <and> (Char)Int <and> (Short)Int <and> (Byte)Int cannot be applied to (Rational) 2 * c ^
Implicit Conversions
implicit def intToRational(x: Int) = new Rational(x)
scala> 2 * c res4: Rational = 6/7
def *(that: Rational): Rational = new Rational(numer * that.numer, denom * that.denom)
def *(i: Int): Rational = new Rational(numer * i, denom)
Functional Objects - Summary
Immutable objects
• class parameters
• immutable fields (val)
• methods don’t change object, but return value
Natural, concise notation
• methods as infix operators, operator identifiers
• method overloading
• implicit conversion
Pattern Matching
Match is similar to Java switch. Differences:
• Match is expression: returns a value
• Alternatives never fall through
• MatchError when no pattern matches
Scala’s Match
e0 match { case 1 => e1; case 2 => e2; case _ => e3 }
val firstArg = if (args.length > 0) args(0) else ""firstArg match { case "salt" => println("pepper") case "chips" => println("salsa") case "eggs" => println("bacon") case _ => println("huh?")}
Choosing between Actions
val firstArg = if (!args.isEmpty) args(0) else ""val friend = firstArg match { case "salt" => "pepper" case "chips" => "salsa" case "eggs" => "bacon" case _ => "huh?" }println(friend)
Choosing between Values
Constant Patterns
def describe(x: Any) = x match { case 5 => "five" case true => "truth" case "hello" => "hi!" case Nil => "the empty list" case _ => "something else"}
expr match { case 0 => "zero" case somethingElse => "not zero: " + somethingElse}
Pattern Variables
Recursion and Induction
Natural number
• 0 is a number
• if n is a number then n + 1 is a number
• nothing else is a natural number
Inductive Definitions
def property(n: Int): Boolean = n match { case 0 => // base case case m => ... property (m - 1) ... // recursive case for n + 1 }
def f(n: Int): Int = n match { case 0 => // base case case m => ... f(m - 1) ... // recursive case for n + 1 }
Induction Principle
def isEven(n: Int): Boolean = n match { case 0 => ? case m => ? } def isOdd(n: Int): Boolean = n match { case 0 => ? case m => ? }
def isEven(n: Int): Boolean = n match { case 0 => true case m => isOdd(m - 1) } def isOdd(n: Int): Boolean = n match { case 0 => false case m => isEven(m - 1) }
def power(n: Int, exp: Int): Int = exp match { case 0 => ? case m => ?
}
def power(n: Int, exp: Int): Int = exp match { case 0 => 1 case m => if(exp % 2 == 1) n * power(n, m - 1) else power(n * n, m / 2) }
Algebraic Data Types
Natural number
• 0 is a number
• if n is a number then n + 1 is a number
List
• Empty list is a list
• If L is a list then adding an element in front of L produces a list
Inductive Data Structures
abstract class IntList
case class Nil() extends IntList
// Nil() is a list (the empty list)
case class Cons(hd: Int, tail: IntList) extends IntList
// if hd is an Int and tail is an IntList// then Cons(hd, tl) is an IntList
Scala: Case ClassesNil()Cons(1, Nil())Cons(2, Cons(1, Nil()))Cons(1, Cons(2, Cons(3, Nil())))...
abstract class IntListcase class Nil() extends IntListcase class Cons(hd: Int, tail: IntList) extends IntList
def f(xs: IntList): T = xs match { case Nil() => // base case case Cons(x, ys) => ... f(ys) ... // recursive case }
Induction Principle for IntList
def length(xs: IntList): Int = xs match { case Nil() => ? case Cons(x, ys) => ? }
length of list xs
def length(xs: IntList): Int = xs match { case Nil() => 0 case Cons(x, ys) => 1 + length(ys) }
length of list xs
def sum(xs: IntList): Int = xs match { case Nil() => ? case Cons(x, ys) => ? }
sum of the integers in xs
def sum(xs: IntList): Int = xs match { case Nil() => 0 case Cons(x, ys) => x + sum(ys) }
sum of the integers in xs
def product(xs: IntList): Int = xs match { case Nil() => ? case Cons(x, ys) => ? }
product of the integers in xs
def product(xs: IntList): Int = xs match { case Nil() => 1 case Cons(x, ys) => x * product(ys) }
product of the integers in xs
def append(xs: IntList, ys: IntList): IntList = xs match { case Nil() => ? case Cons(x, zs) => ? }
append elements of ys to xs
def append(xs: IntList, ys: IntList): IntList = xs match { case Nil() => ys case Cons(x, zs) => Cons(x, append(zs, ys)) }
append elements of ys to xs
def reverse(xs: IntList): IntList = xs match { case Nil() => ? case Cons(y, ys) => ? }
elements of xs in reverse
def reverse(xs: IntList): IntList = xs match { case Nil() => Nil() case Cons(y, ys) => append(reverse(ys), Cons(y, Nil())) }
elements of xs in reverse
def reverse(xs: IntList): IntList = reverseAcc(xs, Nil()) def reverseAcc(xs: IntList, rest: IntList): IntList = xs match { case Nil() => rest case Cons(y, ys) => reverseAcc(ys, Cons(y, rest)) }
reverse in linear time using accumulator
def reverse(xs: IntList): IntList = xs match { case Nil() => Nil() case Cons(y, ys) => append(reverse(ys), Cons(y, Nil())) }
def filterEven(xs: IntList): IntList = xs match { case Nil() => Nil() case Cons(y, ys) => if(y % 2 == 0) Cons(y, filterEven(ys)) else filterEven(ys) }
the list of elements of xs that are even
def insert(x: Int, xs: IntList): IntList = xs match { case Nil() => Cons(x, Nil()) case Cons(y, ys) => if(x < y) Cons(x, Cons(y, ys)) else Cons(y, insert(x, ys)) } def isort(xs: IntList): IntList = xs match { case Nil() => Nil() case Cons(y, ys) => insert(y, isort(ys)) }
sort elements in ascending order
def msort(xs: IntList): IntList = { val n = lenght(xs) / 2 n match { case 0 => xs case _ => val (as, bs) = splitAt(xs, n) merge(msort(as), msort(bs)) }}
Merge Sort
def splitAt(xs: IntList, n: Int): (IntList, IntList) = xs match { case Nil() => (Nil(), Nil()) case Cons(y, ys) => n match { case 0 => (Nil(), xs) case _ => val (as, bs) = splitAt(ys, n - 1) (Cons(y, as), bs) } }
define merge(xs: IntList, ys: IntList): IntList = xs match { case Nil() => ys case Cons(a, as) => ys match { case Nil() => xs case Cons(b, bs) => if(a < b) Cons(a, merge(as, ys)) else Cons(b, merge(xs, bs)) } }
Merge Sort
abstract class IntListcase class Nil() extends IntListcase class Cons(hd: Int, tail: IntList) extends IntList
def f(xs: IntList): T = xs match { case Nil() => // base case case Cons(x, ys) => ... f(ys) ... // recursive case }
Induction Principle for IntList
Binary Trees
abstract class BinTree
case class Empty() extends BinTree
case class Node(left: BinTree, value: Int, right: BinTree) extends BinTree
Empty()
Node(Empty(), 42, Empty())
Node(Empty(), 5, Node(Empty(), 42, Empty()))
Node(Node(Empty(), 2, Empty()), 5, Node(Empty(), 42, Empty()))
def f(t: BinTree): A = t match { case Empty() => ... case Node(t1, i, t2) => ... f(t1) ... f(t2) ... }
def replace(x: Int, y: Int, t: BinTree): BinTree = t match { case Empty() => ? case Node(l, n, r) => ?
}
replace occurrence of x by y
def replace(x: Int, y: Int, t: BinTree): BinTree = t match { case Empty() => Empty() case Node(l, n, r) => Node( replace(x, y, l), if(n == x) y else n, replace(x, y, r) ) }
replace occurrence of x by y
def toList(t: BinTree): IntList = t match { case Empty() => ? case Node(l, n, r) => ?}
transform binary tree to list
def toList(t: BinTree): IntList = t match { case Empty() => List() case Node(l, n, r) => append(toList(l), Cons(n, toList(r)))}
transform binary tree to list
Invariant:
Node(t1, n, t2)- all numbers in t1 are smaller than n- all numbers in t2 are larger than n
Functions:def insert(x: Int, t: BinTree): BinTreedef lookup(x: Int, t: BinTree): Boolean
Correctnesslookup(x, insert(x, t)) == true
Binary Search Trees
def lookup(x: Int, t: BinTree): Boolean = { t match { case Empty() => ? case Node(left, value, right) => ?
} }
Lookup in Binary Search Tree
def lookup(x: Int, t: BinTree): Boolean = { t match { case Empty() => false case Node(left, value, right) => if(x < value) lookup(x, left) else if (x > value) lookup(x, right) else true } }
Lookup in Binary Search Tree
def insert(x: Int, t: BinTree): BinTree = { t match { case Empty() => ? case Node(left, value, right) => ?
} }
Insert in Binary Search Tree
def insert(x: Int, t: BinTree): BinTree = { t match { case Empty() => Node(Empty(), x, Empty()) case Node(left, value, right) => if(x < value) Node(insert(x, left), value, right) else if(x > value) Node(left, value, insert(x, right)) else t } }
Insert in Binary Search Tree
def toBinTree(xs: IntList): BinTree = xs match { case Nil() => Empty() case Cons(y, ys) => insert(y, toBinTree(ys))}
def listSort(xs: IntList): IntList = toList(toBinTree(xs))
Insert in Binary Search Tree
Algebraic Datatypes in C
Based on: Simple algebraic data types for C by Pieter Hartel and Henk Muller. Version 8, 2nd September, 2010
abstract class Treecase class Leaf(v: Int)case class Branch(v: Int, left: Tree, right: Tree)
def sum(t: Tree): Int = t match { case Leaf(v) => v case Branch(v, left, right) => v + sum(left) + sum(right)}
Algebraic Data Types in Scala
typedef struct tree_struct { int val; struct tree_struct *left; struct tree_struct *right;} tree;
tree *mkBRANCH(int val, tree *left, tree *right) { tree *result = calloc(1, sizeof(struct tree_struct)); if (result == NULL) { printf("panic\n"); } result->val = val; result->left = left; result->right = right; return result;}
ADT K&R Style
int krsum1(tree *cur) { if (cur == NULL) { return 0; } else { return cur->val + krsum1(cur->left) + krsum1(cur->right); }}int krsum2(tree_cc *cur) { /*assert cur->left==NULL <==> cur->right==NULL*/ if (cur->left == NULL) { return cur->val; } else { return cur->val + krsum1(cur->left) + krsum2(cur->right); }}void test() { tree *r = mkBRANCH(30, NULL, NULL); tree *l = mkBRANCH(20, NULL, NULL); tree *t = mkBRANCH(10, l, r); printf("%d\n", krsum1(t));}
ADT K&R Style
No explicit cases for Leaf and Branch
• distinction by use of NULL values for branches
• Does not cater for more alternatives
Confusion about NULL
• uninitialized value
• end-of-list, leaf-of-tree
• => errors
Explicit allocation of tree nodes
ADT K&R Style
typedef enum { LEAF = 0, BRANCH = 1} tree_tag;
typedef struct tree_struct { tree_tag tag; char *filename; union { struct { int _val; } _LEAF; struct { int _val; struct tree_struct *_left; struct tree_struct *_right; } _BRANCH; } data;} tree;
Algebraic Data Type with Union
tree *mkLEAF(int _val) { tree *result = calloc(1, sizeof(struct tree_struct)); if (result == NULL) { printf("panic\n"); } result->tag = LEAF; result->data._LEAF._val = val; return result;}
tree *mkBRANCH(int val, tree *left, tree *right) { tree *result = calloc(1, sizeof(struct tree_struct)); if (result == NULL) { printf("panic\n"); } result->tag = BRANCH; result->data._BRANCH._val = val; result->data._BRANCH._left = left; result->data._BRANCH._right = right; return result;}
Algebraic Data Type with Union
int sum(tree *t) { if (t == NULL) { return 0; } else if (t->tag == LEAF) { return t->data._LEAF._val; } else { // t->tag == BRANCH return t->data._BRANCH._val + sum(t->data._BRANCH._left) + sum(t->data._BRANCH._right); }}
Recursive Functions on ADT
Reading & Programming in Week 5
Reading
Sebesta Chapter 6:
Scala Chapter 6: Functional Objects
Scala Chapter 15: Case Classes and Pattern Matching
Week 6: First-Class Functions
WebLab: C, JavaScript, Scala tutorialsGraded Assignment 1: Dynamic Dispatch in C (deadline 2 April 2013, 23:59)
top related