Akka를 공부하다가 예제에서 implicit
라는 키워드를 사용하는 것을 보았다.
이 키워드가 정확하게 의미하는 것이 무엇인지 몰랐다. 따라서 implicit에 대해 정리하려고 한다.
또한, 이 내용은 Programming in Scala 4/e
Chapter.21 - 암시적 변환과 암시적 파라미터에 자세하게 나와있어 이 내용들을 종합하여 정리할 예정이다.
스칼라에서 implicit
는 매우 편리하기도 하지만 코드 가독성을 엄청 떨어뜨릴 수도 있는 키워드이다.
보통 사람들은 자신의 코드는 마음대로 바꾸거나 확장하며 사용하지만, 다른 사람이 만든 라이브러리를 사용하고 싶은 경우에는 있는 그대로 사용한다. 스칼라에서는 이러한 문제에 해결 방법으로 암시적 파라미터와 암시적 변환을 사용한다.
이 포스트에서는 위의 내용들을 정리하려고 한다.
암시적 변환은 서로를 고려하지 않고 독립적으로 개발된 두 덩어리의 서프트웨어를 한데 묶을 때 유용하다. 즉, 근본적으로 동일한 어떤 대상을 각 라이브러리가 각각 다르게 인코딩할 수 있다.
다음 예제를 보자.
val button = new JButton
button.addActionListener(
new ActionListener {
def actionPerformed(event:ActionEvent) = println("pressed!")
}
)
위의 코드는 addActionListener
가 내부 인자로 ActionListener
라는 객체를 원하고 있고, 해당 객체 안에 들어있는 함수를 선언한 것이다.
물론 제대로 의미가 전달되지만 스칼라스러운 코드는 아니다.
button.addActionListener( (_:ActionEvent) => println("pressed") )
그럼 위와 같은 스칼라스러운 코드는 잘 동작할까? 정답은 아니다. [나중에 정리하겠지만 스칼라 2.12에서는 위의 코드가 실행된다.]
왜냐하면 addActionListener
가 파라미터로 원하는 것은 함수가 아니라 객체이다. 하지만, 암시적 변환을 사용한다면 이 코드를 작동하게 할 수 있다.
implicit def function2ActionListener(f:ActionEvent => Unit) =
new ActionListener {
def actionPerformed(event:ActionEvent) => f(event)
}
button.addActionListener(
function2ActionListener(
(_:ActionEvent) => println("pressed")
)
)
위의 코드는 잘 동작한다.
implicit def function2ActionListener(f:ActionEvent => Unit) =
new ActionListener {
def actionPerformed(event:ActionEvent) => f(event)
}
button.addActionListener(
(_:ActionEvent) => println("pressed")
)
또한 위의 코드 역시 잘 동작한다. 그 이유는 무엇일까?
function2ActionListener
를 implicit
로 표시했으므로 이를 생략해도 암시적 변환을 통해 위의 코드를 제대로 동작 시킨다.
예제를 하나만 더 보자.
case class Paper(content: String)
case class Book(name: String, code: Int)
// book 객체를 paper 객체로 바꾸는 메소드
implicit def bookToPaper(book: Book): Paper = Paper(s"${book.code} - ${book.name}")
val book: Book = Book("Scala", 1234)
val paper: Paper = book // bookToPaper 함수를 이용해 Paper 객체로 변환
분명, Book에서 Paper로 바꾸려면 bookToPaper
라는 메소드가 사용되어야 할텐데, 메소드가 없이도 잘 변환이 된다. 즉, implicit로 선언된 형변환 메소드를 통해 오토 캐스팅이 가능해진다.
하지만 이렇게 편리한 기능을 제공하는 암시적 변환도 다음과 같은 문제점이 있다.
implicit
로 선언된 것은 지역적이지가 않다. 즉, 접근제어자를 통해 클래스나 패키지 제한을 걸어두어도 프로젝트에 존재하면 그냥 사용할 수 있는 것이다.암시적 파라미터는 메서드의 파라미터에 implicit를 선언하는 방식이다.
컴파일러는 때때로 someCall(a)
호출을 내부에서 someCall(a)(b)
로 바꾸거나, new SomeClass(a)
를 new SomeClass(a)(b)
로 자체적으로 바꾸기도 한다. 즉, 함수 호출을 완성하는데 필요한 빠진 파라미터 목록을 채워 넣어주는 역할도 한다.
위와 같이 사용하기 위해서는 디폴트 값이 있거나 implicit
를 표시하여 어떤 파라미터에 채울 것인지 명시한다.
implicit val x: Int = 500
def plusValue(paramX: Int)(implicit paramY: Int) = paramX + paramY
println( plusValue(100)(200) ) // 300
println( plusValue(200) ) // 700
위의 예시를 보자. 위의 예시에서 implicit
로 x가 선언되었으며, plusValue의 파라미터 목록 중 paramY
가 implicit로 선언되었다. 즉, 만약 파라미터가 빠져있다면 implicit로 선언된 paramY 자리에 외부 스코프에 있는 implicit 값을 가져오게 한다는 소리이다.