Implicit class 최적화

2024. 5. 14. 09:00· Scala/언어
목차
  1. value class
  2. Implicit class 최적화
  3. value class를 실제로 생성하게 되는 상황
  4. value class가 array에 할당될 때
  5. 언제 implicit class와 value class를 같이 쓸 수 있을까?
  6. 오직 하나의 public 형태의 주 생성자만 존재해야 하며, 주 생성자는 개발자가 선언한 value class 이외의 타입을 하나만 받는 형태여야 한다
  7. @specialized를 사용하면 안 된다
  8. 내부에 중첩된 local class, trait, object를 만들면 안 된다
  9. 직접 equals와 hashCode를 만들면 안 된다
  10. top-level class 이거나 전역적으로 접근 가능한 object의 멤버여야 한다
  11. def만 멤버로 지닐 수 있다. lazy vals, vars, val members를 지닐 수 없다
  12. 다른 클래스가 value class를 상속받을 수 없다
  13. 결론
반응형

implicit class는 마치 대상 타입을 확장하는 것과 같은 기능을 컴파일러 적으로 지원해주어 사용자가 더욱 효율적으로 코드를 짤 수 있게 도와줍니다.

다만 이 기능은 대상 클래스를 감싸는 implicit class를 생성하는 문장을 암시적으로 끼워넣기 때문에 매 번 implicit class를 생성하는 부가 비용이 추가됩니다.

이러한 부가 비용은 단순히 한 두 번 호출하는 정도로는 크게 상관이 없지만, 이를 자주 사용하는 상황에서는 어플리케이션에 조금씩이지만 누적되는 부담을 줍니다.

이러한 부가 비용을 없애는 방법으로 implicit class를 value class로 바꾸는 방법이 존재합니다.

 

value class

설명하는 기준은 scala 2 기준입니다. scala 3를 사용중인 입장이라면 opaque types을 사용하면 됩니다.
document 설명을 한글로 번역하였습니다.

 

value class는 scala 2.10에 처음으로 소개된 기능으로 scala가 runtime object를 할당하는 부담을 피하기 위해 도입한 기능입니다.

class를 value class로 바꾸고 싶다면 AnyVal 서브클래스를 상속하면 됩니다.

 

아래는 아주 간단한 value class의 정의입니다.

class Wrapper(val underlying: Int) extends AnyVal

 

value class는 다음의 정의를 지켜야 합니다

  • 오직 하나의 val 파라미터만 지녀야 합니다
  • def는 마음껏 정의할 수 있습니다
  • val, var, nested trait, nested class, nested object 를 정의할 수 없습니다
  • universal trait만 추가로 상속받을 수 있습니다
더보기
더보기

universal trait 는 Any를 상속받고 def만 멤버로 지니고 있으며 초기화 함수가 없는 trait입니다.

universal trait는 value class가 기본적으로 지녀야 할 함수를 제한할 수 있지만 실제 객체를 생성하게 만들어 value class의 생성 부가 비용을 없애는 장점이 희석됩니다.

위의 Wrapper value class를 사용하게 되면 underlying의 runtime 타입은 Int이지만 compile 타입은 Wrapper로 사용할 수 있어 사용자 편의성을 증대시켜 줍니다.

value class의 또 다른 장점은 runtime 생성 비용을 피하면서 데이터 타입에 대한 타입 안전성을 높여주는 것입니다.

아래의 Meter 클래스를 사용해서 계산을 하게 되면 Meter 인스턴스를 생성하지 않고 실제로는 double 값만 사용해서 계산을 하게 됩니다.

case class Meter(value: Double) extends AnyVal {
def +(other: Meter): Meter = Meter(value + other.value)
}
val x = Meter(3.4)
val y = Meter(4.3)
val z = x + y

 

Implicit class 최적화

implicit class는 주로 확장 함수를 추가하기 위해서 사용합니다. 다만 이를 사용하게 되면 컴파일러가 암시적으로 implicit class의 생성자로 해당 타입을 감싸서 추가적인 생성 비용이 들기 때문에 너무 남용하면 메모리에 좋지 않습니다.

이러한 부작용을 막기 위해 value class 기능을 사용하게 되면 생성 비용을 없앨 수 있어 더욱 효율적인 사용이 가능합니다.

 

이의 구체적인 예시는 Scala의 standard library에서 기본적으로 제공하는 RichInt implicit class로 실제 예시는 다음과 같습니다.

implicit class RichInt(val self: Int) extends AnyVal {
def toHexString: String = java.lang.Integer.toHexString(self)
}

 

