IT박스

Scala의 유형 클래스는 무엇에 유용합니까?

itboxs 2020. 11. 14. 10:02
반응형

Scala의 유형 클래스는 무엇에 유용합니까?


이 블로그 게시물 에서 알 수 있듯이 Scala의 "유형 클래스"는 특성 및 암시 적 어댑터로 구현 된 "패턴"일뿐입니다.

블로그에 따르면 내가 특성 A과 어댑터 가 있으면 이 어댑터를 명시 적으로 호출하지 않고도 type 인수를 사용하여 B -> Atype 인수가 필요한 함수를 호출 할 수 있습니다 .AB

나는 그것이 좋지만 특별히 유용하지는 않다는 것을 알았습니다. 이 기능이 어떤 용도로 유용한 지 보여주는 사용 사례 / 예제를 제공해 주시겠습니까?


요청에 따라 하나의 사용 사례 ...

정수, 부동 소수점 숫자, 행렬, 문자열, 파형 등이 될 수있는 목록이 있다고 상상해보십시오.이 목록이 주어지면 내용을 추가하려고합니다.

이를 수행하는 한 가지 방법은 Addable함께 추가 할 수있는 모든 단일 유형에 의해 상속되어야 하는 특성 을 갖 거나 Addable인터페이스를 개조 할 수없는 타사 라이브러리의 개체를 처리 하는 경우 암시 적으로 변환 하는 것입니다.

이 접근 방식은 개체 목록에 수행 할 수있는 다른 작업을 추가하려는 경우 빠르게 압도됩니다. 또한 대안이 필요한 경우에도 잘 작동하지 않습니다 (예 : 두 파형을 추가하면 두 파형을 연결하거나 오버레이합니까?). 해결책은 애드혹 다형성으로, 기존 유형에 개조 할 동작을 선택하고 선택할 있습니다.

원래 문제의 경우 Addable유형 클래스를 구현할 수 있습니다 .

trait Addable[T] {
  def zero: T
  def append(a: T, b: T): T
}
//yup, it's our friend the monoid, with a different name!

그런 다음 추가 가능하게 만들려는 각 유형에 해당하는 암시 적 하위 클래스 인스턴스를 만들 수 있습니다.

implicit object IntIsAddable extends Addable[Int] {
  def zero = 0
  def append(a: Int, b: Int) = a + b
}

implicit object StringIsAddable extends Addable[String] {
  def zero = ""
  def append(a: String, b: String) = a + b
}

//etc...

목록을 합산하는 방법은 작성하기가 쉽습니다.

def sum[T](xs: List[T])(implicit addable: Addable[T]) =
  xs.FoldLeft(addable.zero)(addable.append)

//or the same thing, using context bounds:

def sum[T : Addable](xs: List[T]) = {
  val addable = implicitly[Addable[T]]
  xs.FoldLeft(addable.zero)(addable.append)
}

이 접근 방식의 장점은 임포트를 통해 범위에서 원하는 암시 적을 제어하거나 명시 적으로 암시 적 인수를 제공하여 일부 유형 클래스의 대체 정의를 제공 할 수 있다는 것입니다. 따라서 파형을 추가하는 다양한 방법을 제공하거나 정수 추가를위한 모듈로 산술을 지정할 수 있습니다. 타사 라이브러리의 유형을 유형 클래스에 추가하는 것도 상당히 어렵지 않습니다.

부수적으로 이것은 2.8 컬렉션 API에서 취한 접근 방식입니다. sum메서드가 on TraversableLike대신에 정의되어 있지만 List유형 클래스는 다음과 같습니다 Numeric( zero뿐만 아니라 몇 가지 작업도 포함합니다 append).


거기에서 첫 번째 주석을 다시 읽으십시오.

유형 클래스와 인터페이스 간의 중요한 차이점은 클래스 A가 인터페이스의 "멤버"가 되려면 자체 정의 사이트에서 그렇게 선언해야한다는 것입니다. 반대로, 필요한 정의를 제공 할 수있는 경우 언제든지 유형 클래스에 모든 유형을 추가 할 수 있으므로 지정된 시간에 유형 클래스의 멤버가 현재 범위에 종속됩니다. 그러므로 우리는 A의 생성자가 우리가 속하길 원하는 타입 클래스를 예상했는지 상관하지 않습니다. 그렇지 않다면 우리는 그것이 실제로 속한다는 것을 보여주는 우리 자신의 정의를 만들고 그에 따라 사용할 수 있습니다. 따라서 이것은 어댑터보다 더 나은 솔루션을 제공 할뿐만 아니라 어떤 의미에서 어댑터가 해결해야하는 전체 문제를 제거합니다.

이것이 타입 클래스의 가장 중요한 장점이라고 생각합니다.

또한 작업에 우리가 디스패치하는 유형의 인수가 없거나 둘 이상있는 경우를 적절하게 처리합니다. 예를 들어 다음 유형 클래스를 고려하십시오.

case class Default[T](val default: T)

object Default {
  implicit def IntDefault: Default[Int] = Default(0)

