Scala의 def와 val, 그리고 함수 리터럴
스칼라 스터디 진행 중 필요하다고 느껴 def 와 val에 대한 이야기를 나름 정리해봤다. def와 val에 대한 차이점은 스칼라가 function literal 혹은 function value를 어떻게 다루는지 살펴보면 보다 쉽게 이해할 수 있다.
Function Literal과 Function Value
함수 리터럴(function literal)은 (a:Int)=> a+1 과 같은 형태로 표현되는데, 스칼라에서 함수 리터럴은 실제로는 FunctionN이라는 클래스이다. 여기서 N은 인자 개수를 나타낸다. 예를 들어, 인자가 두 개인 함수 리터럴을 나타내는 클래스는 Function2이다. 스칼라에서 함수 리터럴을 클래스로 취급한다는 사실은 아주 중요하다.
참고로 FunctionN 클래스는 def로 만든 일반적인 함수가 아닌, 함수 리터럴을 나타내는 타입임에 주의한다. 확인을 위해 def로 일반적인 함수를 하나 정의해 보겠다.
scala> def sum(a:Int, b:Int) = a + b sum: (a: Int, b: Int)Int
sum의 타입을 보면 (a: Int, b: Int)Int 인데, 나중에 확인해 보겠지만 이는 함수 리터럴에 해당하는 타입이 아니다. 함수 리터럴이라면 Function2 혹은 (Int, Int):Int 타입이라고 출력되어야 한다.
def로 정의한 함수를 함수 리터럴로 정의한 함수로 만드려면 다음과 같이 partially applied function을 이용하면 된다. 출력 결과에 보이는 function2는 Function2의 인스턴스라는 의미인데, function2와 같이 함수 리터럴을 이용해 만든 인스턴스를 function value라고 한다.
scala> sum _ res1: (Int, Int) => Int = <function2>
위 예에서는 모든 인자를 그대로 활용했지만, 다음과 같이 인자 1개를 고정하면 Function2 대신 Function1의 인스턴스가, 다시 말해 function value가 반환됨을 확인할 수 있다.
scala> sum( 1, _:Int) res2: Int => Int = <function1>
이제 본론으로 들어가 function value와 함수를 fist class로 다루는게 어떤 관계인지 살펴보려고 한다.
partially applied function의 결과는 funciton value임을 확인했다. 이게 뭐 어떻다는 말일까? Java 등의 객체 지향 언어를 사용한 경험이 있다면 다음과 같이 특정 변수에 객체의 인스턴스를 보관하는 것이 아주 자연스럽다.
scala> val person = new Person("아웃사이다", 35)
마찬가지로 함수를 변수에 담거나, 인자로 전달하거나, 함수의 리턴 값으로 반환하기 위해, FunctionN 객체의 인스턴스를 특정 변수에 보관하는 것 또한 가능하다.
scala> val sum = (a:Int, b:Int) => a + b sum: (Int, Int) => Int = <function2>
결국 위 코드는 아래와 같다.
scala> val sum = new Function2[Int, Int, Int] { | def apply(a:Int, b:Int):Int = { | a + b | } | } sum: java.lang.Object with (Int, Int) => Int = <function2> scala> sum(1,2) res0: Int = 3
다시 말하면 스칼라에서는 function literal을 클래스로 정의하여 함수를 first class로 만들었기 때문에, 일반적인 객체인 함수(정확히는 function value)는 다른 객체와 마찬가지로 자유로운 전달이 가능하다.
지금까지 설명한 내용을 이해했다면, stackoverflow 등에 올라온 val에 def에 대한 질문들을 확인해보면 조금 더 이해가 잘 될지도 모르겠다. 한국 스칼라 사용자 모임 메일링 리스트에서 def과 관련된 내용을 이야기 하다가 다음과 같은 링크가 등장했는데, 시간이 되면 확인해보길 바란다. 근본적으로는 같은 내용이지만 조금 다른 질문을 하고 있다.
http://stackoverflow.com/questions/3646756/scala-def-versus-val
def 혹은 val을 오버라이드 할 때 유의 사항
간단하게 정리하면 def를 val로 override하지는 못하지만, val을 def로 override하는 것은 가능하다.
scala> class A { val a=0 }; class B extends A { override def a=1 } // 실패 <console>:4: error: error overriding value a in class A of type Int; method a is not stable class A { val a=0 }; class B extends A { override def a=1 } ^ scala> class A { def a=0 }; class B extends A { override val a=1 } // 성공 defined class A defined class B
A = new B 와 같이 선언하여 사용한다고 하면 A.a는 stateless하다고 가정하고 사용될 것이다. 하지만 실제 구현이 def이면 해당 멤버가 상태가 없다는 것을 보장하기 힘들다. 때문에 Scala에서는 def를 val로 override 하는 것만을 허용한다.











