스칼라 함수형 프로그래밍 - Closures (클로저)의 이해와 활용
View: 184
0
0
작성자: 달빛제이크
카테고리: Scala Language
발행: 2024-06-11
수정
2024-06-21
안녕하세요. 달빛제이크입니다.
스칼라 Functions and Closures에 대해서 계속 이야기하고 있는 중인데요. 오늘은 이 주제의 마지막으로 Closures (클로저)에 대해서 알아보려고 합니다.
1. 예제 살펴보기
아래 예제를 한 번 보겠습니다.
// example01
val addOne = (x: Int) => x + 1
// example02
val addMore = (x: Int) => x + more
example01은 지난 글에서 설명 드린 함수 리터럴 입니다. 이 함수 리터럴(Function literal)이 Runtime에서 함수 값(Function value)이 되어 인수로 전달할 수 있게 됩니다. 여기서 x는 이 함수 리터럴의 유일한 parameter입니다.
example02는 이 함수 리터럴에 more라는 변수가 추가 되었습니다. 이 함수 리터럴은 정상 동작하지 않습니다. 당연히 more가 변수인 것은 알겠는데 정상적으로 선언되지 않고 갑자기 함수 리터럴에서 사용되고 있는 상황이죠. 그런데 아래와 같이 정상적으로 more를 변수로 선언해주고 사용하면 정상 동작합니다.
val more = 1
val addMore = (x: Int) => x + more
addMore(10) // 11
2-1. bound variable, free variable
여기서 알아두면 좋을 용어가 나옵니다. bound variable과 free variable인데요. 우리나라 말로 묶인 변수와 자유 변수라고 합니다. 어색하네요.
이 함수 리터럴에서 x가 bound variable입니다. 함수 내에서 parameter에 binding 되어 분명한 역할이 있지요.
more는 free variable입니다. 함수 맥락 상 함수 리터럴 자체에서 의미를 부여하지 못하기 때문에 자유롭다는 뜻이겠지요. 이 함수 리터럴이 Runtime에서 함수 값이 되는 데, 이 때 more가 할당된 1이라는 값을 캡처해서 기억하게 됩니다. free variable인 more가 binding 된 값을 Capturing 하면서 함수가 정의된 상황을 함수 리터럴이 "closing"되었다고 이야기 합니다. binding된 값을 Capture 했다는 의미는 Function value가 capture 된 more 변수의 reference (참조)를 가지고 있다는 의미입니다.
2-2. closed term, open term
또 다른 용어로 closed term과 open term이 있습니다. term이라는 의미는 source code의 한 부분이라는 의미로 (x: Int) => x + 1과 같이 free variable이 없는 함수 리터럴을 closed term이라고 부르고, (x: Int) => x + more와 같이 free variable을 가지고 있는 함수 리터럴을 open term이라고도 부릅니다. 앞에서 함수 리터럴을 closing한다는 표현을 썻는데 open term이라는 용어를 쓰면 closing과 대응되기 때문에 오히려 더 직관적으로 이해가 될 것 같습니다.
3. closure 란?
그렇다면 Closure란 무엇일까요?
관련 내용은 앞에서 다 설명했습니다. more와 같은 free variable을 가지고 있는 open term 함수 리터럴이 runtime에서 closing 되는 동작으로 생겨난 함수 값 (Function value)을 Closure(클로저)라고 부릅니다. 아주 쉬운 것을 구구 절절 어렵게 설명했어요. 필요한 용어를 알고 접근하면 이해가 더 쉬워지기 때문이에요.
4. 다시 예제 살펴보기
이제부터는 간단하게 활용 예제를 보겠습니다.
// 첫 번째 예제
var more = 1
val addMore = (x: Int) => x + more
addMore(10) // 11
more = 9999
addMore(10) // 10009
// 두 번째 예제
val someNumbers = List(-11, -10, -5, 0, 5, 10)
var sum = 0
someNumbers.foreach(sum += _)
sum // -11
// 세 번째 예제
def makeIncreaser(more: Int) = (x: Int) => x + more
val inc1 = makeIncreaser(1)
val inc9999 = makeIncreaser(9999)
inc1(10) // 11
inc9999(10) // 10009
첫 번째 예제에서는 val more가 아닌 var more를 선언해 주었습니다. val more = 1로 선언을 해주면 클로저의 more 값이 1로 고정이 되어 변할 수가 없는 데 var more = 1로 선언을 해주었기 때문에 뒤에 more 값을 변경해 주어도 변경된 값으로 정상 동작을 하고 있습니다. 위에서 설명드린 것처럼 free variable은 function value가 되었을 때 binding 된 값의 참조를 가지고 있어서 값의 변경이 가능 합니다.
두 번째 예제는 List의 값을 하나 씩 대입하면서 sum의 값을 변경하고 있습니다. 여기서 someNumbers.foreach(sum += _)를 풀어쓰면 someNumbers.foreach( (x: Int) => sum = sum + x ) 와 같습니다. 여기서 sum은 free variable이고 List의 모든 값들이 더해져서 최종 결과는 -11이 됩니다.
세 번째 예제는 free variable을 함수의 parameter에 binding 하는 경우 입니다. 함수에 선언된 함수 리터럴은 당연히 함수가 호출될 때 함수 값으로 전환되고, 호출 될 때마다 parameter인 more만 바뀌는 것이 아니라 매번 새로운 함수 값이 생성됩니다. parameter인 more는 stack이 아닌 heap에 저장이 되기 때문에 더 이상 변경될 수 없는 값이 됩니다.
Free variable을 가지고 있는 Function literal로부터 생성된 Function value를 Closure 라고 부릅니다.
Closure가 중요한 것 같긴 한데 어떤 상황에서 어떻게 사용하면 좋을 지 좀 더 알아 볼 필요가 있을 것 같습니다.
다음 글에서는 클로저에 대해 좀 더 다양한 예제를 살펴 보겠습니다.
감사합니다.
