Skip to content

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으로 선언되었기 때문에 컴파일러는 다음 순서의 논리적 단계를 거친다.

    1. 컴파일러는 우리가 String에서 '->' 메서드를 호출하려는 것으로 본다.(ex. "one" -> 1)
    2. String에는 -> 메서드가 없기에 범위안에서 String에게 -> 메서드를 제공하는 어떤 타입으로 변환하는 암시적 변환을 찾는다.
    3. ArrowAssoc을 발견한다.
    4. ArrowAssoc에게 "one"을 넘겨 새 ArrowAssoc 객체를 만든다.
    5. 그 뒤 -> 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)
  • 컴파일러의 암시적 변환 메서드 검색 규칙
  1. 객체와 메서드의 조합이 성공적으로 탑입 검사를 통과하는 경우 타입 변환을 시도하지 않는다.
  2. implicit 키워드가 앞에 붙은 클래스와 메서드만 고려한다.
  3. 현재 범위 안에 있는 암시적 클래스와 메서드만 고려한다. 또한 대상 타입의 동반객체 안에 정의된 암시적 메서드도 고려시 포함시킨다.
  4. 암시 메서드를 둘 이상 조합해서, 한 타입을 다른 중간 타입으로 변환한 다음에 최종 타입으로 변환하는 식의 변환은 시도하지 않는다. 오직 타입 인스턴스를 하나 받아서 목표로 하는 타읩의 인스턴스를 반환하는 메서드만 고려한다.
  5. 적용 가능한 암시적 변환이 둘 이상 있는 경우에는 변환을 수행할 수 없다. 유일하고 모호하지 않을 가능성이 있어야 가능.
// 암시적 변환 메서드 검색규칙 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의 암시적 변환 기능은 이에 대한 대안인 타입 클래스를 타입을 정적으로 지정하면서 구현할 수 있게 해준다.
Clone this wiki locally