레이블이 implicit인 게시물을 표시합니다. 모든 게시물 표시
레이블이 implicit인 게시물을 표시합니다. 모든 게시물 표시

2014-09-30

[Scala] implicit keyword (4) - 마치며

 지난 3번의 posting으로 Scala에서 implicit keyword의 3가지 사용법인 implicit converter, implicit classimplicit parameter에 대해서 알아봤다. 이 3가지 모두 약간씩 다르지만, 명시적으로 적어야 하는 코드를 줄여 코드를 간편하게 만들어 주는 역할을 해준다.
 그 때문에 적절하게 사용하면 verbose 코드를 줄여주어 가독성을 높여줄 뿐 아니라, Scala를 이용하여 domain-specific language(a.k.a. DSL)를 만들기 쉽게 만들어 준다. 실제로 많이 쓰이는 Play framework의 Configuration file이나 route file 또는 Scala와 Java 양쪽에서 많이 쓰이는 SBT는 Scala로 구현한 일종의 DSL로 Scala를 이용하여 static하게 compile 가능하게 되어있다.

 하지만 이런 power는 잘못 사용하면 해가 된다. 뭐 어떤 기능이 안 그러겠는가만은 특히 DSL을 만들기 쉽게 해준다는 것은 결국 기존의 언어가 아닌 새로운 언어를 정의하기 쉽게 된다는 것이고, 너무 많이 사용된 라이브러리는 Scala가 아닌 새로운 언어를 다시 배워야 한다는 문제가 생기기도 한다.
 실제로 SBT가 Scala로 컴파일되는 DSL이지만 필자는 아직도 SBT의 문법을 완벽히 이해하지 못했을 정도로 완전히 새로운 언어가 되었다. 또한, 많은 사람이 Scala를 처음 사용하면서 어려워하는 부분이 implicit converter와 implicit class로 인해 현재 class에 정의되지 않은 함수가 호출되는 것이다.1)

 Implicit keyword는 Scala의 난이도를 높여주는 장애물인 것은 분명하지만, 제대로 이해하고 있으면 새로운 세상을 보여주는 받침대가 된다.

1) 이전 글에서도 말했지만, 이러한 특성 때문에 Scala를 처음 사용할 때는 다른 언어보다 더욱더 IDE가 필요하다. 다시 한 번 IntelliJ IDEA를 추천한다.

Scala implicit keyword

[Scala] implicit keyword (3) - Implicit parameter

 implicit keyword의 3번째 사용법은 implicit parameter와 implicit value를 선언하는 것이다. implicit parameter는 함수를 호출할 때 인자를 생략할 수 있도록 해준다. 정확히는 함수를 호출할 때 인자를 생략하면 자동으로 그 인자를 채워서 실행시켜준다.
 이때 무엇으로 채워주는지를 정하는 규칙은 2가지가 있다. 1)

 첫 번째 규칙은 함수가 호출된 scope에서 prefix 없이 접근할 수 있는 implicit parameter와 같은 타입의 변수 중에 implicit label이 붙은 변수를 사용하는 것이다. 이 규칙에는 2가지 타입의 변수가 해당한다. 하나는 implicit parameter이고, 다른 하나는 해당 스코프에 선언된 implicit modifier가 붙은 local 변수이다. 이때 함수가 호출된 스코프에서 해당하는 규칙에 적용되는 변수가 2개 이상 있으면 "ambiguous implicit values"라는 메시지와 함께 컴파일 에러를 발생시킨다. 여기서 주의해야 할 점은 반드시 implicit parameter와 같은 타입의 변수여야 한다는 것이다. 설령 implicit converter로 변환 가능한 변수가 있어도 이는 implicit parameter로 넘어가지 않는다.

 두 번째 규칙은 companion object에 정의된 변수 중 implicit parameter와 같은 타입으로 선언된 implicit label이 붙은 변수를 사용하는 것이다. 이는 첫 번째 규칙에 해당하는 변수가 없을 때 사용되고, 첫 번째 규칙과 마찬가지로 두 번째 규칙에 해당하는 변수가 2개 이상 있으면 "ambiguous implicit values"라는 메시지와 함께 컴파일 에러를 발생시킨다. implicit converter를 적용하지 않는 것도 첫 번째 규칙과 같다.

 Scala library에서 implicit parameter를 잘 활용하는 대표적인 예는 Future class다. Future class의 method들은 언제나 자신이 실행될 ExecutionContext를 필요로 한다. 원래대로라면 함수를 호출할 때마다 ExecutionContext를 넘겨야 한다. 하지만 이를 implicit parameter로 선언하여 함수를 호출할 때 명시적으로 ExecutionContext를 넘기지 않아도 되도록 하였다.
 이는 boilerplate 코드를 간단하게 만든다는 장점도 있다. 하지만 다른 무엇보다도 서로 다른 symbol을 가지는 Option이나 List의 list comprehension method들2)과 같은 look and feel을 가지게 해준다는 장점이 크다. 같은 look and feel을 가지는 것이 단순히 감성적인 문제 때문에 좋은 게 아니다. Scala는 list comprehension method들에 대해서 for comprehension3)을 사용할 수 있게 해주기 때문에 좋은 것이다.