  implicit def OptionDefault[T]: Default[Option[T]] = Default(None)

  ...
}

타입 클래스는 타입 안전 메타 데이터를 클래스에 추가하는 기능이라고 생각합니다.

따라서 먼저 문제 도메인을 모델링하는 클래스를 정의한 다음 여기에 추가 할 메타 데이터를 생각합니다. Equals, Hashable, Viewable 등과 같은 것들이 있습니다. 이것은 문제 도메인과 클래스를 사용하는 메커니즘을 분리하고 클래스가 더 얇기 때문에 하위 클래스를 엽니 다.

그 외에는 클래스가 정의 된 위치뿐만 아니라 범위의 모든 위치에 유형 클래스를 추가 할 수 있으며 구현을 변경할 수 있습니다. 예를 들어 Point # hashCode를 사용하여 Point 클래스에 대한 해시 코드를 계산하면 내가 보유한 특정 Points 집합에 대해 좋은 값 분포를 생성하지 못할 수있는 특정 구현으로 제한됩니다. 하지만 Hashable [Point]를 사용하면 자체 구현을 제공 할 수 있습니다.

[예제 업데이트] 예를 들어 지난주에 사용한 사용 사례가 있습니다. 우리 제품에는 컨테이너를 값으로 포함하는 여러 가지지도 사례가 있습니다. 예 : Map[Int, List[String]]또는 Map[String, Set[Int]]. 이러한 컬렉션에 추가하는 것은 장황 할 수 있습니다.

map += key -> (value :: map.getOrElse(key, List()))

그래서 이것을 감싸는 함수를 갖고 싶었습니다.

map +++= key -> value

주요 문제는 컬렉션에 요소를 추가하는 방법이 모두 동일하지 않다는 것입니다. 일부는 '+'가 있고 다른 일부는 ': +'가 있습니다. 또한 목록에 요소를 추가하는 효율성을 유지하고 싶었 기 때문에 새 컬렉션을 만드는 폴드 / 맵을 사용하고 싶지 않았습니다.

해결책은 유형 클래스를 사용하는 것입니다.

  trait Addable[C, CC] {
    def add(c: C, cc: CC) : CC
    def empty: CC
  }

  object Addable {
    implicit def listAddable[A] = new Addable[A, List[A]] {
      def empty = Nil

      def add(c: A, cc: List[A]) = c :: cc
    }

    implicit def addableAddable[A, Add](implicit cbf: CanBuildFrom[Add, A, Add]) = new Addable[A, Add] {
      def empty = cbf().result

      def add(c: A, cc: Add) = (cbf(cc) += c).result
    }
  }

여기 Addable에서는 컬렉션 CC에 요소 C를 추가 할 수 있는 유형 클래스 정의 했습니다. 두 가지 기본 구현이 있습니다. For Lists using ::and for other collections, using the builder framework.

그런 다음이 유형 클래스를 사용하는 것은 다음과 같습니다.

class RichCollectionMap[A, C, B[_], M[X, Y] <: collection.Map[X, Y]](map: M[A, B[C]])(implicit adder: Addable[C, B[C]]) {
    def updateSeq[That](a: A, c: C)(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = {
      val pair = (a -> adder.add(c, map.getOrElse(a, adder.empty) ))
      (map + pair).asInstanceOf[That]
    }

    def +++[That](t: (A, C))(implicit cbf: CanBuildFrom[M[A, B[C]], (A, B[C]), That]): That  = updateSeq(t._1, t._2)(cbf)
  }

