스칼라 유형 프로그래밍 리소스


102

이 질문 에 따르면 Scala의 유형 시스템은 Turing complete 입니다. 초보자가 유형 수준 프로그래밍의 장점을 활용할 수 있도록 어떤 리소스를 사용할 수 있습니까?

지금까지 찾은 리소스는 다음과 같습니다.

이러한 리소스는 훌륭하지만 기본이 누락 된 것 같아서 구축 할 탄탄한 기반이 없습니다. 예를 들어, 유형 정의에 대한 소개는 어디에 있습니까? 유형에 대해 어떤 작업을 수행 할 수 있습니까?

좋은 입문 자료가 있습니까?


개인적으로 저는 Scala에서 타입 레벨 프로그래밍을하고 싶은 사람이 이미 Scala에서 프로그래밍을하는 방법을 알고 있다는 가정을 꽤 합리적이라고 생각합니다. 링크 한 기사의 단어를 이해하지 못하더라도 :-)
Jörg W Mittag

답변:


140

개요

유형 수준 프로그래밍은 기존의 가치 수준 프로그래밍과 많은 유사점이 있습니다. 그러나 런타임에 계산이 발생하는 값 수준 프로그래밍과 달리 유형 수준 프로그래밍에서는 컴파일 타임에 계산이 수행됩니다. 나는 가치 수준에서의 프로그래밍과 유형 수준에서의 프로그래밍 사이의 유사점을 그리려고 노력할 것이다.

패러다임

유형 수준 프로그래밍에는 "객체 지향"과 "기능적"의 두 가지 주요 패러다임이 있습니다. 여기에서 링크 된 대부분의 예제는 객체 지향 패러다임을 따릅니다.

객체 지향 패러다임에서 유형 수준 프로그래밍의 훌륭하고 매우 간단한 예는 apocalisp의 lambda calculus 구현 에서 찾을 수 있습니다 .

// Abstract trait
trait Lambda {
  type subst[U <: Lambda] <: Lambda
  type apply[U <: Lambda] <: Lambda
  type eval <: Lambda
}

// Implementations
trait App[S <: Lambda, T <: Lambda] extends Lambda {
  type subst[U <: Lambda] = App[S#subst[U], T#subst[U]]
  type apply[U] = Nothing
  type eval = S#eval#apply[T]
}

trait Lam[T <: Lambda] extends Lambda {
  type subst[U <: Lambda] = Lam[T]
  type apply[U <: Lambda] = T#subst[U]#eval
  type eval = Lam[T]
}

trait X extends Lambda {
  type subst[U <: Lambda] = U
  type apply[U] = Lambda
  type eval = X
}

예제에서 볼 수 있듯이 유형 수준 프로그래밍을위한 객체 지향 패러다임은 다음과 같이 진행됩니다.

