독서관련/Programming in Scala

Scala CH21. 암시적 변환과 암시적 파라미터

ColinKang 2020. 3. 29. 13:22
아래의 글은 마틴 오더스키,렉스 스푼,빌 베너스 공저 / 오현석,이동욱,반영록 공역, 『Programming in Scala 3/e』,에이콘출판사(2017), CH01의 내용을 기반으로 작성하였습니다.

 

1. 암시적 변환

메소드가 하나뿐인 인터페이스를 구현하는 내부 클래스 : 액션 리스너 등으로 외부의 이벤트를 받아 동작을 제어

스칼라에서는 함수 리터럴로 가능한데, 자바 스윙에는 없었음. 이또한 자바 8이 되면서 추상 메소드가 하나뿐인 인터페이스는 람다로 변경이 가능해짐.(SAM Single Abstract Method), 혹은 함수형 인터페이스

 

자바 스타일

var button = new JButton

button.addActionListener(

    new ActionListener {

        def actionPerformed(event: ActionEvent) = {

            println("pressed!")

        }

    }

)

 

 

 

 

implicit def function2ActionListener(f: ActionEvent => Unit) =

    new ActionListener {

        def actionPerformed(event: ActionEvent) = f(event)

    }

 

 

button.addActionListener(

    function2ActionListener(

        (_: ActionEvent) => println("pressed")

    )

)

 

 

implicit으로 선언했기 때문에 생략해도 컴파일러가 알아서 처리 해줌

 

 

button.AddActionListener(

    (_: ActionEvent) => println("pressed")

)

 

2. 암시적 규칙

암시적 정의는 컴파일러가 타입 오류를 고치기 위해 삽입 할 수 있는 정의

컴파일러는 암시적 변환을 위해서 다음과 같은 규칙에 따라 처리함.

표시규칙 : implicit로 표시한 정의만 검토 대상

 

스코프규칙 : 삽입된 implicit 변환은 스코프 내에 단일 식별자로만 존재하거나, 변환의 결과나 원래 타입과 연관이 있어야 한다.

 

예외적으로 컴파일러는 원 타입이나 변환 결과 타입의 동반 객체에 있는 암시적 정의도 살펴본다

한번에 하나만 규칙 : 오직 하나의 암시적 선언만 사용한다.

 

명시성 우선 규칙 : 코드가 그 상태 그대로 타입 검사를 통과한다면 암시를 통한 변환을 시도하지 않는다.

 

암시적 변환 이름 붙이기

 

object MyConversion

implicit def stringWrapper(s: String) : IndexedSeq[Char] = ....

import Myconversion.stringWrapper

암시가 쓰이는 부분

 

"abc".exists

stringWrapper("abc").exists 로 변환하는데 String에는 exist 메소드가 없지만, IndexSeq에는 있기 때문

 

3. 예상 타입으로의 암시적 변환

implicit def doubleToInt(x: Double) = x.toInt

 

 

val i:Int = 3.5

i: Int = 3

scala.Predef 는 모든 스칼라 프로그램에 암시적으로 임포트 됨

 

4. 호출 대상 객제 변환

용도 :

1. 수신 객체 변환을 통해 새 클래스를 기존 클래스 계층구조에 매끄럽게 통합 할 수 있다

2. 언어 안에서 도메인 특화 언어를 만드는 일을 지원

 

새 타입과 함께 통합하기

val oneHalf = new Rational(1, 2) // 1/2

oneHalf + 1

res1: Rational = 3/2

 

 

1 + oneHalf

error : overloaded method value ... cannot be applied.

 

 

implicit def intToRational(x: Int) = new Rational(x, 1)

 

 

1 + oneHalf

res2: Rational = 3/2

intToRational(1) + oneHalf

 

새로운 문법 흉내 내기