runtime에 3.toHexString을 호출하면 RichInt를 직접 생성하지 않고 static object(RichInt$.MODULE$.toHexString$.extension(3))를 불러 이를 호출하는 형태로 바뀌게 되어 추가적인 생성 비용 없이 확장 함수를 사용할 수 있게 됩니다,

 

implicit class와 value class를 섞기만 하면 무조건 이러한 장점들을 얻을 수 있을거라 생각할 수 있지만 이것도 역시 상당수의 제약사항을 지켜야만 가능합니다.

JVM이 태생적으로 이러한 value class 기능을 지원해주는 것이 아니기 때문에 언제 실제 class를 만들게 될 지, 어떻게 해야 피할 수 있을지 개발자가 알고 유의하여 코드를 개발해야 합니다.

 

value class를 실제로 생성하게 되는 상황

다음과 같은 상황일 때 컴파일러는 실제 value class를 만들게 됩니다.

  • value class가 다른 타입으로 인식될 때
  • value class가 array에 할당될 때
  • pattern matching과 같은 runtime type checking을 시도할 때

 

value class가 다른 타입으로 인식될 때

value class가 universal trait로 인식될 때를 포함해서 어떠한 다른 타입으로 인식된다면 컴파일러는 강제로 실제 객체를 생성해버립니다.

value class가 다른 타입으로 인식되는 상황은 다음의 두 가지 상황입니다.

  • value class가 함수에서 다른 타입의 파라미터로 건네질 때 (universal trait도 포함됩니다)
  • value class가 type argument로 사용될 때

만약 Meter value class가 있고 Distance universal trait를 상속받았다고 할 때, 이 Distance를 받는 함수에 Meter를 건네주면 Meter를 Distance라는 다른 타입으로 인식하고 실제 객체를 생성합니다.

trait Distance extends Any
case class Meter(value: Double) extends AnyVal with Distance
// Meter는 Distance로 여겨지므로 실제 객체를 생성해버립니다
def add(a: Distance, b: Distance): Distance = ???
add(Meter(3.4), add(4.3))

 

만약 type argument로 value class를 사용하게 된다면 심지어 identity 함수여도 실제 객체를 생성합니다.

// type argument로 value class를 받았기 때문에 실제 객체를 생성합니다
def identity[T](t: T): T = t
identity(Meter(5.0))

 

value class가 array에 할당될 때

value class가 array에 할당되면 array가 심지어 value class만 받는 array이더라도 실제 객체를 생성합니다.

이 역시 JVM이 value class를 지원하지 않기 때문에 생겨난 문제입니다.

val m = Meter(5.0)
val array = Array[Meter](m)

 

pattern matching과 같은 runtime type checking을 시도할 때

다음처럼 실제 runtime type cheking을 시도하면 실제 객체를 생성하게 됩니다.

case class P(i: Int) extends AnyVal
val p = P(3)
// 여기서 패턴 매칭을 시도하면서 실제 객체를 생성합니다
p match {
case P(3) => println("Matched 3")
case P(_) => println("Not 3")
}
// 여기서 타입을 변환하면서 실제 객체를 생성합니다
p.asInstanceOf[P]

 

언제 implicit class와 value class를 같이 쓸 수 있을까?

value class 제약사항들을 모두 지킬 수 있다면 implicit class에 value class를 같이 사용하여 runtime 생성 부가비용을 줄일 수 있습니다.

value class의 제약사항은 다음과 같으므로 이를 모두 지켜주면 됩니다.

 

오직 하나의 public 형태의 주 생성자만 존재해야 하며, 주 생성자는 개발자가 선언한 value class 이외의 타입을 하나만 받는 형태여야 한다

위의 제약사항을 지켜야 하지만, 필요하다면 다음의 방식으로 우회할 수 있습니다.

value class를 감싸는 value class가 필요해지면 다음처럼 우회하면 됩니다.

case class Meter(value: Int) extends AnyVal
case class RichMeter(value: Int) extends AnyVal {
def +(other: Meter) = Meter(value + other.value)
}
object RichMeters {
implicit def wrap(m: Meter): RichMeter = RichMeter(m.value)
}
import RichMeters._
import scala.language.implicitConversions
// x와 y는 int로 여겨집니다
object Test {
def test = {
val x = Meter(3)
val y = Meter(4)
x + y
}
}
// 디컴파일 결과
public final class Test$ {
public static final Test$ MODULE$ = new Test$();
public int test() {
int x = 3;
int y = 4;
return .MODULE$.$plus$extension(slicktest.RichMeters..MODULE$.wrap(x), y);
}
private Test$() {
}
}

 

만약 implicit class에 정렬과 같은 implicit 파라미터가 필요하다면 아래처럼 실제 함수에 implicit 파라미터를 옮기면 됩니다.