  • 첫째 : 다양한 추상 유형 필드를 사용하여 추상 특성을 정의합니다 (추상 필드가 무엇인지 아래 참조). 이는 구현을 강요하지 않고 모든 구현에 특정 유형 필드가 존재하도록 보장하기위한 템플릿입니다. 람다 미적분 예제에서 이는 trait Lambda다음 유형이 존재 함을 보장 하는 것에 해당합니다. subst,apply ,와 eval.
  • 다음 : 추상 특성을 확장하고 다양한 추상 유형 필드를 구현하는 하위 특성 정의
    • 종종 이러한 하위 특성은 인수로 매개 변수화됩니다. 람다 미적분 예제에서 하위 유형은 trait App extends Lambda두 가지 유형 ( ST, 둘 다의 하위 유형이어야 함 Lambda)으로 trait Lam extends Lambda매개 변수화되고 하나의 유형 (T ) 및 trait X extends Lambda(매개 변수화되지 않음 .
    • 유형 필드는 종종 subtrait의 유형 매개 변수를 참조하고 때로는 해시 연산자를 통해 유형 필드를 참조하여 구현됩니다 #(점 연산자 : .값 과 매우 유사 함 ). App람다 미적분 예제의 특성 에서 유형 eval은 다음과 같이 구현됩니다.type eval = S#eval#apply[T] .. 이것은 본질적으로 eval특성의 매개 변수 유형을 S호출 하고 결과에 apply매개 변수 T를 사용하여 호출 합니다. 참고, S이 보장되는 eval매개 변수의 하위로를 지정하기 때문에 유형을 Lambda. 마찬가지로, 결과 eval필수는 가지고 apply가의 하위 유형으로 지정되어 있기 때문에, 유형을 Lambda추상적 특성에 지정된대로 Lambda.

기능적 패러다임은 특성으로 함께 그룹화되지 않은 매개 변수화 된 유형 생성자를 정의하는 것으로 구성됩니다.

가치 수준 프로그래밍과 유형 수준 프로그래밍의 비교

  • 추상 클래스
    • 가치 수준 : abstract class C { val x }
    • 유형 수준 : trait C { type X }
  • 경로 종속 유형
    • C.x (객체 C의 필드 값 / 함수 x 참조)
    • C#x (특성 C에서 필드 유형 x 참조)
  • 함수 서명 (구현 없음)
    • 가치 수준 : def f(x:X) : Y
    • 유형 수준 : type f[x <: X] <: Y ( "유형 생성자"라고하며 일반적으로 추상 특성에서 발생)
  • 기능 구현
    • 가치 수준 : def f(x:X) : Y = x
    • 유형 수준 : type f[x <: X] = x
  • 조건문
  • 평등 확인
    • 가치 수준 : a:A == b:B
    • 유형 수준 : implicitly[A =:= B]
    • 값 수준 : 런타임시 단위 테스트를 통해 JVM에서 발생합니다 (즉, 런타임 오류 없음).
      • 본질적으로 주장은 다음과 같습니다. assert(a == b)
    • 유형 수준 : 유형 검사를 통해 컴파일러에서 발생합니다 (즉, 컴파일러 오류 없음).
      • 본질적으로 유형 비교입니다. 예 : implicitly[A =:= B]
      • A <:< B, A이 하위 유형 인 경우에만 컴파일됩니다.B
      • A =:= B, 경우에만 컴파일 A의 하위 유형 BB의 하위 유형입니다A
      • A <%< B, ( "viewable as") A는 다음과 같이 볼 수있는 경우에만 컴파일됩니다 B(예 : A에서 하위 유형으로의 암시 적 변환 이 있음 B).
      • 더 많은 비교 연산자

유형과 값 간 변환

  • 많은 예제에서 트레이 트를 통해 정의 된 유형은 추상적이고 봉인 된 경우가 많으므로 직접 인스턴스화하거나 익명 하위 클래스를 통해 인스턴스화 할 수 없습니다. 따라서 null특정 유형의 관심을 사용하여 값 수준 계산을 수행 할 때 자리 표시 자 값 으로 사용하는 것이 일반적 입니다.

    • 예 : 관심있는 유형은 val x:A = null어디에 있습니까?A
  • 유형 삭제로 인해 매개 변수화 된 유형은 모두 동일하게 보입니다. 또한 (위에서 언급했듯이) 작업하는 값은 모두 인 경향이 null있으므로 개체 유형에 대한 조건 지정 (예 : match 문을 통해)은 효과가 없습니다.

비결은 암시 적 함수와 값을 사용하는 것입니다. 기본 사례는 일반적으로 암시 적 값이고 재귀 사례는 일반적으로 암시 적 함수입니다. 실제로 유형 수준 프로그래밍은 암시 적 요소를 많이 사용합니다.

다음 예제를 고려하십시오 ( metascalaapocalisp에서 가져옴 ).

sealed trait Nat
sealed trait _0 extends Nat
sealed trait Succ[N <: Nat] extends Nat

여기에는 자연수의 피노 인코딩이 있습니다. 즉, 음수가 아닌 각 정수에 대한 유형이 있습니다. 0에 대한 특수 유형, 즉 _0; 및 각각 0보다 큰 정수의 형태의 유형 가지고 Succ[A], A작은 정수를 나타내는 형식이다. 예를 들어 2를 나타내는 유형은 다음과 같습니다 Succ[Succ[_0]](0을 나타내는 유형에 두 번 적용).

보다 편리한 참조를 위해 다양한 자연수에 별칭을 지정할 수 있습니다. 예:

type _3 = Succ[Succ[Succ[_0]]]

(이것은 val함수의 결과로 a를 정의하는 것과 매우 유사 합니다.)

이제 우리가 의 타입에 인코딩 된 자연수를 나타내는 정수 를 따르고 반환하는 def toInt[T <: Nat](v : T)인수 값을받는 값 수준 함수를 정의한다고 가정 합니다. 우리는 값이 예를 들어, ( 유형의 ), 우리가 원하는 것입니다 반환vNatvval x:_3 = nullnullSucc[Succ[Succ[_0]]]toInt(x)3 .

를 구현 toInt하기 위해 다음 클래스를 사용합니다.

class TypeToValue[T, VT](value : VT) { def getValue() = value }

우리는 아래에서 볼 때,이 클래스로부터 구성된 오브젝트 것이다 TypeToValueNat_0(예를 들어)까지 _3, 각각 대응하는 형태 (즉, 가치 표현을 저장할 TypeToValue[_0, Int]값을 저장하는 것 0, TypeToValue[Succ[_0], Int]값을 저장하는 것 1등). 참고, TypeToValue두 가지 유형에 의해 매개 변수화된다 T하고 VT. T우리가 할당 값을하려는 유형 (예에서에 해당 Nat) 및 VT가치 우리가 (우리의 예에에 할당하고의 유형에 해당 Int).

이제 다음 두 가지 암시 적 정의를 만듭니다.

implicit val _0ToInt = new TypeToValue[_0, Int](0)
implicit def succToInt[P <: Nat](implicit v : TypeToValue[P, Int]) = 
     new TypeToValue[Succ[P], Int](1 + v.getValue())

그리고 toInt다음과 같이 구현 합니다.

def toInt[T <: Nat](v : T)(implicit ttv : TypeToValue[T, Int]) : Int = ttv.getValue()

toInt작동 방식 을 이해하기 위해 몇 가지 입력에서 수행되는 작업을 고려해 보겠습니다.

val z:_0 = null
val y:Succ[_0] = null

우리가 호출 할 때 toInt(z), 암시 적 인수에 대한 컴파일러 보이는 ttv유형의은 TypeToValue[_0, Int](이후 z유형이다 _0). 객체를 찾고, 이 객체 _0ToIntgetValue메소드를 호출하고 돌아옵니다.0 . 주목해야 할 중요한 점은 사용할 객체를 프로그램에 지정하지 않았으며 컴파일러가 암시 적으로 발견했다는 것입니다.

이제 고려해 봅시다 toInt(y). 이번에는, 컴파일러는 암시 적 인수에 보이는 ttv유형의 TypeToValue[Succ[_0], Int](이후 y유형이다 Succ[_0]). succToInt적절한 유형 ( TypeToValue[Succ[_0], Int]) 의 객체를 반환 할 수 있는 함수를 찾아 평가합니다. 이 함수 자체는 v유형 의 암시 적 인수 ( )를 사용합니다 TypeToValue[_0, Int](즉 TypeToValue, 첫 번째 유형 매개 변수가 하나 더 적은 경우 Succ[_]). 컴파일러는 _0ToInt( toInt(z)위 의 평가에서 수행 된 것처럼) 공급 하고 value를 사용 succToInt하여 새 TypeToValue객체를 생성합니다 1. 다시 말하지만, 컴파일러는 이러한 모든 값을 명시 적으로 액세스 할 수 없기 때문에 암시 적으로 모든 값을 제공한다는 점에 유의해야합니다.

작업 확인

유형 수준 계산이 예상 한대로 작동하는지 확인하는 방법에는 여러 가지가 있습니다. 다음은 몇 가지 접근 방식입니다. 확인 하려는 두 가지 유형 AB을 확인하십시오. 그런 다음 다음 컴파일을 확인하십시오.

또는 유형을 값으로 변환하고 (위에 표시된대로) 값의 런타임 검사를 수행 할 수 있습니다. 예 : assert(toInt(a) == toInt(b))where ais of type Abis of type B.

추가 자료

사용 가능한 구성의 전체 세트 는 scala 참조 매뉴얼 (pdf) 의 유형 섹션에서 찾을 수 있습니다 .

Adriaan Moors 는 스칼라의 예제와 함께 유형 생성자 및 관련 주제에 대한 여러 학술 논문을 보유하고 있습니다.

Apocalisp 는 스칼라의 유형 수준 프로그래밍에 대한 많은 예제가있는 블로그입니다.

  • Scala 의 유형 수준 프로그래밍은 부울, 자연수 (위와 같음), 이진 숫자, 이기종 목록 등을 포함하는 일부 유형 수준 프로그래밍에 대한 환상적인 가이드 투어입니다.
  • More Scala Typehackery 는 위의 람다 미적분 구현입니다.

ScalaZ 는 다양한 유형 수준 프로그래밍 기능을 사용하여 Scala API를 확장하는 기능을 제공하는 매우 활동적인 프로젝트입니다. 팔로어가 많은 매우 흥미로운 프로젝트입니다.

MetaScala 는 자연수, 부울, 단위, HList 등에 대한 메타 유형을 포함하는 Scala 용 유형 수준 라이브러리입니다. Jesper Nordenberg (그의 블로그) 입니다.

Michid (블로그) (다른 답변에서) 스칼라 입력 레벨의 프로그래밍의 굉장한 예제가 있습니다 :

Debasish Ghosh (블로그) 에는 관련 게시물도 있습니다.

(나는이 주제에 대해 조사를 해왔고 여기에 내가 배운 것이 있습니다. 나는 아직 그것에 익숙하지 않으므로이 답변에서 부정확 한 부분을 지적하십시오.)


12

흥미로운 블로그에 감사드립니다. 나는 한동안 그것을 따라 왔으며 특히 위에서 언급 한 마지막 게시물은 객체 지향 언어의 유형 시스템이 가져야하는 중요한 속성에 대한 이해를 높였습니다. 감사합니다!
Zach Snow



당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.