lecture 5: functional programming

Post on 10-May-2015

1.588 Views

Category:

Documents

3 Downloads

Preview:

Click to see full reader

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