1) http://www.scala-lang.org/old/node/114
2) map, flatMap, filter 등...
3) http://www.scala-lang.org/old/node/111 for comprehension도 Scala의 중요 feature 중 하나이기 때문에 후에 따로 설명하도록 하겠다.

Scala implicit keyword

2014-09-07

[Scala] implicit keyword (2) - Implicit class

 Scala에서 implicit keyword를 사용하는 또 다른 예제는 Implicit class이다.

 Implicit class는 이미 존재하는 class에 새로운 함수를 추가하여 확장하기 위해서 사용된다.
 정확히 말하면 원래 있던 클래스 자체를 바꾸지는 않고, class를 implicit conversion해서 호출할 수 있는 함수를 추가할 수 있게 해준다.
 Standard library에 있는 implicit class의 대표적인 예는 Duration class들이다.

 위의 예제에서 보듯이 DurationIntDurationDouble이라는 implicit class가 각각  Int와 Double을 확장시켜서 seconds/milliseconds/nanoseconds 같은 method를 추가해서 Duration 객체를 만들 수 있게 해준다.

 이 설명을 보면 지난 글에서 설명해줬던 implicit converter가 해줄 수 있는 일과 크게 다르지 않아 보인다.
 사실 Implicit class는 implicit converter의 syntactic sugar에 불과하다. 내부적으로 implicit class로 정의된 class는 class의 이름과 같은 implicit converter를 같은 scope에 추가한다.
 즉, 아래와 같이 정의된 implicit class는

 아래와 같이 변환된다.


 내부적으로 implicit converter에 해당하는 함수를 정의하는 것이기 때문에 몇 가지 제약사항이 있다.
 첫 번째 제약사항으로 implicit class는 trait이나 classobject 안에 정의되어야 한다.
 이는 함수의 정의가 trait이나 class나 object 안에 정의되어야 하기 때문이다. 그래서 보통 implicit class들은 package object 안에 정의된다.
 두 번째 제약사항은 non-implicit인 parameter가 반드시 1개인 constructor를 가지고 있어야 하는 것이다.
 implicit parameter1)는 여러 개 가질 수 있으니 그 외에 넘기고 싶은 인자가 있으면 implicit parameter로 넘겨야 한다.
 세 번째 제약사항은 implicit class가 선언되는 scope 안에 implicit class와 같은 이름의 무언가가 있을 수 없다는 것이다.
 이것도 내부적으로 implicit converter에 해당하는 함수가 만들어지기 때문이다. 이 때문에 내부적으로 class와 같은 이름의 object를 만드는 case class는 implicit class가 될 수 없다.

1) implicit parameter에 대해서는 다음 글에서 설명하도록 하겠다.

Scala implicit keyword

2014-09-06

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

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

Scala implicit keyword

2014-09-05

[Scala] implicit keyword (0)

 Scala의 가장 인상적인 keyword를 말하라고 하면 많은 사람이 implicit을 꼽을 것이다.
 implicit은 scala의 확장성에 무한한 힘을 주는 keyword임과 동시에 코드를 읽을 때 헬 게이트를 여는 주범이기도 하다. 1)

 implicit keywod는 Implicit converter, Implicit class, Implicit parameter의 3가지 목적으로 이용된다.
 앞으로 3번에 걸쳐서 각각에 대해 설명하도록 하겠다.

1) 개인적으로 코딩할 때 대부분 언어를 vim으로 작업해왔지만, scala를 배우면서 IntelliJ라는 IDE를 사용하기 시작했다.

Scala implicit keyword