스칼라의 타이프클래스 소개

  • 파트 1:이 게시물
  • 파트 2:스칼라의 유형 안전 직렬화
  • 파트 3:더 높은 종류의 유형을 사용한 유형별 오류 처리

유형 클래스 란 무엇입니까

자신의 코드에서 유형 클래스를 이해하고 사용하는 가장 큰 장벽 중 하나는 이름 자체입니다. 이름이 불러 일으키는 것과는 달리 형식 클래스는 객체 지향 언어의 클래스가 아닙니다. 위키 백과에 따르면:

컴퓨터 과학에서 유형 클래스는 임시 다형성을 지원하는 유형 시스템 구성입니다. 이는 매개 변수 적으로 다형성 유형의 유형 변수에 제약 조건을 추가하여 달성됩니다.

포장 풀기:

  • ‘임시’다형성이란 무엇입니까? 그것이 무엇이든,그것은 유형 클래스가 우리가 달성하는 데 도움이 될 것입니다.
  • ‘파라메트릭 다형성’유형이란? 그것이 무엇이든,그것이 우리가 우리 자신의 유형 클래스를 만드는 방법입니다.

유형 클래스는 하스켈에 처음 나타 났으며 하스켈에서 추상화를 구축하는 기본적인 방법입니다. 그들은 그들이 당신에게 하스켈 책을 배우기의 첫 페이지에 소개되어 너무 중요합니다:

형식 클래스는 일부 동작을 정의하는 일종의 인터페이스입니다. 형식이 형식 클래스의 일부인 경우 형식 클래스에서 설명하는 동작을 지원하고 구현한다는 의미입니다.

좋아,그래서 우리는 우리의 유형에 대한 몇 가지 동작을 정의하고 유형에 따라 그 동작을 다르게 구현하려고하는 것 같습니다. 마지막 부분 인”유형에 따라 다르게 동작 구현”은 이전의’다형성’비트입니다. 그래서 이 용어는 이해되기 시작했습니다.그것은 유형에 대한 클래스입니다. 형식 클래스에서 지정한 동작을 구현하는 클래스는 해당 형식 클래스(‘형식 클래스’)의 멤버라고 합니다.

이 시점에서 이것이 상속처럼 들린다고 말하는 것이 적절할 것입니다. 둘 다 추상화를 구축하고 다형성을 제공 할 수 있습니다.

그렇다면 왜 유형 클래스를 탐색해야합니까? 대부분의 경우 상속 계층 구조보다 훨씬 강력하고 확장 가능합니다. 그들은 훨씬 더 간단 할 수 있으며,코드베이스가 이미 우에 기댈 때 훨씬 더 잘 맞습니다. 강력한 프로젝트에서도 유형 클래스는 여전히 하루를 절약 할 수 있습니다.

타입클래스 정의

소개로 충분하다.

우리는 자바(따라서 스칼라)의 모든 객체에 존재하는 방법에 익숙합니다..toString. 그 목적은 해당 개체의 문자열 표현을 생성하는 것입니다. 이 작업을 수행하기 위해 유형 클래스를 사용하는 방법을 살펴 보겠습니다. 하스켈의toString버전은Show이라고 불리기 때문에 그 이름을 대략적으로 고수 할 것입니다:

trait Showable {
def show(a: A): String
}

그게 다야:Showable유형 클래스가 있습니다. 왜 형식 매개 변수A을 사용합니까? 우리가 유형 클래스의 첫 번째 정의를 기억한다면,우리는”매개 변수 적으로 다형성 유형의 유형 변수에 제약 조건을 추가하여 달성했습니다.”Showable은 매개 변수 적으로 다형성 유형이며,ㅏ 우리의 유형 변수,이는 단순히 유형 매개 변수로 유형(A)을 취한다는 것을 의미합니다. (우리는 정의에 따라A에 어떤 제약 조건도 두지 않았지만 괜찮습니다. 그것은 유형 클래스를 함께 구성하거나 나중에 사용할 때 우리가하는 일입니다.)

타입 클래스 구현

