===== Curs 03. Functii de ordin superior ===== /* Suppose we want to compute the sum of all integers from a range: */ def sumAll(start: Int, stop: Int): Int = { def loop (i: Int, crtSum: Int): Int = if (i > stop) crtSum else loop(i+1,crtSum+i) loop(start,0) } //... as well as the sum x*x of all elements def sumSquares(start: Int, stop: Int): Int = { def loop (i: Int, crtSum: Int): Int = if (i > stop) crtSum else loop(i+1,crtSum+i*i) loop(start,0) } // we can see that the code is completely the same, with the // exception of the accumulator. We can actually write a more general // implementation that can be used for both sums: def sumWithf(f: Int => Int, start: Int, stop: Int): Int = { def loop(i: Int, crtSum: Int): Int = if (i > stop) crtSum else loop(i+1,f(i)+crtSum) loop(start,0) } // How to use sumWithf? def id(x: Int): Int = x sumWithf(id,0,10) // In Scala we can define functions without naming them (like anonymous classes) // They are called anonymous functions, or shorter - "lambdas". We will see why later sumWithf( (x:Int) => x, 0, 10) // we can skip types in lambdas: sumWithf( (x) => x, 0, 10) // whenever our lambda has a single param, we can omit parentheses sumWithf(x => x, 0, 10) sumWithf(x => x*x, 0, 10) // Now, imagine the current setting. We have a very large array // of values, and we need to do some per element transformations // like x => x * x, only more elaborate. The transformations are only // a few. E.g. x => x, x => x * x, x => x * x * x. However we // need to apply them over a lot of ranges. We might write the code // like this: def alg1(x: Int): Int = x def alg2(x: Int): Int = x * x def alg3(x: Int): Int = x * x * x // now, we can use sumWithf(alg1,i,j) for all those i and j under scrutiny // this does not scale nicely. The programmer should be able // to write something like ApplyAlg1(i,j), without copy-pasting sumWithf, // and preferabily by using sumWithf. We can achieve this as follows, by // changing the design of f: def currySumWithf(f : Int => Int): (Int, Int) => Int ={ def sumWithf(start: Int, stop: Int): Int = { def loop (i: Int, acc: Int): Int = if (i > stop) acc else loop(i+1,f(i)+acc) loop(start,0) } sumWithf } val ApplyAlg1 = currySumWithf(x => x) val ApplyAlg2 = currySumWithf(x => x * x) ApplyAlg1(0,10) ApplyAlg2(0,10) // this is great, apart from the definition currySumWith f, which // has a very long signature which is hard to read. // This type of coding is so widespread that most languages have a special // syntax to make it nicer to write. def cleanSumWithf(f: Int => Int)(start: Int, stop: Int): Int = { def loop (i: Int, acc: Int): Int = if (i > stop) acc else loop(i+1,f(i)+acc) loop(start,0) } // we say that cleanSumWithf takes its parameters IN TURN. It first // takes a function f, and returns another function taking two integers: cleanSumWithf(x => x)(0,10) def fcurry(x: Int)(y: Int)(z: Int): Int = x + y + z // is fcurry(0,1,2) correct? // if fcurry(0)(1,2) correct? // we say that functions such as fcurry are in curry form. I.e. they take // parameters IN TURN. On the other hand: def funcurry(x: Int, y: Int, z: Int): Int = x + y + z // is uncurried. // We generally use curried functions whenever they help is build partial // functionality that can be used in other places by the programmer. // For instance sumWithf(x => x*x) designates Algorithm2 which can be // applied on different ranges or passed as a parameter. // A lot of library functions in Scala are curried, and we will see many examples // as soon as we introduce lists. // For now, we will stick to very basic examples which we use to practice. // Suppose we want to combine two algorithms into one. For instance // build x^5 by doing alg1(alg2(x)). There exists functional composition // in Scala, but because everything is an object, including functions, // we have to postpone it until we discuss objects in Scala. //def compose(f: Int => Int, g: Int => Int): Int => Int // sometimes, tracking types can be eased by introducing type aliases: type Algorithm = Int => Int /* def compose(f: Algorithm, g: Algorithm): Algorithm = { def comp(x: Int): Int = f(g(x)) comp }*/ // we can improve this code to: def compose(f: Algorithm, g: Algorithm): Algorithm = x => f(g(x)) type Reducer = (Int, Int) => Int val Alg1and2: Reducer = cleanSumWithf(compose(x => x,x => x * x)) Alg1and2(0,10) //Suppose we define: // f(x) = y type Fun2D = Int => Int val h = (x:Int) => 2*x + 1 def gen2DFun(x: Int)(y: Int): Fun2D = z => x*z + y // what kind of function is this? val hp = gen2DFun(2)(1) // Now, suppose we would like to write an alternative to gen2DFun // which is not curry. We can obviously rewrite it: def uncurryGen2D(x: Int, y: Int, z: Int): Int = x*z + y // or we can write a piece of code that can be reused someplace-else: /* def uncurry(cf: Int => Int => Fun2D): (Int,Int,Int) => Int = { def aux(x: Int, y: Int, z:Int): Int = cf(x)(y)(z) aux }*/ // or we can improve it to: def uncurry(cf: Int => Int => Fun2D): (Int,Int,Int) => Int = (x,y,z) => cf(x)(y)(z) uncurry(gen2DFun)(2,1,3)