암시 적 변환 대 유형 클래스


93

Scala에서는 기존 또는 새로운 유형을 개조하기 위해 적어도 두 가지 방법을 사용할 수 있습니다. 를 사용하여 무언가를 정량화 할 수 있음을 표현하고 싶다고 가정합니다 Int. 다음과 같은 특성을 정의 할 수 있습니다.

암시 적 변환

trait Quantifiable{ def quantify: Int }

그런 다음 암시 적 변환을 사용하여 예를 들어 문자열 및 목록을 수량화 할 수 있습니다.

implicit def string2quant(s: String) = new Quantifiable{ 
  def quantify = s.size 
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{ 
  val quantify = l.size 
}

이들을 가져온 후 quantify문자열과 목록 에서 메서드 를 호출 할 수 있습니다 . 수량화 가능한 목록은 길이를 저장하므로 이후에 quantify.

유형 클래스

대안은 Quantified[A]어떤 유형 A이 정량화 될 수 있음을 나타내는 "증인"을 정의하는 것입니다.

trait Quantified[A] { def quantify(a: A): Int }

우리는이 유형에 대한 클래스의 인스턴스 제공 String하고 List어딘가에.

implicit val stringQuantifiable = new Quantified[String] {
  def quantify(s: String) = s.size 
}

그런 다음 인수를 정량화해야하는 메서드를 작성하면 다음과 같이 작성합니다.

def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) = 
  as.map(ev.quantify).sum

또는 컨텍스트 바운드 구문을 사용합니다.

def sumQuantities[A: Quantified](as: List[A]) = 
  as.map(implicitly[Quantified[A]].quantify).sum

그러나 언제 어떤 방법을 사용합니까?

이제 질문이옵니다. 이 두 가지 개념을 어떻게 결정할 수 있습니까?

내가 지금까지 알아 차린 것.

유형 클래스

  • 유형 클래스는 멋진 컨텍스트 바인딩 구문을 허용합니다.
  • 유형 클래스를 사용하면 매번 사용할 때마다 새 래퍼 객체를 만들지 않습니다.
  • 유형 클래스에 여러 유형 매개 변수가있는 경우 컨텍스트 바운드 구문이 더 이상 작동하지 않습니다. 정수뿐만 아니라 일반적인 유형의 값으로도 수량화하고 싶다고 상상해보십시오 T. 유형 클래스를 만들고 싶습니다.Quantified[A,T]

암시 적 변환

  • 새 객체를 생성했기 때문에 값을 캐시하거나 더 나은 표현을 계산할 수 있습니다. 그러나 여러 번 발생할 수 있고 명시 적 변환이 한 번만 호출 될 수 있으므로 이것을 피해야합니까?

답변에서 기대하는 것

두 개념의 차이가 중요한 하나 (또는 ​​그 이상)의 사용 사례를 제시하고 내가 왜 다른 개념을 선호하는지 설명하십시오. 또한 두 개념의 본질과 서로 간의 관계를 설명하는 것은 예가 없어도 좋을 것입니다.


유형 클래스가 컨텍스트 경계를 사용하지만 "뷰 바운드"를 언급하는 유형 클래스 포인트에 약간의 혼동이 있습니다.
Daniel C. Sobral 2011

1
+1 훌륭한 질문; 나는 이것에 대한 철저한 대답에 매우 관심이 있습니다.
Dan Burton

@ 다니엘 감사합니다. 나는 항상 틀렸다.
ziggystar 2011

2
당신은 한 곳에서 잘못된 위치 : 두 번째 암시 적 변환의 예에서 사용자가 저장 size값의 목록을하고 정량화하는 후속 호출에 목록의 고가의 통과를 피할 수 있다고하지만, 당신의 모든 호출에 quantifylist2quantifiable트리거됩니다 따라서 다시 인스턴스화 Quantifiable하고 quantify속성을 다시 계산합니다 . 내가 말하는 것은 실제로 암시 적 변환으로 결과를 캐시 할 수있는 방법이 없다는 것입니다.
Nikita Volkov 2012 년

@NikitaVolkov 당신의 관찰이 맞습니다. 그리고 나는 마지막 두 번째 단락에서 내 질문에서 이것을 언급합니다. 캐싱은 변환 된 객체가 하나의 변환 메서드 호출 후 더 오래 사용되면 작동합니다 (변환 된 형식으로 전달 될 수 있음). 유형 클래스는 아마도 더 깊게 갈 때 변환되지 않은 객체를 따라 연결될 것입니다.
ziggystar

답변:


42

Scala In Depth의 자료를 복제하고 싶지는 않지만 유형 클래스 / 유형 특성이 무한히 더 유연하다는 점에 주목할 가치가 있다고 생각합니다.

def foo[T: TypeClass](t: T) = ...

로컬 환경에서 기본 유형 클래스를 검색하는 기능이 있습니다. 그러나 다음 두 가지 방법 중 하나로 언제든지 기본 동작을 재정의 할 수 있습니다.

  1. 암시 적 조회를 단락시키기 위해 Scope에서 암시 적 유형 클래스 인스턴스 생성 / 가져 오기
  2. 유형 클래스 직접 전달

