[Scala] implicit keyword (1) - implicit converter

Implicit converter가 하는 일은 이름 그대로 value를 적절한 type으로 implicit하게 convert하는 것이다. Scala는 기본적으로 strong typed language이기 때문에 implicit conversion을 지원하지 않는다. 함수를 호출할 때 함수의 symbol에 맞지 않으면 바로 컴파일에러가 발생한다. implicit conversion을 하기 위해서는 해당 scope 안에 implicit converter를 구현해두어야 한다. Implicit converter의 선언은 다음과 같다.

Implicit converter는 unary function에 implicit keyword를 붙여주는 것으로 정의된다. 이렇게 Implicit converter를 구현해두면, sizeToRectangle 함수가 정의되어 있는 scope에서는 Size 객체가 Rectangle 객체로 implicit conversion이 가능해진다.

예를 들어 Scala에서 자주 쓰이는 Option이라는 class를 보자. Option은 c#의 nullable과 유사한 type으로 원소가 없을 수 있는 객체이다. 이는 다른 관점에서 보면 최대 원소가 1개인 collection이라고 볼 수 있고, Option을 사용할 때 collection처럼 list comprehensive method1)를 사용하는 것을 권장한다.

하지만 Option 코드를 보면 OptionIterable이나 Traversable을 상속받지 않았고, 일부 함수를 제외하고 collection처럼 이용는데 필요한 모든 함수를 가지고 있지도 않다. 대신 OptionIterable로 변환해주는 option2Iterable이라는 implicit converter가 있기 때문에 Iterable이 필요한 경우 자동으로 변환해서 넘겨준다.

Implicit converter에 대응되는 개념이 다른 언어에 없는 것은 아니다. C++에서는 unary constructor를 구현하거나 assignment operator나 type case operator를 구현하여 implicit conversion을 구현한다. C#은 conversion operator를 구현하여 implicit conversion을 구현할 수 있다. 하지만 implicit converter는 이들보다 더 표현력 있고 확장성 있는 개념이다. implicit converter가 다른 언어의 implicit conversion보다 표현력 있고 확장성 있다는 것을 보여주는 좋은 예제가 RichInt다.

위의 예제는 Scala가 기본으로 제공해주는 Int타입의 확장함수 들이다. 하지만 Int 구현체에는 위의 함수들에 대한 구현이 없다. 왜냐하면 Scala는 정책상으로 Int class내에는 primitive operator2)에 대해서만 구현하도록 하고 있기 때문이다. 그렇다면 위의 함수들은 어디에 정의되어 있을까? primitive operator가 아닌 함수들은RichInt에 구현되어 있다. Int에서 RichInt로 implicit convert해주는 함수가 정의되어 있기 때문에 Int로 선언된 변수들에 대해서도 RichInt에 정의된 함수를 쓸 수 있는 것이다.

여기서 Scala의 implicit converter가 C++에서 제공하는 implicit conversion보다 가지는 장점을 알 수 있다. C++에서 type case operator나 unary constructor를 구현하거나 C#에서 conversion operator를 구현하더라도 implicit type casting을 해서 함수의 symbol을 찾지 않는다. 다시 말해서 C++이었다면 RichInt와 implicit conversion을 위한 operator들이 구현되어 있다고 해도 symbol을 찾기 위해서는 아래와 같은 추가 작업이 필요하다.

하지만 Scala는 instance의 현재 class에 해당하는 symbol의 함수가 없어도 현재 scope에 있는 implicit converter를 1번 적용해서 나오는 class에서 해당하는 symbol의 함수를 찾을 수 있으면, 해당하는 함수를 부른다.

또 기존의 implicit conversion에는 큰 문제가 있는데, implicit conversion의 정의가 class 내에 정의되어야 한다는 것이다. 즉, A에서 B로 가는 implicit conversion의 구현을 하고 싶으면 A와 B 둘 중 최소한 하나는 내가 수정할 수 있는 class여야 한다는 것이다.

library에서 제공해주는 class 간의 implicit conversion을 구현할 방법이 없고 이 때문에 쓸데없이 Wrapper class를 만들어야 하는 일이 종종 발생한다. 게다가 class에 implicit conversion이 정의되기 때문에 특정한 함수에서만 implicit conversion을 허용하거나, 반대로 특정 함수에서는 implicit conversion을 허용하지 않는 것을 할 수 없다.

물론 이런 강대한 표현력은 가끔 과다해서 단점이 되기도 한다. 어떤 객체의 함수가 그 객체의 class 정의 밖에 존재하는 것이기 때문에 코드를 읽는데 어려움을 준다. 가끔은 다른 부분에서 자연스럽게 썼던 함수를 implicit converter를 import하지 않아서 사용하지 못하는 일도 있다. 하지만 이 정도 문제는 좋은 IDE(Integrated Development Environment)가 있으면 쉽게 해결되는 문제다. 3) 너무 과하지 않을 정도로 적당히4) 사용하면 한층 더 확장된 세계를 볼 수 있게 된다.


1) filter, map, flatMap ...

2) unary -, unary ~, +, * ...

3) 다시 한 번 IntelliJ IDEA를 추천한다.

4) 프로그래밍 언어는 결국 도구다. 어떤 도구를 쓰더라도 도구를 어떻게 사용할지는 그 도구를 사용하는 사람, 다시 말해 프로그래머가 잘 결정해야 한다.


댓글

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

RAII는 무엇인가

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API