스칼라의 암시는 크게 두 가지의 방법으로 쓰인다.
1. 암시적 파라미터(암시적 인자)
메소드의 파라미터에 implicit 키워드를 사용하는 방법이다.
이미 implicit 키워드를 이용해 선언한 값이 존재할 때 그 값을 쓰게 된다.
implicit val x: Int = 500
def plusValue(paramX: Int)(implicit paramY: Int) = paramX + paramY
println( plusValue(100)(200) ) // 300
println( plusValue(200) ) // 700
이런 사용은 Future를 사용할 때를 생각하면 편하다.
ExecutionContext가 필요한 Future 함수는 두번째 인자로 ExecutionContext를 입력받는다.
* @param executor the execution context on which the future is run
* @return the `Future` holding the result of the computation
*/
def apply[T](body: =>T)(implicit @deprecatedName('execctx) executor: ExecutionContext): Future[T] =
Future의 apply
직접 입력하지 않고 scala.concurrent.ExecutionContext.Implicits.global을 import해 인자 없이 사용할 수도 있다.
import scala.concurrent.ExecutionContext.Implicits.global
val result: Future[String] = Future {
TimeUnit.SECONDS.sleep(2L)
"Hello-world"
}
result.onComplete{
case Success(str) => println(str)
case Failure(e) => e.printStackTrace()
}
import한 Implicits 오브젝트에 이미 선언된 ExecutionContext가 있기 때문이다.
implicit lazy val global: ExecutionContext = impl.ExecutionContextImpl.fromExecutor(null: Executor)
이렇게 사용하는 implicit에는 몇 가지 규칙이 있다.
1) 마지막 파라미터에만 implicit 키워드를 쓸 수 있다. 2) 파라미터 목록 가장 처음에만, 단 한번만 사용 가능하다. 3) 파라미터가 implicit 키워드로 시작하면, 그 파라미터 목록 안의 모든 파라미터는 implicit 키워드의 영향을 받는다. |
1.1 암시적 선언과 Implicity 메소드
리스트에 map 함수를 적용한 후 소팅하는 sortBy를 감싼 메소드의 예를 보자.
def sortBy1[A, B](lst: List[A])(f: A => B)(implicit ord: Ordering[B]): List[A] = {
lst.sortBy(f) // ord 생략됨
}
sortBy1은 타입 B를 Ordering[B]로 캐스팅한 값을 implicit ord로 가진다.
lst.sortBy 함수의 세 번째 파라미터는 직접 입력하지 않았지만 만들어진 ord를 사용한다.
def sortBy2[A, B: Ordering](lst: List[A])(f: A => B): List[A] = {
val ord = implicitly[Ordering[B]] // implicit로 선언된 ord와 같음.
lst.sortBy(f)
}
sortBy2는 첫 번째 함수와 같지만 파라미터 대신 함수 바디에 implicitly 메소드를 이용했다.
하지만 이 방법을 위해서는 타입 B의 상위 타입을 함수 제네릭에 제시해야만 한다.
implicity 메소드를 사용하면 val ord = ... 와 같이 값으로 직접 선언하지 않아도 implicit 키워드를 붙여 선언한 값처럼 사용할 수 있다.
1.2 암시적 파라미터를 이용한 기능 제한
암시적 파라미터로 사용할 수 있는 기능을 제한하는 함수를 만들 수 있다.
def getMenu(implicit userInfo: UserInfo) = userInfo.getPermission() match {
case "Admin" => "AdminMenu"
case _ => "UserMenu"
}
유저 정보를 매번 파라미터로 주고받을 필요 없이 암시적 파라미터를 통해 이용하는 방법이다.
2. 타입 클래스 패턴
타입 클래스 패턴을 이용하면 클래스에 인터페이스를 구현한 것과 같은 효과를 볼 수 있다.
class OuterClass(paramX: Int) {
println(s"OuterClass - $this")
def *&*(paramY: Int) = paramY * paramX
}
implicit def imp(paramX: Int): OuterClass = new OuterClass(paramX) // 아우터 클래스는 부분 함수로 만들어 사용해야 한다.
implicit class InnerClass(paramX: Int) { // 이너 클래스에는 implicit 키워드를 바로 사용 가능하다.
println(s"InnerClass - $this")
def ***(paramY: Int) = paramY * paramX
}
println(200 *** 500)
println(200 *** 500)
println(200 *&* 500)
println(200 *&* 500)
/*
InnerClass - default.Implicity$InnerClass@7e0babb1
100000
InnerClass - default.Implicity$InnerClass@5a39699c
100000
OuterClass - default.OuterClass@3cb5cdba
100000
OuterClass - default.OuterClass@56cbfb61
100000
*/
이 기능을 이용하면 이미 구현 되어있는 클래스에 새로운 메소드를 추가한 것처럼 사용이 가능하다.
많은 API나 Framework의 DSL은 타입 클래스 패턴을 사용했다고 보면 될 것이다.
이너 클래스일 때의 사용법과 아우터 클래스일 때의 사용법이 다른 것을 기억해야 할 것이다.
또 하나 특이한 점은 숫자 사이 infix 메소드가 실행될 때마다 클래스의 객체가 만들어진다는 사실이다.
2.2 암시적 변환
사실 implicit의 가장 강력한 기능은 이 암시적 변환이 아닐까 싶다.
자바에서의 자동 형변환은 부모 클래스로의 캐스팅이나 오토 박싱/오토 언박싱 등 한정적인 상황에서만 가능하다.
하지만 스칼라에서는 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 객체로 변환
많은 클래스의 컴패니언 오브젝트에는 이미 많은 암시적 변환이 선언되어 있다.
implicit def char2int(x: Char): Int = x.toInt
implicit def char2long(x: Char): Long = x.toLong
implicit def char2float(x: Char): Float = x.toFloat
implicit def char2double(x: Char): Double = x.toDouble
(Char Companion object의 암시적 형 변환들)
따라서 기본적인 형 변환은 단지 타입을 명시하는 것만으로 변환이 가능하다. 하지만 새로운 클래스를 선언했고, 잦은 형 변환을 해야한다면 implicit 키워드를 사용한 형 변환 함수를 구현하는 것이 좋을 것이다.
이렇게 implicit 키워드는 많은 곳에 사용 할 수 있다. 하지만 몇 가지 단점이 있다.
첫 번째로 implicit로 선언된 함수나 값은 지역적이지 않다.
클래스나 패키지같은 제한을 무시한 채 프로젝트에 존재한다면 그냥 사용한다는 것이다.
두 번째는 컴파일이 느려진다는 것이다.
첫 번째 단점과 이어진 문제인데, implicit으로 선언된 값을 찾기 위한 '검색 시간'이 소요되기 때문이다.
특히 동일한 implicit 값을 여러번 사용한다면 더욱 느려질 수 있다.
'Scala' 카테고리의 다른 글
Scala Function Object의 메소드들 (0) | 2017.04.01 |
---|---|
Java의 CompletableFuture, Scala의 Future와 Promise (0) | 2017.03.28 |
Java의 Comparator/Comparable, Scala의 Ordered/Ordering (0) | 2017.03.15 |
Scala로 만들어본 Merge Sort (0) | 2017.02.28 |
Scala로 만들어본 Quick Sort (0) | 2017.02.28 |