지난 블로그에서는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함수를 호출하지만, implicit
형 HashTag
멤버가 없으므로 오류를 발생합니다.
(12) implicit
형 HashTag
클래스 형 변수 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) Monoid
는 A
형에 대해 선언된 추상 클래스이고, op
와 unit
메소드를 제공합니다.
(14) 에서 Int
에 대해 Monoid
를 확장한 IntMultMonoid
를 객체를 정의했고, implicit
형으로 만들어, 암시형 인자로 자동으로 사용되도록 했다. op
메소드는 두 인자에 대한 곱셈을 수행하고, unit
메소드는 1을 반환합니다.
(19) 에서 String
에 대해 Monoid
를 확장한 StringMonoid
를 암시적 객체로 선언하고, op
메소드는 두 인자를 하나로 합치고, unit
메소드는 빈 문자열을 반환합니다.
(24) 에서 sum
을 curried 함수로 선언하고, 2번째 인자를 implicit
형 인자 m
으로 선언한 후, xs가 빈 리스트면, 암시형 인자 m
의 m.unit
을 호출하고, 빈 리스트가 아니면, m.op
를 호출합니다.
(29) 에서sum(List(2,3,4))
을 호출하면, 컴파일러는 Int
형에 대해 선언된 implicit
형 Monoid[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
를 사용하면, 코드를 함축화할 수 있어서, 좀더 읽기 쉬운 간략화된 코드를 만들 수 있고, 함축된 코드를 만드는 재미도 느낄 수 있습니다.
'SW 설계 > 스칼라' 카테고리의 다른 글
스칼라: implicit 기본 사용법 (0) | 2021.10.27 |
---|---|
스칼라: Mixins (0) | 2021.10.09 |
스칼라: Sealed, Final, Option, Try 클래스 (0) | 2021.09.25 |
스칼라: 부분적용함수 (Partially Applied Function), 부분함수 (Partial Function) 그리고 Case (0) | 2021.09.25 |