내포된 메서드의 예를 보자.
def factorial(i: Int): Long = {
def fact(i: Int, accumulator: Long): Long = {
if (i <= 1) accumulator
else fact(i - 1, i * accumulator)
}
fact(i, 1)
}
(0 to 5) foreach ( i => println(factorial(i)) )
- fact() 함수가 내포된 메서드이다.
- 길이가 긴 메서드의 본문을 여러 작은 메서드로 리팩토링하는 경우 유용하다.
- fact()와 factorial에서 같은 이름의 i라는 매개변수를 사용하지만 factorial의 i는 가려진다.
- 오직 fact()를 호출할 때만 사용된다.
- fact()가 재귀적이고, 스칼라의 지역 영역 타입 추론은 재귀 함수의 반환 타입을 추론할 수 없기 때문에 fact()의 반환 타입을 꼭 명시해야 한다.
재귀 함수에 대한 의문
- 재귀 함수의 경우 stack이 쌓여 메모리가 터질 수 있기 때문에 걱정이 된다.
- scala의 경우 꼬리 재귀 최적화의 경우 while문 처럼 컴파일이 되기 때문에 괜찮다.
꼬리 재귀
- 꼬리 재귀를 제대로 작성했는지는 @tailrec을 사용하면 잘 알 수 있다.
import scala.annotation.tailrec
def factorial(i: Int): Long = {
@tailrec
def fact(i: Int, accumulator: Int): Long = {
if (i <= 1) accumulator
else fact(i-1, i * accumulator)
}
fact(i,1)
}
(0 to 5) foreach ( i => println(factorial(i)) )
- 만약 fact가 꼬리 재귀가 아니라면, 컴파일러가 오류를 발생시킬 것이다.
- 꼬리 재귀 함수는 마지막에 fact(i,1)처럼 내부 함수 하나만 사용하여 반환을 해야하며 사칙연산이 들어가면 안된다.
꼬리 재귀는 왜 사용할까?
- 재귀 함수를 사용하면 좀 더 코드가 간결해지는데 꼬리 재귀로 만들면 최적화가 되어 메모리가 터지지 않으니까 사용하는게 아닐까?
- 코드가 간결은 해지지만 꼬리 재귀로 짜기에는 머리가 좀 아플거 같다.