See https://github.com/fpinscala/fpinscala/wiki/Chapter-2:-Getting-started for official notes.
Recursive function calls can be tail recursive if the last call of the function can be returns immediately. Scala will convert this into a while loop, and there will be not stack frame exhaustion. An example of a recursive call that is not tail recursive:
1 + go(n, acc)
In this case, even after having evaluated go(n, acc)
the funcion result must be returned in order to be evaluated in the stack above.
A tail call elimination can be made when there is no subsequent evaluation.
An annotation @annotation.tailrec
can be added before the function call in order to catch any recursive functions that should but cannot be tail call eliminated.
In [9]:
def fibTail(n: Int): Int = {
@annotation.tailrec
def go(n: Int, current: Int, fibCurrent: Int, fibPrevious: Int): Int = {
if(n == current) return(fibCurrent)
go(n, current + 1, fibCurrent + fibPrevious, fibCurrent)
}
if(n == 0) return(0)
go(n, 1, 1, 0)
}
In [4]:
fibTail(11)
Markus Fib Note that he uses three arguments and counts down rather than up.
In [76]:
def fib(n: Int): Int = {
@annotation.tailrec
def loop(n: Int, currentFib: Int, nextFib: Int): Int =
if (n == 0) currentFib else loop(n-1, nextFib, currentFib + nextFib)
if (n < 0) throw new IllegalArgumentException("fibonacci argument must be greater than 0")
loop(n, 0, 1)
}
In [16]:
(0 to 5).map(fib)
In [14]:
(0 to 5).map(fibTail)
In [11]:
def fibRunar(n: Int): Int = {
@annotation.tailrec
def go(n: Int, acc: Int, res: Int): Int =
if (n == 0) res
else go(n - 1, acc + res, acc)
go(n, 1, 0)
}
In [ ]:
In [24]:
def isSorted[A](as: Array[A], ordered: (A,A) => Boolean): Boolean = {
@annotation.tailrec
def loop(n: Int): Boolean = {
if(n >= as.length) true
else if(ordered(as(n-1), as(n))) loop(n+1)
else false
}
if(as.length == 1) return(true)
loop(1)
}
In [25]:
isSorted(Array(1), (x: Int,y: Int) => x <= y)
In [26]:
isSorted(Array(1,2,3), (x: Int,y: Int) => x <= y)
In [27]:
isSorted(Array(1,6,5), (x: Int,y: Int) => x <= y)
In [28]:
isSorted(Array("AB", "B"), (x: String, y: String) => x <= y)
In [21]:
@annotation.tailrec
final def isSortedMarkus[A](as: Array[A], gt: (A,A) => Boolean): Boolean = {
if (as.length < 2) true
else if (gt(as.head, as.tail.head)) false
else isSortedMarkus(as.tail, gt)
}
In [83]:
isSortedMarkus(Array(1,6,5), (x: Int,y: Int) => x <= y)
In [86]:
load.ivy("com.storm-enroute" %% "scalameter" % "0.7")
In [ ]:
Currying functions changes the structure of how you have to call it. If we look at a function that takes two arguments: f(a: A, b: B): C
and curry it, it will take one argument but return a function: g(a: A): B => C
. Partially applying a function on the other hand will take a function the will take say two arguments f(a: A, b: B): C
, and fix one of the argument b
so the remaining function takes on argument: h(a: A): C
.
In [77]:
def curry[A,B,C](f: (A, B) => C): A => (B => C) = {
a => b => f(a,b)
}
In [80]:
def add(x: Int, y: Int) = x + y
In [82]:
In [ ]:
In [36]:
add(2,3)
In [37]:
val add3to = curry(add)(3)
In [39]:
add3to(4)
Curry implementation that's build in to scala:
In [47]:
val f = (a: Int, b: Int) => a + b
val curf = f.curried
In [57]:
val bla = curf _
Hmm? how about this one!
In [56]:
bla()(3)(3)
In [58]:
def uncurry[A,B,C](f: A => B => C): (A, B) => C = {
(a, b) => f(a)(b)
}
In [59]:
val uncurriedAdd = uncurry(curry(add))
In [61]:
uncurriedAdd(2,3)
In [71]:
def compose[A, B, C](f: B => C, g: A => B): A => C =
a => f(g(a))
In [72]:
def addQuotes(d: Int): String = "'" + d + "'"
In [73]:
def add3AndCompose = compose(addQuotes, add3to)
In [74]:
add3AndCompose(4)
In [ ]: