함수는 값이다

함수는 값이다

어떤 언어든 순수 함수를 작성할 수는 있어요. 하지만 Scala의 함수형 프로그래밍(FP)에서 아주 중요한 두 번째 특징은 바로 함수를 값으로 만들 수 있다는 것이에요. String이나 Int 값을 만들듯이 함수도 값으로 만들 수 있는 거죠.

출처: Scala 3 Book — Functions Are Values

본문

함수를 값으로 만들 수 있다는 특징은 여러 이점을 줘요. 그중 가장 흔한 것은 (a) 메서드가 함수를 매개변수로 받도록 정의할 수 있다는 점, (b) 함수를 다른 메서드의 매개변수로 넘겨줄 수 있다는 점이에요. 이 책에서도 map이나 filter 같은 메서드를 다룰 때 여러 번 보셨죠.

val nums = (1 to 10).toList

val doubles = nums.map(_ * 2)           // double each value
val lessThanFive = nums.filter(_ < 5)   // List(1,2,3,4)

이 예시들에서는 익명 함수(anonymous function)를 mapfilter에 넘겨주고 있어요.

익명 함수는 *람다(lambda)*라고도 불러요.

익명 함수를 filtermap에 넘겨주는 것뿐 아니라, 메서드를 넘겨줄 수도 있어요.

// two methods
def double(i: Int): Int = i * 2
def underFive(i: Int): Boolean = i < 5

// pass those methods into filter and map
val doubles = nums.filter(underFive).map(double)

메서드와 함수를 값처럼 다룰 수 있다는 능력은 함수형 프로그래밍 언어가 제공하는 강력한 기능이에요.

엄밀히 말하면, 다른 함수를 입력 매개변수로 받는 함수를 *고차 함수(Higher-Order Function)*라고 해요. (농담 삼아 말하면, 누군가가 썼듯이 다른 클래스의 인스턴스를 생성자 매개변수로 받는 클래스를 "고차 클래스"라고 부르는 것과 비슷한 말이죠.)

함수와 익명 함수, 메서드

아까 예시들에서 보았듯이, 이것이 익명 함수예요.

_ * 2

고차 함수에서 다루듯이, 이것은 다음 문법의 축약형이에요.

(i: Int) => i * 2

이런 함수들을 이름이 없다고 해서 "익명(anonymous)"이라고 불러요. 이름을 붙이고 싶다면 그냥 변수에 할당하면 돼요.

val double = (i: Int) => i * 2

이제 이름 있는 함수, 즉 변수에 할당된 함수가 생겼어요. 이 함수는 메서드처럼 그냥 사용하면 돼요.

double(2)   // 4

대부분의 상황에서 double이 함수인지 메서드인지는 중요하지 않아요. Scala는 둘을 같은 방식으로 다루게 해주거든요. 이면에서는 메서드를 함수처럼 다루게 해주는 Scala의 기술을 Eta Expansion이라고 불러요.

함수를 변수처럼 자유롭게 주고받을 수 있다는 능력은 Scala 같은 함수형 프로그래밍 언어를 구분 짓는 특징이에요. 그리고 이 책 전반의 mapfilter 예시에서 보셨듯이, 함수를 다른 함수에 넘겨주는 능력 덕분에 코드가 간결하면서도 읽기 쉬워져요. 즉 *표현력(expressive)*이 생기는 거죠.

함수를 다른 함수의 매개변수로 넘기는 과정이 아직 익숙하지 않다면, 직접 실험해 볼 수 있는 예시를 몇 개 더 보여드릴게요.

List("bob", "joe").map(_.toUpperCase)   // List(BOB, JOE)
List("bob", "joe").map(_.capitalize)    // List(Bob, Joe)
List("plum", "banana").map(_.length)    // List(4, 6)

val fruits = List("apple", "pear")
fruits.map(_.toUpperCase)       // List(APPLE, PEAR)
fruits.flatMap(_.toUpperCase)   // List(A, P, P, L, E, P, E, A, R)

val nums = List(5, 1, 3, 11, 7)
nums.map(_ * 2)         // List(10, 2, 6, 22, 14)
nums.filter(_ > 3)      // List(5, 11, 7)
nums.takeWhile(_ < 6)   // List(5, 1, 3)
nums.sortWith(_ < _)    // List(1, 3, 5, 7, 11)
nums.sortWith(_ > _)    // List(11, 7, 5, 3, 1)

nums.takeWhile(_ < 6).sortWith(_ < _)   // List(1, 3, 5)

더 알아보기