Map(1 - > "one, 2 - > "two", 3 - > "three")

스칼라 프리엠블 scala.Predef의 ArrowAssoc 클래스의 메소드

package scala

object Predef {

    class ArrowAssoc[A](x: A) {

        def -> [B](y: B): Tuple2[A, 2] = Tuple(x, y)

    }

    implicit def any2ArrowAssoc[A](x: A) ArrowAssot[A] = new ArrowAssoc(x)

}

 

암시적 클래스

case class Rectangle(width: Int, height: Int)

 

 

implicit class RectangleMaker(width: int) {

    def x(height: Int) = Rectangle(width, height)

}

 

 

implicit def RectangleMaker(width: Int) = new RectangleMaker(width)

 

 

val myRectangle = 3 x 4

myRectangle: Rectangle = Rectangle(3, 4)

암시적 클래스는 케이스 클래스 일 수 없으면, 암시 클래스의 생성자에는 파라미터가 1개만 있어야 한다.

암시 클래스는 반드시 다른 객체, 클래스, 또는 트레이트와 같은 파일에 들어 있어야 한다.

 

5. 암시적 파라미터

class PrefereedPrompt(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 PreferredPrompe"relax> ")

Greeter.greet("bob")(bobsPrompt)

 

 

Welcome, Bob. The system is ready.

relex>

 

 

object JoesPrefs {

    implicit val prompt = new PreferredPrompt("Yes, master> ")

}

 

 

Greeter.greet("Joe")

cound not find implicit value for

 

 

import JoesPrefs._

 

 

Greeter.greet("Joe")

Welcome, Joe. The system is ready.

Yes, master>

 

여러 파라미터가 있는 암시적 파라미터 목록

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 cop of " + drink.preference +" ?")

        println(prompt.preference)

    }

}

 

 

object JoesPrefs {

    implicit val prompt = new PreferencePrompt("Yes, master> ")

    implicit val drink = new PreferenceDrink("tea")

}

 

 

import JoesPrefs._

 

 

Greeters.greet("Joe")(prompt, drink)

Greeters.greet("Joe")

 컴파일러가 암시적 파라미터를 고를 때, 스코프 안에 있는 값의 타입과 파라미터의 타입이 일치하게 하기 때문에, 

실수로 일치하는 일이 적도로 (오작동), 암시적 파라미터를 충분히 드물거나, 특별한 타입으로 만들것

암시적 파라미터가 가장 많이 쓰이는 경우는 파라미터 목록의 앞쪽에 명시적으로 들어가야 하는 인자 타입에 대한 정보를 제공하고 싶은 경우.

 

암시적 파라미터에 대한 스타일 규칙

암시적 파라미터의 타입 안에는 일반적이지 않은 특별한 이름의 타입을 사용하는 게 좋다.

def maxListPoorStyle[T](elements: List[T])(implicit orderer: (T, T) => Boolean): T

흔한 제네릭 타입임.

def maxListImpParam[T](elements: List[T])(implicit ordering: Ordering[T])): T

 

6. 맥락 바운드

스칼라는 이 파라미터의 이름을 없애고 메소드 헤더를 맥락 바운드 (context bound)를 사용해 더 짧게 작성할 수 있게 해준다.

def maxList[T](elements: List[T])(implicit ordering: Ordered[T]): T

    .. if (ordering.gt(x, maxRest)) x

 

 

def implicitly[T](implicit t: T) = t

 

 

def maxList[T](elements: List[T])(implicit comparator: Ordered[T]): T

    .. if (implicitly[Ordering[T]].gt(x, maxRest)) x

 

 

def maxList[T : Ordering](elements: List[T]): T

    .. if (implicitly[Ordering[T]].gt(x, maxRest)) x

[T <: Ordered[T]] 라고 쓰면 그것은 T가 Ordered[T] 타입이어야 한다는 뜻

[T : Ordering] 이라고 쓰면 그것은 T가 어떤 것이 되어야 한다는 것을 말하지 않는다. 다만 T와 관련 있는 어떤 형태의 순서(Ordering)이 존재해야한다는 뜻

맥락 바운스를 사용하면, 순서(또는 그 타입의 어떤 다른 성질)를 필요로 하는 코드를 사용하되, 그 타입의 정의를 변경하지 않고도 그것이 가능하게 해준다.

 

7. 여러 변환을 사용하는 경우

암시적 변환이 여러개 있을 경우, 변환을 추가 하지 않는다.

2.7까지느 명시적으로 지정

2.8에서는, 가능한 변환 중 하나가 다른 하나보다 절대적으로 더 구체적이라면 컴파일러는 더 구체적인 것을 선택한다.

다른 변환 대신 항상 선택하리라고 믿을 만한 이유가 있다면, 명시적으로 사용을 요구하지 않는다.

더 구체적인 상황

  • 전자의 인자 타입이 후자의 서브타입이다.
  • 두 변환 모두 메소드 인데, 전자를 둘러싼 클래스가 후자를 둘러싼 클래스를 확장한다.

동기는, 자바 컬렉션과 스칼라 컬랙션, 그리고 문자열의 상호작용성을 향상하기 위함.

val cba = "abc".reverse

cba타입을 유추하면 직관적으로는 String이지만 collection 임.

즉 "abc" == "abc".reverse.reverse 가 false 임

2.8에서 String에서 StringOps라는 새로운 타입으로 변환하는 더 구체적인 암시 변환이 생김.

 

8. 암시 디버깅

명시적으로 변환해보거나

-Xprint:typer