De-Note sparkafka's dev blog

[Scala] Call-by-value와 Call-by-name의 차이

들어가며

Coursera 수업을 들으면서 Scala의 인자 전달법에 대해 모르던 내용이 나와서 정리한다.

Coursera의 Functional Programming Principles in Scala의 내용 중 일부를 정리하였다.


Call-by-value와 Call-by-name

Scala에서는 함수를 호출할 때 사용하는 인자 전달법으로 Call-by-value, Call-by-name 2가지를 지원한다.


  • Call-by-value

Call-by-value는 일반적인 프로그래밍 언어들에서 기본적으로 지원하는 인자 전달법으로, 인자 값을 복사하여 함수에 전달하고, 인자로 표현식이 들어온 경우 표현식을 먼저 계산하고 함수에 전달한다.

Scala에서는 다음과 같이 Call-by-value 인자를 만든다.

def square(x: Double) = x * x
def sumOfSquares(x: Double, y: Double) = square(x) + square(y)

Call-by-value 형식의 Evaluation은 다음과 같다.

sumOfSquares(3, 2+2)
sumOfSquares(3, 4)
square(3) + square(4)
3 * 3 + square(4)
9 + square(4)
9 + 4 * 4
9 + 16
25

그런데 이런 Call-by-value가 문제가 있는 경우가 존재한다.

def loop: Int = loop
def chooseX(x: Int, y: Int) = x

chooseX(3, loop(2))

위 코드에서 chooseX(x, y)는 항상 x를 반환하지만, chosse(3, loop(2))에서 인자들을 먼저 reduce 하는데, loop가 무한루프이기 때문에 에러가 발생한다. 이런 상황은 Call-by-name을 활용하면 해결할 수 있다.


  • Call-by-name

Call-by-name은 함수의 인자들을 reduce 하기 전에 함수를 적용한다. 표현식이 들어올 경우 표현식 통째로 함수에 인자로 넣는다.

Scala에서는 다음과 같이 =>를 사용하여 Call-by-name 인자를 만든다.

def square(x: => Double) = x * x
def sumOfSquares(x: => Double, y: => Double) = square(x) + square(y)

Call-by-name 형식의 Evaluation은 다음과 같다.

sumOfSquares(3, 2+2)
square(3) + square(2+2)
3 * 3 + square(2+2)
9 + square(2+2)
9 + (2+2) * (2+2)
9 + 4 * (2+2)
9 + 4 * 4
9 + 16
25

위 Call-by-value의 예에서 Call-by-name을 사용했을 경우는 다음과 같다.

def loop: Int = loop
def chooseX(x: Int, y: => Int) = x

chooseX(3, loop(2))
3

위와 같이 함수에 인자를 넘겨줄 때 Call-by-name 방식을 사용하면 정상적으로 값이 반환된다.


  • 상황에 따른 단계 비교

다음 상황에서 Call-by-value와 Call-by-name을 사용했을 때 어디가 더 빠를까?

def test(x: Int, y: Int) = x * x

// 1
test(2, 3)
// 2
test(3+4, 8)
// 3
test(7, 2*4)
// 4
test(3+4, 2*4)


Call-by-value

// 1
test(2, 3)
2 * 2
4

// 2
test(3+4, 8)
test(7, 8)
7 * 7
49

// 3
test(7, 2*4)
test(7, 8)
7 * 7
49

// 4
test(3+4, 2*4)
test(7, 2*4)
test(7, 8)
7 * 7
49


Call-by-name

// 1
test(2, 3)
2 * 2
4

// 2
test(3+4, 8)
(3+4) * (3+4)
7 * (3+4)
7 * 7
49

// 3
test(7, 2*4)
7 * 7
49

// 4
test(3+4, 2*4)
(3+4) * (3+4)
7 * (3+4)
7 * 7
49

항상 Call-by-value가 빠른게 아니고, Call-by-name이 더 빠른 경우도 있다.


마치며

Scala에서 생소한 개념인 Call-by-name에 대해 알아보았다. 아직까지는 Call-by-name을 어떻게 써야 할지 감이 잘 안 온다. 더 깊게 공부하다 보면 써먹을 곳이 있겠지?