이제 우리는 보여줄 수 있기를 원하는 모든 유형에 대해Showable특성의 구현을 제공해야합니다. 이러한 구현은 문자 그대로 특성Showable의 인스턴스이기 때문에 유형 클래스의”인스턴스”라고합니다. 이것들은 대부분.show을 재정의하는 익명 클래스이며,그것들을 정의하는 데 관련된 상용구가 있기 때문에(사전 람다 자바 프로그래밍에서’함수 객체’를 기억하십니까?),우리는make라는 도우미를 정의 할 것입니다. 사용자 이름 유형에 대한 구현부터 시작하겠습니다:

object Showable {
def make(showFn: A => String): Showable = new Showable {
override def show(a: A): String = showFn(a)
}
}final case class UserName(first: String, last: String)implicit val defaultUserNameShowable = Showable.make(userName => s"${userName.first} ${userName.last}")val janeName = UserName("Jane", "Doe")

그래서 우리는 이것을 어떻게 호출 할 것인가? 다음과 같이 표시 가능한 인스턴스에서 메서드를 직접 호출 할 수 있습니다:

defaultUserNameShowable.show(janeName) // "Jane Doe"

향상된 구문

UserNameShowable구현을 호출 할 수 있었지만 그다지 좋지 않았습니다. 우리는 타입 클래스 인스턴스(defaultUserNameShowable)에 대한 참조를 가지고 그 이름을 알아야했습니다. 이것은 우리가 멀리 추상화 원하는 무언가이다. 스토리가 여기서 끝났다면 스칼라의 타입 클래스는 사용하기가 그리 좋지 않을 것입니다. 다행히,우리는 크게 의미를 사용하여 인체 공학을 향상시킬 수 있습니다. 호출 도우미로Showable의 컴패니언 객체를 보강합니다:

implicit class ShowSyntax(a: A) extends AnyVal {
def show(implicit showable: Showable): String = showable.show(a)
}
...
janeName.show

이”확장 메서드”를 범위 어딘가에 배치하면 모든 객체에.show메서드가 있는 것처럼 가장할 수 있습니다.

이것이

를 달성 한 이유는 무엇입니까? 음,UserName은 우리가 의존하는 도서관에 살고 있으며,우리가 그것을 소비 할 때까지는 이미 컴파일되어 있다고 가정 해 봅시다. 그것은final클래스이기 때문에 우리는 서브 클래스 수 없습니다. 그러나 유형 클래스를 사용하면 본질적으로 새로운 동작을 임시 방식으로”첨부”할 수 있습니다. 이것은 위의 정의의”임시”조각입니다: 이 다형성을 유형 자체가 정의 된 장소와 별도로 추가 할 수 있습니다.

또한 상속과 달리 스칼라에서 유형 클래스를 정의 할 때’일관된’것이 필요하지 않으므로Showable에 대해 여러 구현을 정의하고 그 중에서 선택할 수 있습니다. 일부 유형 클래스의 경우 이것은 좋은 일이며 다른 클래스의 경우 그렇게 많지 않습니다.

하스켈에서 유형 클래스는 일관성이 있습니다. 스칼라 3 에서 마틴 오 데르 스키에 따르면,우리는 각 유형 클래스에 대해 일관성이 있어야하는지 여부를 선택할 수있는 능력을 갖게 될 것입니다.

하지만 지금은show의 대체 의미를 제공하겠습니다.UserName:

implicit val secretUserNameShowable = Showable.make(_ => "<secret person>")

우리가 지금 해야 할 일은Showable인스턴스 중 하나만 한 번에 범위에 있는지 확인하는 것입니다. 그리고 그렇지 않다면,그것은 큰 문제가 아닙니다,우리는 컴파일 타임’모호한 암시 적 값’오류를 얻을 것입니다:

janeName.show
> ambiguous implicit values:
both value secretUserNameShowable in object typeclasses of type => Showable
and value defaultUserNameShowable in object typeclasses of type => Showable
match expected type Showable