예를 들면 다음과 같습니다.

def myMethod(): Unit = {
   // overrides default implicit for Int
   implicit object MyIntFoo extends Foo[Int] { ... }
   foo(5)
   foo(6) // These all use my overridden type class
   foo(7)(new Foo[Int] { ... }) // This one needs a different configuration
}

이것은 타입 클래스를 훨씬 더 유연하게 만듭니다. 또 다른 한 가지는 유형 클래스 / 특성이 암시 적 조회를 더 잘 지원한다는 것입니다 .

첫 번째 예제에서 암시 적 뷰를 사용하는 경우 컴파일러는 다음에 대해 암시 적 조회를 수행합니다.

Function1[Int, ?]

어떤에서 모양 Function1의 동반자 객체와 Int동반자 객체입니다.

공지 사항 Quantifiable없는 암시 조회에. 즉, 암시 적 뷰를 패키지 개체에 배치 하거나 범위로 가져와야합니다. 무슨 일이 일어나고 있는지 기억하는 것이 더 많은 일입니다.

반면에 유형 클래스는 명시 적 입니다. 메서드 시그니처에서 찾고있는 것을 볼 수 있습니다. 또한 암시 적 조회가 있습니다.

Quantifiable[Int]

Quantifiable의 컴패니언 객체 의 컴패니언 객체를 찾습니다 Int. 기본값을 제공 할 수 있고 새로운 유형 ( MyString클래스 와 같은 )이 동반 객체에 기본값을 제공 할 수 있으며 암시 적으로 검색됩니다.

일반적으로 유형 클래스를 사용합니다. 초기 예제에서는 훨씬 더 유연합니다. 암시 적 변환을 사용하는 유일한 장소는 Scala 래퍼와 Java 라이브러리 사이에 API 계층을 사용할 때이며,주의하지 않으면 '위험'할 수 있습니다.


20

작동 할 수있는 한 가지 기준은 새 기능이 "느끼는"방식입니다. 암시 적 변환을 사용하면 다른 방법처럼 보이게 만들 수 있습니다.

"my string".newFeature

... 유형 클래스를 사용하는 동안 항상 외부 함수를 호출하는 것처럼 보입니다.

newFeature("my string")

암시 적 변환이 아닌 형식 클래스를 사용하여 얻을 수있는 한 가지는 형식 의 인스턴스가 아닌 형식에 속성을 추가 하는 것입니다. 그런 다음 사용 가능한 유형의 인스턴스가없는 경우에도 이러한 속성에 액세스 할 수 있습니다. 표준 예는 다음과 같습니다.

trait Default[T] { def value : T }

implicit object DefaultInt extends Default[Int] {
  def value = 42
}

implicit def listsHaveDefault[T : Default] = new Default[List[T]] {
  def value = implicitly[Default[T]].value :: Nil
}

def default[T : Default] = implicitly[Default[T]].value

scala> default[List[List[Int]]]
resN: List[List[Int]] = List(List(42))

이 예제는 또한 개념이 밀접하게 관련되어있는 방법을 보여줍니다. 무한히 많은 인스턴스를 생성하는 메커니즘이 없으면 유형 클래스는 거의 유용하지 않습니다. 를 빼고 implicit방법 (안 틀림 변환), 난 단지 유한 한 많은 종류가 있습니다 가질 수 Default속성을.


@Phillippe-나는 당신이 작성한 기술에 매우 관심이 있지만 Scala 2.11.6에서 작동하지 않는 것 같습니다. 귀하의 답변에 대한 업데이트를 요청하는 질문을 게시했습니다. 당신은 사전에 감사를 도울 수 있다면 다음을 참조하십시오 : stackoverflow.com/questions/31910923/...
크리스 베드 포드

@ChrisBedford default미래의 독자 를 위해 의 정의를 추가했습니다 .
Philippe

13

이름이 지정된 래퍼를 사용하여 함수 응용 프로그램과 유사하게 두 기술의 차이점을 생각할 수 있습니다. 예를 들면 :

trait Foo1[A] { def foo(a: A): Int }  // analogous to A => Int
trait Foo0    { def foo: Int }        // analogous to Int

전자의 인스턴스는 유형의 함수를 캡슐화하는 A => Int반면 후자의 인스턴스는 이미A . 패턴을 계속할 수 있습니다 ...

trait Foo2[A, B] { def foo(a: A, b: B): Int } // sort of like A => B => Int

따라서 Foo1[B]일부에 부분적으로 적용하는 것처럼 생각할 수 Foo2[A, B]있습니다.A 인스턴스에 있습니다. 이에 대한 좋은 예는 Miles Sabin에 의해 "스칼라의 기능적 종속성" 으로 작성되었습니다 .

그래서 제 요점은 원칙적으로 다음과 같습니다.

  • (암시 적 변환을 통해) 클래스를 "포장"하는 것은 "0 차"경우입니다.
  • 타입 클래스를 선언하는 것은 "첫 번째 주문"사례입니다 ...
  • Fundeps (또는 Fundeps과 같은 것)가있는 다중 매개 변수 유형 클래스가 일반적인 경우입니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.