Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행했다.

21장

암시적 변환

암시적 변환 예

  • 서로를 고려하지 않은 두 독립 소프트웨어를 한데 묶는데 유용
// (1) 자바 코드를 스칼라 코드로 그대로 포팅
val button = new JButton
button.addActionListener(
  new ActionListener {
    def actionPerformed(event: ActionEvent) {
      println("pressed!")
    }
  }
)

// 암시적 변환 코드
implicit deff function2ActionListener(f: ActionEvent => Unit) =
  new ActionListener {
    def actionPerformed(event: ActionEvent) = f(event)
  }

// (2) 변환 코드 사용
button.addActionListener(
  function2ActionListener(
    (_: ActionEvent) => println("pressed!")
  )
)

// (3) 암시적 변환 사용
button.addActionListener(
  (_: ActionEvent) => println("pressed!")
)

암시적 변환 규칙

  • 표시 규칙 : implicit로 표시한 정의만 검토 대상이다
  • 스코프 규칙 : 삽입할 implicit 변환은 스코프 내에 단일 식별자로만 존재하거나, 변환의 결과나 원래 타입과 연관이 있어야 한다(단, 원 타입이나 변환 결과 타입의 동반 객체에 있는 암시적 정의도 검토 대상임)
  • 한번에 하나만 규칙 : 오직 하나의 암시적 선언만 사용한다
  • 명시성 유선 규칙 : 코드가 그 상태 그대로 타입 검사를 통과한다면 암시를 통한 변환을 시도하지 않는다

암시적 변환 이름

  • 명시적으로 변환을 사용할 때 사용
  • 특정 지점에 사용 가능한 암시적 변환을 확인해야할 경우(사용하는 변환만 임포트)

암시가 사용되는 부분

  • 예상 타입 암시적 변환 : implicit def int2double(x: Int): Double = x.toDouble
  • 호출 대상 객체 변환 : "abc".exists === stringWrapper("abc").exists
// 호출 대상 객체 변환 - 새 타입과 함께 통합
implicit def intToRational(x: Int) = new Rational(x, 1)

val oneHalf = new Rational(1, 2)
1 + oneHalf
// Rational = 3/2
// 호출 대상 객체 변환 - 새로운 문법 흉내 내기
package scala
object Predef {
  class ArrowAssoc[A](x: A) {
    def -> [B](y: B): Tuple2[A, B] = Tuple2(x, y)
  }
  implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
}

Map(1 -> "one", 2 -> "two", 3 -> "three")
  • 암시적 파라미터

암시적 파라미터

// 암시적 파라미터 예
class PreferredPrompt(val preference: String)

object Greeter {
  def greet(name: String)(implicit prompt: PreferredPrompt) {
    println("Welcome, "+ name +". The system is ready.")
    println(prompt.preference)
  }
}

// 명시적 사용 예
val bobsPrompt = new PreferredPrompt("relax> ")
Greeter.greet("Joe")(bobsPrompt)

// 암시적 파라미터 사용 예
object JoesPrefs {
  implicit val prompt = new Preferredprompt("Yes, master> ")
}

import JoesPrefs._
Greeter.greet("Joe")
// 암시적 파라미터 예 - 두개 파라미터
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)

object Greeter {
  def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) {
    println("Welcome, "+ name +". The system is ready.")
    print("But while you work, ")
    println("why not enjoy a cup of "+ drink.preference +"?")
    println(prompt.preference)
  }
}

object JoesPrefs {
  implicit val prompt = new PreferredPrompt("Yes, master> ")
  implicit val drink = new PreferredDrink("tea")
}

import JoesPrefs._

// 명시적 사용 예
Greeter.greet("joe")(prompt, drink)

// 두 암시적 파라미터 사용 예
import JoesPrefs._
Greeter.greet("joe")
  • 암시적 파라미터 타입은 충분히 드물거나 특별해야한다. -> 암시적 파라미터의 타입 안에는 역할을 알려주는 이름을 최소한 하나 이상 사용하는 것이 좋다.
  • 암시적 파라미터는 앞쪽 파라미터 목록에서 명시적으로 사용한 인자 타입에 대한 정보를 제공하는 경우에 많이 사용된다.
// 원소 타입이 Ordered의 서브 타입이 아닌 리스트에서 사용할 수 없음!
def maxListUpBound[T <: Ordered[T]](elements: List[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListUpBound(rest)
      if (x > maxRest) x
      else maxRest
  }

// 암시적 파라미터 사용
def maxListImpParm[T](elements: List[T])(implicit orderer: T => Ordered[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListImpParm(rest)(orderer)
      if (orderer(x) > maxRest) x
      else maxRest
  }

뷰 바운드

  • 파라미터에 대해 implicit을 사용하는 경우, 메소드 본문 안에서 implicit 값으로 취급됨
// 메소드 본문 안에서 implicit 사용
def maxList[T](elements: List[T])(implicit orderer: T => Ordered[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListImpParm(rest)    // orderer
      if (x > maxRest) x                    // orderer(x)
      else maxRest
  }

// 뷰 바운드 사용
def maxList[T <% Ordered[T]](elements: List[T]): T =
  elements match {
    case List() => throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
      val maxRest = maxListImpParm(rest)    // orderer
      if (x > maxRest) x                    // orderer(x)
      else maxRest
  }
  • T <% Ordered[T] : T를 Ordered[T]로 다룰 수 있는 모든 T 타입을 사용할 수 있다
  • 타입 T가 이미 Ordered[T]인 경우에는 Predef의 identity 함수 사용
implicit def identity[A](x: A): A = x // 받은 객체를 그대로 반환함

적용 가능한 암시적 변환이 여러개인 경우

  • ~ Scala 2.7 : 컴파일 오류
  • Scala 2.8 : 더 구체적인 변환 적용
    • 인자 타입이 다른 것의 서브 타입이다
    • 변환이 모두 메소드인데, 하나를 둘러싼 클래스가 다른 것을 둘러싼 클래스를 확장한다.
val cba = "abc".reverse
// 2.7 : cba는 컬렉션 - String -> 스칼라 컬렉션 변환
// 2.8 : cba는 String - 더 구체적인 String -> SringOps 변환 추가(reverse 메소드가 String 반환)

암시 디버깅

  • 컴파일러가 암시를 찾지 못하는 경우, 명시적으로 변환을 써보고 오류가 발생하는지 확인한다.
  • 컴파일러에 -Xprint:typer 옵션 사용하여, 타입 검사기가 추가한 모든 암시적 변환이 있는 코드를 확인한다.


+ Recent posts