한 가지 중요한 점은UserNameShowable을 결합하지 않았다는 것입니다. 우리가 한 모든 것은UserName에 대한Showable의 의미를 정의하면서 서로 완전히 독립적으로 유지하는 것입니다. 이 디커플링은 유지 보수성에 큰 영향을 미치며 코드 전체에 얽힌 종속성 관계를 방지하는 데 도움이됩니다.

컴파일 타임 안전

show의 의미를 제공하지 않은 객체에서.show을 호출하려고 하면 어떻게 됩니까? 즉,우리의 목표 범위에Showable의 인스턴스가 없을 때? 우리는 단순히 컴파일러 오류가 발생합니다:

java.util.UUID.randomUUID().show
> could not find implicit value for parameter showable: Showable

우리는 우리의 유형 클래스를 정의 할 때 우리는 실제로이 오류 메시지를 사용자 정의 할 수 있습니다:

@scala.annotation.implicitNotFound("No Showable in scope for A = ${A}. Try importing or defining one.")
trait Showable {
...
}
...
janeName.show
> No Showable in scope for A = java.util.UUID. Try importing or defining one.

주요 테이크 아웃

  • 유형 클래스를 사용하면 이러한 동작을 구현할 객체 및 유형과 완전히 별도로 일련의 동작을 정의 할 수 있습니다.
  • 형식 클래스는 형식 매개 변수를 사용하는 특성과 구문을 깨끗하게 만드는 암시가있는 순수한 스칼라로 표현됩니다.
  • 유형 클래스를 사용하면 소스를 수정할 수없는 유형 및 개체에 대한 동작을 확장하거나 구현할 수 있습니다. (표현 문제를 해결하는 데 필요한 임시 다형성을 제공합니다).

다음에 오는 것

부여,Showable은 사용이없는 것은 아니지만 사소한 유형 클래스입니다. 그러나 우리는 훨씬 더 유용한 것들을 쉽게 상상할 수 있습니다:

trait SafeSerializable {
def serialize(a: A): Array
def deserialize(bytes: Array): A
}
trait Convertible {
def to(a: A): B
def from(b: B): A
}
trait Functor] {
def map(a: F, f: A => B): F
}

SafeSerializable 우리는 직렬화가 우리의 유형에 등록되어 있는지 여부를 확인하기 위해 런타임 때까지 대기 아카의 직렬화 같은 것에 대한 훌륭한 대안이 될 수 있습니다. 컴파일 타임에 직렬화 구현을 암시 적으로 전달하는 것이 훨씬 안전합니다!

Convertible는 여러 유형의 데이터를 변환하는 훌륭한 재사용 가능한 방법이 될 수 있습니다. 트위터의 도서관이 있는데 같은 목표를 염두에두고 있습니다.

Functor은’범주 형 프로그래밍’이라는 함수형 프로그래밍 영역에서 매우 강력한 추상화를위한 빌딩 블록입니다. 스칼라 즈 및 고양이와 같은 라이브러리는Functor과 같은 유형 클래스를 기반으로합니다. 이번에 논의하지 않은 한 가지 중요한 개념은Functor과 같은 간단한 빌딩 블록에서 점점 더 복잡한 동작을 구축하기 위해 유형 클래스를 쉽게 구성 할 수 있다는 것입니다.

또한 스칼라 생태계의 많은 필수 라이브러리(. (2)타입 클래스를 사용하고,많은 경우에 타입 클래스의 주요 드라이버로 노출시킨다.2018 년 11 월 1 일 타입 클래스를 직접 이해하는 것은 스칼라 에코시스템에 있는 것을 활용하는 데 더 효과적으로 활용할 수 있는 좋은 방법입니다.

앞으로SafeSerializable와 같은 즉시 실용적인 유형 클래스를 살펴볼 것입니다. Functor]Monad]과 같은 보다 추상적 인 범주 형 클래스에 대한 소개는cats라이브러리 문서를 참조하십시오. 형식 클래스를 사용하여 스칼라 프로그래밍에 대한보다 심층적 인 가이드를 보려면 underscore.io 이 주제에 대한 환상적인 책이 있습니다: 고양이가있는 스칼라

파트 2:스칼라의 유형 안전 직렬화

요점 코드

답글 남기기

이메일 주소는 공개되지 않습니다.