SW 설계/스칼라

스칼라: implicit 사용법 응용

yztech 2021. 10. 27. 04:14
반응형

지난 블로그에서는implicit의 기본적인 사용 방법을 알아보았습니다.
이번에는 좀더 구체적인 예를 가지고, 활용하는 방법을 알아보도록 하겠습니다.

예 3. 암시형 인자

앞서 보여준 예 2와 비슷하게, 자주 사용되는 hash tag를 문자열 앞에 추가하는 함수를 만들어보겠습니다.

scala> class HashTag(val s: String)
defined class HashTag

scala> def addHashTag(s: String)(implicit p: HashTag) = p.s + s
addHashTag: (s: String)(implicit p: HashTag)String

scala> addHashTag("scala")
<console>:15: error: could not find implicit value for parameter p: HashTag
       addHashTag("scala")
                 ^

scala> implicit val myHashTag = new HashTag("##")
myHashTag: HashTag = HashTag@6d2db15b

scala> addHashTag("scala")
res4: String = ##scala

위 예에서 보듯이,

(1) String형 인자를 멤버로 갖는 HashTag 클래스를 선언합니다.

(4) addHasTag함수는 HashTag 인자를 암시적으로 사용하기 위해, 2개의 인자를 나눠서 받는 curried함수로 만들고, 두의 인자만 implicit 로 선언합니다.

(7) addHashTag함수를 호출하지만, implicitHashTag 멤버가 없으므로 오류를 발생합니다.

(12) implicitHashTag 클래스 형 변수 myHashTag를 선언합니다.

(15) addHashTag 를 호출하면, 전달된 문자열 앞에, myHasgTag의 멤버 s를 붙여서 반환합니다.

예 4. 암시형 인자

// Implicit parameters
abstract class Monoid[A] {
  def op(x: A, y: A): A
  def unit: A
}

object ImplicitParamTest extends App {
/*
  implicit object IntAddMonoid extends Monoid[Int] {
    def op(x: Int, y: Int): Int = x + y
    def unit: Int = 0
  }
*/
  implicit object IntMultMonoid extends Monoid[Int] {
    def op(x: Int, y: Int): Int = x * y
    def unit: Int = 1
  }

  implicit object StringMonoid extends Monoid[String] {
    def op(x: String, y: String): String = x + y
    def unit: String = ""
  }

  def sum[A](xs: List[A])(implicit m: Monoid[A]): A = {
    if(xs.isEmpty) m.unit
    else m.op(xs.head, sum(xs.tail))
  }

  println(sum(List(2,3,4)))
  println(sum(List("a","b","c")))
}

실행결과

24
abc

위 코드에서 보면,

(2) MonoidA 형에 대해 선언된 추상 클래스이고, opunit 메소드를 제공합니다.

(14) 에서 Int에 대해 Monoid를 확장한 IntMultMonoid 를 객체를 정의했고, implicit 형으로 만들어, 암시형 인자로 자동으로 사용되도록 했다. op 메소드는 두 인자에 대한 곱셈을 수행하고, unit 메소드는 1을 반환합니다.

(19) 에서 String에 대해 Monoid를 확장한 StringMonoid 를 암시적 객체로 선언하고, op 메소드는 두 인자를 하나로 합치고, unit 메소드는 빈 문자열을 반환합니다.

(24) 에서 sum을 curried 함수로 선언하고, 2번째 인자를 implicit 형 인자 m 으로 선언한 후, xs가 빈 리스트면, 암시형 인자 mm.unit을 호출하고, 빈 리스트가 아니면, m.op를 호출합니다.

(29) 에서sum(List(2,3,4))을 호출하면, 컴파일러는 Int 형에 대해 선언된 implicitMonoid[Int] 객체를 찾고, IntMultMonoid 객체를 자동으로 전달합니다.

이 함수의 호출 과정은 다음과 같습니다.

sum(xs=List(2,3,4))

→ m.op(2, sum(List(3,4))

→ m.op(2, m.op(3,sum(List(4))))

→ m.op(2, m.op(3,m.op(4,sum(List()))))

→ m.op(2,m.op(3,m.op(4,m.unit)))

→ m.op(2,m.op(3,m.op(4,1)))

→ m.op(2,m.op(3,4))

→ m.op(2,12)

→ 24

(9)에서 Int 에 대해 Monoid를 확장한 IntAddMonoid 를 주석처리한 이유는, (29) 실행시 컴파일러는 Int형에 대해 implicit 형으로 선언한 Monoid[Int]를 찾는데, 이 객체가 두 개가 존재하므로, 오류를 발생합니다. 따라서, 하나는 주석처리해야 컴파일러 오류없이 사용할 수 있습니다.

예 5. 암시적 변환 함수

예 1에서 보여주었듯이, 암시적 변환은 한 타입을 다른 타입으로 변경하는데 필요한 명시적인 변환을 간략화할 수 있습니다.

아래 Jbutton 클래스의 addButtonDownAction함수는 버튼이 눌렸을 때 할 행동, action을 인자로 받습니다.

취할 action은 println("Pressed.")인데, 본 함수는 리스너 객체인ActionListener를 인자로 받고 있고, 그 내부에 콜백함수로 actionPerformed라는 함수를 정의해서 전달해야 합니다.

 val button = new JButton
 button.addButtonDownAction (
      new ActionListener {
            def actionPerformed(event: ActionEvent) {
                   println("Pressed.")
             }
      }
 )

스칼라는 위와 같은 코드를 암시적 변환을 사용하여, 다음과 같이 함축화할 수 있습니다.

button.addButtonDownAction((_:ActionEvent) => println("Pressed."))

이렇게 하면, addButtonDownAction함수를 사용할 때 늘 정의해야 하는 boilerplate 코드들은 숨기고, 사용자가 정의할 action만 인자로 넘길 수 있어서, 코드를 간략화할 수 있습니다. 위 함수를 호출할때, ActionEvent를 받아서 반환값이 없는 함수인자가 전달해야 하므로, 이를 위한 변환 함수를 implcit로 다음과 같이 선언해야 합니다.

 implicit def function2ActionListener(f: ActionEvent => Unit) =
     new ActionListener {
         def actionPerformed(event: ActionEvent) = f(event)
     }

지금까지 스칼라에서 사용되는 implicit를 이해하고, 활용하는 방법을 알아보았습니다.
implicit를 사용하면, 코드를 함축화할 수 있어서, 좀더 읽기 쉬운 간략화된 코드를 만들 수 있고, 함축된 코드를 만드는 재미도 느낄 수 있습니다.

반응형