스칼라의 Implicit 마법과 작별 준비

스칼라에서 implicit, implicitly가 저 같은 입문자를 괴롭힌다는 소식을 접했지만 이내 Scala 3에서는 사용하지 않아도 충분한 대안이 마련되었다고 합니다. 대안은 다음 글에 작성하도록 하겠습니다(아직은 몰라요).

그럼 implicit이 사라지기 전에 가볍게 한 번 보고 지나가겠습니다.

예제

스칼라를 더 나은 자바로 사용하기 위해서 아래와 같은 코드를 사용했습니다. 이 예제 하나를 위해서는 그럭저럭 작성할 만한 코드같습니다.

먼저 아래 코드를 보시죠.

입장

case class Point(x: Int, y: Int)

@tailrec
def sumP(xs: List[Point], acc: Point = Point(0, 0)): Point = xs match {
  case h :: t => sumP(t, Point(acc.x + h.x, acc.y + h.y))
  case _      => acc
}

sumP(List(Point(1, 2), Point(3, 4))) // Point(4, 6)

멋져 보이는 코드를 작성했습니다. 하지만… 우리의 목표 implicit을 위해~!

그럼 새로운 trait을 만들어 위에서 사용할 시작 값(Point(0, 0))과 각 값을 합하는 작업을 추상화해보겠습니다.

trait HapHae[A] {
  def init: A
  def hap(x: A, y: A): A
}

def hapHaeRa[A](xs: List[A])(hapHae: HapHae[A]): A =
  xs.foldLeft(hapHae.init)(hapHae.hap) // init과 hap을 사용하기 위해 foldLeft(for 문과 비슷)로 변경!

새로운 합을 할 수 있도록 합해!(HapHae)라는 trait을 통해 거부할 수 없는 명령어 합해라!(hapHaeRa) 함수를 작성했습니다. init이라는 초깃값을 가져야 하고 hap이라는 함수를 구현해야만 잘 동작합니다.

그럼 이 HapHae는 어떻게 써먹을까요?

val pointHapHae = new HapHae[Point] {
  override def init: Point = Point(0, 0)
  override def hap(x: Point, y: Point): Point = Point(x.x + y.x, x.y + y.y)
}

hapHaeRa(List(Point(4, 2), Point(3, 4)))(pointHapHae) // // Point(7, 6)

짜잔~!

위 코드에서 보듯 초깃값과 합 함수를 구현한 새로운 HapHae를 생성했습니다. 거부할 수 없는 명령 합해라!(haeHaeRa)를 통해 결과를 잘 얻어올 수 있었습니다.

절정

implicit은 Deprecated 되었습니다. Scala 3에서 대안이 소개됩니다. 다음(다음다음 다음?!) 글에서 이 대안을 (골고루~ 골고루~)다루도록 하겠습니다!

하지만 뭔가 마음에 들지 않습니다. 뒤에 꼬리처럼 붙는 pointHapHae가 거슬리기 시작합니다. 그래서 이를 없앨 수 있는 implicit 마법을 준비해 봤습니다. 간단히 키워드(implicit)를 더해주기만 하면 됩니다. 임시 다형성을 통해서 이를 해결해 보겠습니다.

임시 다형성(ad-hoc polymorphism)은 타입 파라미터를 이용해 암시적 인스턴스를 주입받아 사용하는 방법입니다.

이번에는 사람(Person)을 합해보도록 하겠습니다.

case class Person(name: String, age: Int)

implicit val personHapHae = new HapHae[Person] { // <--------- 여기
  override def init: Person = Person("", 0)
  override def hap(x: Person, y: Person): Person = Person(x.name + y.name, x.age + y.age)
}

// ...

def hapHaeRa[A](xs: List[A])(implicit hapHae: HapHae[A]): A = ...  // <--------- 여기

// 사용
hapHaeRa(List(Person("Hello + ", 30), Person("Scala", 40))) // Person(Hello + Scala, 70)

hapHaeRa의 꼬리 personHapHae가 사라졌지만, 코드는 컴파일되고 정상 작동합니다. implicit을 사용해서 귀찮은 꼬리 하나를 뗴는 마법을 부려봤습니다.

계속 이야기했지만 곧 사라질 예정이긴 하지만 어딘가엔 기존 코드에 남아있을… 혹은 다른 프레임워크 등에 산재해 있을 implicit을 이해하는 데 도움이 되었으면 좋겠습니다.

결론

implicit은 알아두되 사용은 자제하고 대안으로 소개된 Scala 3 에서의 방법을 사용하자!