implicit class RichOrdering[T](test: Test[T]) extends AnyVal {
def sort(other: Foo[T])(implicit ord: Ordering[T]) = ???
}

 

@specialized를 사용하면 안 된다

이 제약사항은 무조건 지켜야 합니다.

 

내부에 중첩된 local class, trait, object를 만들면 안 된다

내부에 중첩하지 말고 외부에서 만들고 참조하여 사용하는 식으로 우회하면 됩니다.

 

직접 equals와 hashCode를 만들면 안 된다

implicit class 자체가 이미 직접 equals와 hashCode를 만들면 안 되어서 강제로 지킬 수 있습니다.

 

top-level class 이거나 전역적으로 접근 가능한 object의 멤버여야 한다

일반적으로 implicit class는 전역적으로 접근 가능한 식으로 만들기 때문에 크게 문제되지 않습니다.

 

def만 멤버로 지닐 수 있다. lazy vals, vars, val members를 지닐 수 없다

implicit class는 이러한 멤버들을 지닐 수 있지만 def 이외의 사용처는 존재하지 않으므로 크게 문제되지 않습니다.

 

다른 클래스가 value class를 상속받을 수 없다

implicit class는 상속될 수 있지만 그 사용처는 딱히 존재하지 않으니 크게 문제되지 않습니다.

 

결론

implicit class의 런타임 생성 비용이라는 문제점을 value class와 같이 사용하여 장점만 남길 수 있습니다.

다만 value class의 제약사항과 실제 객체가 생성되는 문제상황을 개발자가 알아야 하기 때문에 이러한 제약 사항을 알고 잘 지켜주면 됩니다.

 

value class의 제약사항

  1. 오직 하나의 public 형태의 주 생성자만 존재해야 하며, 주 생성자는 개발자가 선언한 value class 이외의 타입을 하나만 받는 형태여야 한다
  2. @specialized를 사용하면 안 된다
  3. 내부에 중첩된 local class, trait, object를 만들면 안 된다
  4. 직접 equals와 hashCode를 만들면 안 된다
  5. top-level class이거나 전역적으로 접근 가능한 object의 멤버여야 한다
  6. def 만 멤버로 지닐 수 있다. lazy vals, vars, val member를 지닐 수 없다
  7. 다른 클래스가 value class를 상속받을 수 없다

value class가 실제로 생성되는 상황

  1. value class가 다른 타입으로 인식될 때
  2. value class가 array에 할당될 때
  3. pattern matching과 같은 runtime type checking을 시도할 때
반응형
  1. value class
  2. Implicit class 최적화
  3. value class를 실제로 생성하게 되는 상황
  4. value class가 array에 할당될 때
  5. 언제 implicit class와 value class를 같이 쓸 수 있을까?
  6. 오직 하나의 public 형태의 주 생성자만 존재해야 하며, 주 생성자는 개발자가 선언한 value class 이외의 타입을 하나만 받는 형태여야 한다
  7. @specialized를 사용하면 안 된다
  8. 내부에 중첩된 local class, trait, object를 만들면 안 된다
  9. 직접 equals와 hashCode를 만들면 안 된다
  10. top-level class 이거나 전역적으로 접근 가능한 object의 멤버여야 한다
  11. def만 멤버로 지닐 수 있다. lazy vals, vars, val members를 지닐 수 없다
  12. 다른 클래스가 value class를 상속받을 수 없다
  13. 결론
'Scala/언어' 카테고리의 다른 글
  • Scala 공부해볼만한 사이트 추천
  • Scala의 Implicit class 정의
  • Scala에서의 상수 정의
  • Scala Duration을 이용한 java LocalDateTime 계산
sleeptoy
sleeptoy
프로그래밍 관련 지식과 경험을 공유합니다
sleeptoy
쉬어가는 장난감
sleeptoy
반응형
전체
오늘
어제
  • 분류 전체보기 (50)
    • Scala (28)
      • 언어 (6)
      • EffectiveScala (15)
      • Dependency injection (2)
      • Collections API (5)
    • Java (1)
      • 언어 (1)
    • 함수형 프로그래밍 (3)
    • Database (3)
      • Slick (3)
    • 프로그래밍 공통 (13)
      • WEB (2)
      • 개발론 (0)
      • 테스트 (11)
    • 트러블슈팅 (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록
  • 글쓰기

공지사항

인기 글

태그

  • scala
  • implicit class
  • Functional programming
  • 단위테스트
  • Slick
  • scalatest
  • TEST
  • Functor
  • twitter
  • collections api
  • either
  • effective scala
  • scala slick
  • RESTful
  • database
  • restfulapi
  • 함수형 프로그래밍
  • Dependency Injection
  • java
  • Collections

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.3.0
sleeptoy
Implicit class 최적화
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.