-
Notifications
You must be signed in to change notification settings - Fork 7
5.3 암시적 변환
QyuStory edited this page Sep 5, 2016
·
20 revisions
scala> Map("one" -> 1, "two" -> 2)
res0: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2)
- 이 때 Map.apply 메서드는 쌍으로 이루어진 가변 길이 인자 목록을 원함.
- 실제로 위에서 '->'는 메서드이다. 그러나 String이나 Int에는 정의되어 있지 않다.
- 스칼라의 암시적 변환 기능을 통해 이 메서드를 임의 타입의 두 값에 적용할 수 있도록 해줌.
def apply[A, B](elems: (A, B)*): Map[A, B]
- 또한 a -> b는 튜플을 위한 리터럴이 아니기때문에 이 메서드를 (a, b)형태가 나오도록 해줘야 함
- 아래의 코드처럼 ->가 정의된 래퍼(wrapper)객체를 사용하는 트릭을 통해 암시적 변환을 사용.
- ArrowAssoc 클래스는 Any 타입의 객체를 받아서 '->'가 호출되면 쌍을 반환한다.
implicit final class ArrowAssoc[A](val self: A) {
def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
}
- 물론 위에서 정의한 wrapper 객체를 통해 다음과 같이 Map을 생성할 수 있다.
scala> Map(new ArrowAssoc("one") -> 1, new ArrowAssoc("two") -> 2)
res0: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2)
-
클래스가 implicit으로 선언되었기 때문에 컴파일러는 다음 순서의 논리적 단계를 거친다.
- 컴파일러는 우리가 String에서 '->' 메서드를 호출하려는 것으로 본다.(ex. "one" -> 1)
- String에는 -> 메서드가 없기에 범위안에서 String에게 -> 메서드를 제공하는 어떤 타입으로 변환하는 암시적 변환을 찾는다.
- ArrowAssoc을 발견한다.
- ArrowAssoc에게 "one"을 넘겨 새 ArrowAssoc 객체를 만든다.
- 그 뒤 -> 1 부분을 처리하고 전체 식의 타입이 Map.apply가 요구하는 튜플 인스턴스와 맞아떨어지는지 검사한다.
-
암시적 변환의 고려 대상이 되기 위해서는 반드시 implicit 키워드와 함께 선언되어야 한다.
-
반드시 인자를 하나만 받는 생성자가 있는 클래스거나 인자를 단 하나나만 받는 메서드여야만 한다.
class Complex(real: Double, imaginary: Double) {
def mkString = "" + real + " + " + imaginary + "i"
}
implicit class MakeCom(val real: Double) {
def makeCom(imagin: Double):Complex = new Complex(real, imagin)
}
val complex = 12.34 makeCom 4.5
println(complex.mkString)
- 암시적 메서드는 다른 목적을 위해 이미 존재하는 암시적이지 않은 타입을 사용해서 무언가를 변환해야하는 경우에 사용.
- 암시적 메서드를 무분별하게 사용하면 디버깅이 어려운 이상한 동작을 야기할 수 있다.
- Scala 2.10부터 암시적 메서드를 선택적 기능으로 간주한다.
- import scala.language.implicitConversions
- 전역 컴파일러 옵션 -language:implicitConversions 사용
final class ArrowAssoc[A](val self: A) {
def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
}
...
implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
- 컴파일러의 암시적 변환 메서드 검색 규칙
- 객체와 메서드의 조합이 성공적으로 탑입 검사를 통과하는 경우 타입 변환을 시도하지 않는다.
- implicit 키워드가 앞에 붙은 클래스와 메서드만 고려한다.
- 현재 범위 안에 있는 암시적 클래스와 메서드만 고려한다. 또한 대상 타입의 동반객체 안에 정의된 암시적 메서드도 고려시 포함시킨다.
- 암시 메서드를 둘 이상 조합해서, 한 타입을 다른 중간 타입으로 변환한 다음에 최종 타입으로 변환하는 식의 변환은 시도하지 않는다. 오직 타입 인스턴스를 하나 받아서 목표로 하는 타읩의 인스턴스를 반환하는 메서드만 고려한다.
- 적용 가능한 암시적 변환이 둘 이상 있는 경우에는 변환을 수행할 수 없다. 유일하고 모호하지 않을 가능성이 있어야 가능.
// 암시적 변환 메서드 검색규칙 3번 예
import scala.language.implicitConversions
case class Foo(s: String)
object Foo {
implicit def fromString(s: String): Foo = Foo(s)
}
class O {
def m1(foo: Foo) = println(foo) // 컴파일러는 클래스 O에 import해주지 않아도 Foo.fromString 메서드를 찾는다.
def m(s: String) = m1(s) // s:String가 Foo이기를 기대하면서 m1 호출
}
// 암시적 변환 메서드 검색규칙 4번 예
import scala.language.implicitConversions
case class Foo(s: String)
object Foo {
implicit def withInt(i: Int): String = i.toString // 중간 타입으로 변형을 통해 암시적 변환 불가능
implicit def fromString(s: String): Foo = Foo(s)
}
class O {
def m1(foo: Foo) = println(foo)
def m(s: String) = m1(s)
}
// 암시적 변환 메서드 검색규칙 5번 예
import scala.language.implicitConversions
case class Foo(s: String)
object Foo {
implicit def fromString(s: String): Foo = Foo(s)
implicit def withString(s: String): Foo = Foo(s) //적용 가능한 암시적 메서드가 두개 -> Error발생
}
class O {
def m1(foo: Foo) = println(foo)
def m(s: String) = m1(s)
}
##문자열 인터폴레이터를 만들자
- StringContext를 새로운 메서드와 함께 확장한 암시적 변환을 이용하여 우리만의 인터폴레이터를 만들자
import scala.util.parsing.json._
object Interpolators {
implicit class jsonForStringContext(val sc: StringContext) { //implicit class는 객체 안에 정의되어야 한다.(범위제한)
//import문을 통해 코드의 범위안으로 class를 호출한다.
def json(values: Any*): JSONObject = { // json 메서드. argument list를 받고 JSONObject를 리턴한다.
val keyRE = """^[\s{,]*(\S+):\s*""".r // 문자열 부분에서 key name을 추출하기 위한 일반적 표현
val keys = sc.parts map { // 일반적 표현을 통해 key name을 문자열 부분으로부터 추출한다.
case keyRE(key) => key // 일반적 표현과 매치되면 key 를 리턴
case str => str // 매치되지 않으면 전체 String을 사용
}
val kvs = keys zip values // key값과 values값을 통해 key value pair를 만들어준다.
JSONObject(kvs.toMap) // key value pari를 이용해 Map을 만들고 JSONObject를 만들기 위해 사용한다.
}
}
}
import Interpolators._
val name = "Dean Wampler"
val book = "Programming Scala, Second Edition"
val jsonobj = json"{name: $name, book: $book}"
println(jsonobj)
##표현력 문제
- 모듈을 확장하는데 있어서 그들의 소스코드를 변화시키지 않으려는 바람을 표현력 문제(Expression Problem)이라고 한다.
- 객체지향에서는 서브타입 다형성(polymorphism)을 통해서 그 문제를 해결하고 있다.
- 서브타입 다형성은 동작을 추상적으로 기술하기 때문에 타입 계층의 어느부분에서 동작을 처음 정의해야 하는지, 그리고 어떤 동작이 일부만 필요할 때 문제가 발생한다.
- Scala의 암시적 변환 기능은 이에 대한 대안인 타입 클래스를 타입을 정적으로 지정하면서 구현할 수 있게 해준다.