300 likes | 477 Views
Functional Programming, an introduction. Program looks like function The contents here are from Miranda book and haskell.org. Why use functional programming.
E N D
Functional Programming, an introduction Program looks like function The contents here are from Miranda book and haskell.org
Why use functional programming • Substantially increased programmer productivity (Ericsson measured an improvement factor of between 9 and 25 in one set of experiments on telephony software). • Shorter, clearer, and more maintainable code. • Fewer errors, higher reliability. • A smaller "semantic gap" between the programmer and the language. • Shorter lead times.
Functional languages are superb for writing specifications which can actually be executed (and hence tested and debugged).
What is functional programming? • A functional program is a single expression, which is executed by evaluating the expression. • The focus is on what is to be computed, not how it should be computed. For example:
Some example Haskell square x = x*x area = square side min x y = | x<=y = x |otherwise = y • We can run it using “min 3 4” • The result is 3
The point is expression evaluation • Square (3+4) square 7 7*7 49 Reduce by our definition Can’t reduce any more. We call this a normal form, or Canonical form
Quicksort concat qsort [] = [] qsort (x:xs) = qsort elts_lt_x ++ [x] ++ qsort elts_greq_x where elts_lt_x = [y | y <- xs, y < x] elts_greq_x = [y | y <- xs, y >= x] elts_lt_x is the list of all y's such that y is drawn from the list xs, and y is less than x
Our Quicksort has polymorphism • It can compare anything that < or > can compare
When C is better • In applications where performance is required at any cost, or when the goal is detailed tuning of a low-level algorithm, an imperative language like C would probably be a better choice than Haskell, exactly because it provides more intimate control over the exact way in which the computation is carried out.
Haskell types • Basic: Int, Double and String • Lists • Lists is a standard data type in Haskell • The nil list, or the "empty list", is written []. • [1,2,3,4]=1:2:3:4:[]. : is called “cons” • A list can be constructed from any type (even functions or user defined types), but all elements must be of the same type. • list from 1 to 10 we write [1..10] • [1,3..100], the first two elements give the "step" of the sequence.
Tuples • We can group types together into tuples. • For instance (2,4) is the tuple of the Ints 2 and 4. • Maybe you want to return two values from a function , in Haskell you just return a tuple of two data types) • Tuples aren't restricted to just two elements, (1,2,3) is the 3-tuple of the Ints 1,2 and 3. • Any data type can be put inside a tuple, even the ones you define yourself. • Contents of the tuple can be of differing types: ("Haskell", [1,2,3], 4.5). • Note that a list is also considered a type and can thus be put inside a tuple - this works the other way as well, you can have a list of tuples.
function • What is a function • A function takes a set of inputs, and returns a set of output. • A program in Haskell is just a single function. • A function in Haskell is also a value. • You can pass a function as a parameter to another function, for instance. • A function can also be returned by another function. You can even put a function in a list, or any other data construct.
f a b c = a+b+c • function name, f, • and then the parameters a, b and c. • On the right hand side of the equation comes the expression which the function will evaluate • To call a function g = f 1 2 3 • g a function with zero parameters that returns the expression on the right hand side.
no paranthesis in function calls • Parenthesis is used for grouping only.h = f (1+2) (2+3) (3+4)
Partial application • f above is a function with three parameters, but that's not the whole truth. Consider this function, which we define in terms of fj a b = f 5 a b • The function j takes two parameters which are added together with the integer 5.Now we will take a look at an alternate definition of this function.i = f 5 • This function is completely equivalent with the function j above. So how can that be? • We state that the function i is equal to the function f passed only a single parameter, 5. • But f was supposed to take three parameters, surely this must be a programming error?
This is completely valid Haskell syntax. • What this means is that you apply 5 to f, the result of this is a function that takes the two remaining parameters. • You can think of it as f "waiting" for the rest of the parameters. Thus i will be a function of two parameters, the two parameters that f is "waiting" for. • A nice way to think of this is that all functions are really just a function of zero or a single parameter. • The function f a b c is the function that takes one parameter, a, and returns a function that takes the remaining two parameters, b and c. • This function is in turn a function that also takes a single parameter, b, and returns a function that takes the last parameter, c. • This takes a single parameter, c, and returns the function of zero parameters that returns the value of a+b+c.
mul a b = a*b • All this function does is multiply its two parameters. We can now use this in conjunction with partial application to define this function double = mul 2 • We pass 2 to the function mul. • Now 2 is bound to the variable a. • This means that mul is "waiting" for the value of b. In other words; the result of mul 2 is a function taking the second parameter and multiplying it with two. • So since the value of the right hand side is a function of one parameter, double must also be a function of one parameter. We can write this explicitly if we wish:double x = mul 2 x
Type signatures • Notice that we have not written out any type signatures on any of the functions above. • This isn't needed, Haskell will infer it automatically mul :: Int -> Int -> Int mul a b = a*b A function from Int to Int to Int (Note that this definition of mul will only work on ints, whereas the version where we omitted the type signature will work for any type which as the * operator defined.)
Normally you consider the last part of the type signature to denote the type of the output. • But if you take partial application into account you could just as well consider the function mul to be "a function that goes from an Int to a function that goes from Int to Int". • In other words the output of the function is another function ("from Int to Int").mul :: Int -> (Int -> Int)mul a b = a*b
The type signature of a "variable" (or a function with zero parameters) is justa :: Stringa = "Hello"We just say that a is of type String (no arrows, unlike function).
Higher Order Functions • functions that take another function as a parameter • it is the whole point of Haskell • twice :: (Int -> Int) -> Int -> Inttwice f value = f (f value) • As we can see from the type signature this function will take a function of type "Int to Int" as a parameter, and then a value of type Int, then it will return a value of type Int.We can see from the definition that it will apply the function (which we assign the name f ), and then apply the function to the result.
f ( g x) • function composition • We can redefine twice like:twice :: (Int -> Int) -> Int -> Inttwice f = f . f • The . operater will apply the right most function to the parameter, and then the left most function to the result. • We can use our “twice” for any function • quadruple :: Int -> Intquadruple = twice double
Pattern Matching and Recursion • Let's define a recursive factorial function in Haskell:fac :: Int -> Intfac 0 = 1fac n = fac (n-1) * n Haskell will continue down to try to find a pattern that matches (top first).
Matching Higher Order Function map :: (Int -> Int) -> [Int] -> [Int] map f [] = [] map f (x:xs) = f x : map f xs • apply a function to all elements of a list • Try …. map double [1,2]
Summing elements in the list sum :: [Int] -> Int sum [] = 0 sum (x:xs) = x + sum xs • We can parameterize the function which is used on the list to combine the elements of the list. • So we'll need to pass • the function (which was +, in the case of sum) • and the base case (which was 0 in the case of sum).
foldr :: (Int -> Int -> Int) -> Int -> [Int] -> Intfoldr func base [] = basefoldr func base (x:xs) = x `func` (foldr func base xs)"fold right". • It take some function, func, and apply it to each element and the rest of the list ("folding it", into the list), exactly like we use + in the sum function. • The base value is the identity value of the function, for the sum function this was 0. It's the value which is returned for the empty list. • There's a bit of new syntax here. By enclosing a function of two parameters like so `func` we can actually make that function an infix operator (like + and *). The reverse is also possible, to transform an infix operator you enclose it in paranthesis (for example you could write (+) 4 3, to add 3 and 4, just as well as 4 + 3).
Now let's redefine the sum function using our new foldr function.sum :: [Int] -> Intsum = foldr (+) 0 • Let's define another function, product, using foldrproduct :: [Int] -> Intproduct = foldr (*) 1
Lambda Functions • construct a function on the fly. \a b c -> expr • The starting backslash is meant to look like a lambda symbol. • The a b, and c are the parameters, then there's an arrow and after that the expression. • The advantage of this notation is that you can create unnamed functions which can come in handy when you wish to map some operation on a list, for instance:func xs b = map (\a -> 2*a-b) xs • Here we create a function on the fly and send it off to the map function. So for small functions this can be very convenient.
The where notation • What if we have a function definition that won't fit on a single line? • We can use the where-notation to rewrite the map function above.map :: (Int -> Int) -> [Int] -> [Int]map f [] = []map f (x:xs) = f x : mappedwhere mapped = map f xs • Basically you can put a where clause underneath a function and make definitions there which can be used in the function itself. • These definitions are in the same scope as the function itself and as such it has access to the parameters of the function.