High Order Functions
FILE : HighOrderFunctionsDemo
High order function is a function which receive or/and return other functions.
In Scala methods can be converted into functions so you can also hear about "high order methods".
//method
def highOrderMethod(f:Int=>Int): Int = f(1)
//function
val highOrderFunction : (Int=>Int) => Int = function => function(1)
The first place where you can find this construction is collection API
val simpleAddFive:Int=>Int = i => i+5
List(1,2,3).map(simpleAddFive)
Return a Function
The same as we can pass a function to other function we can also return a function. This is very handy when we want to modify given function so it fits composition or if we want to inject initial data into function.
Here we have an example of function which adds provided value to fixed initial value
def createAddToN(n:Int) : Int => Int = i => i+n
val addToThree=createAddToN(3)
Or we can return function with the same types as input but with some additional functionality - this is a functional implementation of a decorator pattern
title("Functional Decorator")
def decorateWithLogging(original:Int=>Int) : Int=>Int = i => {
println(s" [LOGGING] : received parameter : $i")
original(i) //calling original
}
val decrement : Int=>Int = i => i-1
val decorated=decorateWithLogging(decrement)
Dependency injection
With high order function you can implement basic dependency injection which can create operations on injected context.
For example we can inject Dao and then return function which will operate on this DAO.
def injectDao(dao:UserDao) : Int=> Option[User] = id => dao.findById(id)
val dao= new UserDao
val daoFunction: (Int) => Option[User] =injectDao(dao)
section("daoFunction(1) : "+daoFunction(1))
Cyrrying
The idea behind currying is that instead returning flat value you will return a function which can be applied later.
So in the following example
$$f(x) -> g(y) -> z$$
you can first apply 'x' _and later apply '_y'
val g = f(x=...)
val z = g(y= ..)
or in short
val z = f(x=..)(y=..)
In scala example this will look like this
def calculateGross(tax:Double)(net:BigDecimal): BigDecimal = net + (net * tax)
val calculateGrossForSpecificTax:BigDecimal -> BigDecimal = calculateGross(0.23)
Or if you want to use function syntax
val calculateGross : Double => BigDecimal => BigDecimal = tax => net => net + (net * tax)
This way you can easily separate application of particular arguments which give you more design options on how to configure/parameterize given logic.
Exercises
FILE : HighOrderFunctionsExercises
Exercise1
In this exercise you have to implement two methods which are returning function.
def createAddPrefix(prefix: String): String => String = ???
def createComparatorToConstant(constant: Int): Int => Boolean = ???
both are in the first tests when you have to complete them. Use test assertion as a spec.
Exercise2
This time you have to implement your own andThen implementation. Remember not to use standard 'andThen' from Function class.
Exercise3
This time you have inject proper logging level and as a result create an usable logger :
object Exercise3{
sealed trait LogLevel
case object DEBUG extends LogLevel
case object INFO extends LogLevel
case object ERROR extends LogLevel
def createLogger(level:LogLevel) : String => String = ???
}
Exercise4
This is most advanced exercise in this section. You will have to implement your own curry/ uncurry methods
object Exercise4 {
def curry(f:(Int,Int)=>Int) : Int => Int => Int = ???
def unCurry(f:Int => Int=>Int) : (Int,Int) => Int = ???
def curryGeneric[A,B,C](f:(A,B)=>C) : A => B => C = ???
def unCurryGeneric[A,B,C](f:A => B => C) : (A , B) => C = ???
}
So for example when you have function with two parameters as a result of curry you should receive one param function which will return function with second parameter
val addInts: (Int,Int) => Int = (i1,i2) => i1+i2
val intsCurried: Int => Int => Int = Exercise4.curry(addInts)
After fiunishing version just for int try to implement generic version
val repeatString : (String,Long) => String = (s,n) => (1L to n).map(_ => s).mkString
val repeatStringCurried: String => Long => String = Exercise4.curryGeneric(repeatString)