  implicit def toRichCollectionMap[A, C, B[_], M[X, Y] <: col

특수 비트는 adder.add요소를 추가하고 adder.empty새 키에 대한 새 컬렉션을 만드는 데 사용됩니다.

비교하기 위해, 타입 클래스가 없었다면 3 가지 옵션이 있었을 것입니다 : 1. 컬렉션 타입별로 메소드를 작성하는 것. 예를 들면, addElementToSubListaddElementToSet등이 하위 컬렉션 목록 / 설정되어 있는지 확인하려면 반사를 사용하여 구현하고이 오염 네임 스페이스 2에서 상용구를 많이 만듭니다. 이것은 맵이 비어 있기 때문에 까다 롭습니다 (물론 스칼라는 Manifest에서도 도움이됩니다). 3. 사용자가 가산기를 제공하도록 요구하여 가난한 사람의 유형 클래스를 갖습니다. 같은 그래서 addToMap(map, key, value, adder)추한 보통이다,


이 블로그 게시물이 유용하다고 생각하는 또 다른 방법은 유형 클래스를 설명하는 곳입니다. Monads Are Not Metaphors

기사에서 typeclass를 검색하십시오. 첫 번째 경기 여야합니다. 이 기사에서 저자는 Monad 유형 클래스의 예를 제공합니다.


포럼 스레드 " 무엇이 유형 클래스를 특성보다 더 좋게 만드는가 ? "는 몇 가지 흥미로운 점을 알려 줍니다.

  • 타입 클래스는 동등성순서 지정 과 같이 하위 유형이있는 경우 표현하기 매우 어려운 개념을 매우 쉽게 나타낼 수 있습니다 .
    연습 : 작은 클래스 / 특성 계층 구조를 만들고 계층 구조의 .equals임의 인스턴스에 대한 작업이 적절하게 반사적, 대칭 적, 전 이적 방식으로 각 클래스 / 특성에 구현 되도록하십시오.
  • 유형 클래스를 사용하면 "컨트롤"외부의 유형이 일부 동작을 준수한다는 증거를 제공 할 수 있습니다.
    다른 사람의 유형은 유형 클래스의 구성원이 될 수 있습니다.
  • "이 메소드는 메소드 수신자와 동일한 유형의 값을 취 / 반환"하는 것을 하위 유형의 관점에서 표현할 수 없지만이 (매우 유용한) 제약은 유형 클래스를 사용하여 간단합니다. 이것은 f 경계 유형 문제입니다 (F 경계 유형이 자체 하위 유형에 대해 매개 변수화되는 경우).
  • 트레이 트에 정의 된 모든 작업에는 인스턴스가 필요합니다 . 항상 this논쟁이 있습니다. 따라서 인스턴스없이 호출 할 수있는 방식으로 fromString(s:String): Foo메서드를 정의 할 수 없습니다 . Scala에서 이것은 사람들이 동반자 객체를 추상화하려고 필사적으로 시도하는 것으로 나타납니다. 그러나이 monoid 예제 에서 0 요소로 설명 된 것처럼 typeclass에서는 간단 합니다.trait FooFoo

  • 타입 클래스는 귀납적으로 정의 할 수 있습니다 . 예를 들어, 당신이 가지고 있다면 무료로 JsonCodec[Woozle]얻을 수 있습니다 JsonCodec[List[Woozle]].
    위의 예는 "함께 추가 할 수있는 것"에 대해 설명합니다.

유형 클래스를 보는 한 가지 방법은 소급 확장 또는 소급 다형성 을 활성화하는 것 입니다. 이를 달성하기 위해 Scala에서 유형 클래스를 사용하는 예를 보여주는 Casual MiraclesDaniel Westheide 의 몇 가지 훌륭한 게시물 이 있습니다.

다음 은 typeclass 예제를 포함하여 일종의 소급 적 확장 소급 수퍼 타이핑의 스칼라에서 다양한 방법을 탐색 하는 제 블로그게시물입니다 .


여기서 가능한 최선의 방법으로 설명 된 임시 다원론 외에 다른 사용 사례는 없습니다 .


암시 적유형 클래스모두 Type-conversion에 사용 됩니다. 둘 다의 주요 사용 사례는 수정할 수 없지만 상속 종류의 다형성을 기대하는 클래스에 임시 다형성 을 제공 하는 것입니다. 암시적인 경우 암시 적 def 또는 암시 적 클래스 (래퍼 클래스이지만 클라이언트에서 숨겨 짐)를 모두 사용할 수 있습니다. 타입 클래스는 이미 존재하는 상속 체인에 기능을 추가 할 수 있기 때문에 더 강력합니다 (예 : 스칼라의 정렬 함수에서 Ordering [T]). 자세한 내용은 https://lakshmirajagopalan.github.io/diving-into-scala-typeclasses/ 를 참조 하십시오.


스칼라 유형 클래스에서

  • 임시 다형성 활성화
  • 정적으로 형식화 됨 (예 : 형식 안전)
  • Haskell에서 빌린
  • 표현 문제 해결

기존 코드를 변경 / 재 컴파일하지 않고도 동작을 확장 할 수 있습니다.

Scala Implicits

메소드의 마지막 매개 변수 목록은 암시 적으로 표시 될 수 있습니다.

  • 암시 적 매개 변수는 컴파일러에 의해 채워집니다.

  • 실제로 컴파일러의 증거가 필요합니다.

  • … 범위에 유형 클래스의 존재와 같은

  • 필요한 경우 매개 변수를 명시 적으로 지정할 수도 있습니다.

아래의 예제 확장은 유형 클래스 구현이있는 String 클래스의 문자열이 최종적인 경우에도 새 메서드로 클래스를 확장합니다.

/**
* Created by nihat.hosgur on 2/19/17.
*/
case class PrintTwiceString(val original: String) {
   def printTwice = original + original
}

object TypeClassString extends App {
  implicit def stringToString(s: String) = PrintTwiceString(s)
  val name: String = "Nihat"
  name.printTwice
}

이것은 중요한 차이점입니다 (함수 프로그래밍에 필요함).

enter image description here

고려 inc:Num a=> a -> a:

a 수신은 반환되는 것과 동일하며 하위 입력으로 수행 할 수 없습니다.


I like to use type classes as a lightweight Scala idiomatic form of Dependency Injection that still works with circular dependencies yet doesn't add a lot of code complexity. I recently rewrote a Scala project from using the Cake Pattern to type classes for DI and achieved a 59% reduction in code size.

참고URL : https://stackoverflow.com/questions/5408861/what-are-type-classes-in-scala-useful-for

반응형