[functional-programming] 종속 입력이란 무엇입니까?

누군가 나에게 종속 타이핑을 설명 할 수 있습니까? Haskell, Cayenne, Epigram 또는 기타 기능적 언어에 대한 경험이 거의 없으므로 사용할 수있는 용어가 간단할수록 더 감사 할 것입니다!



답변

이것을 고려하십시오 : 모든 괜찮은 프로그래밍 언어에서 함수를 작성할 수 있습니다.

def f(arg) = result

여기에서 f값을 취하고 값 arg을 계산합니다 result. 값에서 값으로의 함수입니다.

이제 일부 언어에서는 다형성 (일명 일반) 값을 정의 할 수 있습니다.

def empty<T> = new List<T>()

여기에서 empty유형을 취하고 T값을 계산합니다. 유형에서 값으로의 함수입니다.

일반적으로 일반 유형 정의를 가질 수도 있습니다.

type Matrix<T> = List<List<T>>

이 정의는 유형을 취하고 유형을 리턴합니다. 유형에서 유형으로의 함수로 볼 수 있습니다.

평범한 언어가 제공하는 것이 너무 많습니다. 네 번째 가능성, 즉 값에서 유형으로 함수를 정의하는 경우 언어를 종속 유형이라고합니다. 즉, 값에 대한 유형 정의 매개 변수화 :

type BoundedInt(n) = {i:Int | i<=n}

일부 주류 언어에는 혼동해서는 안되는 가짜 형태가 있습니다. 예를 들어 C ++에서 템플릿은 값을 매개 변수로 사용할 수 있지만 적용 할 때 컴파일 타임 상수 여야합니다. 진정한 의존형 언어에서는 그렇지 않습니다. 예를 들어 위의 유형을 다음과 같이 사용할 수 있습니다.

def min(i : Int, j : Int) : BoundedInt(j) =
  if i < j then i else j

여기서 함수의 결과 유형 실제 인수 값 j, 즉 용어 에 따라 다릅니다 .


답변

종속 유형을 사용 하면 컴파일 시간에 더 많은 논리 오류 를 제거 할 수 있습니다 . 이를 설명하기 위해 기능에 대한 다음 사양을 고려하십시오 .f

함수 f짝수 정수만 입력 해야합니다 .

종속 유형이 없으면 다음과 같이 할 수 있습니다.

def f(n: Integer) := {
  if  n mod 2 != 0 then
    throw RuntimeException
  else
    // do something with n
}

여기서 컴파일러 n는 실제로 짝수 인지 감지 할 수 없습니다 . 즉, 컴파일러의 관점에서 다음 표현식이 괜찮은지 감지 할 수 없습니다 .

f(1)    // compiles OK despite being a logic error!

이 프로그램은 실행되고 런타임에 예외를 발생시킵니다. 즉, 프로그램에 논리 오류가 있습니다.

이제 종속 유형을 사용하면 훨씬 더 많은 표현을 사용할 수 있으며 다음과 같이 작성할 수 있습니다.

def f(n: {n: Integer | n mod 2 == 0}) := {
  // do something with n
}

다음 n은 종속 유형 {n: Integer | n mod 2 == 0}입니다. 이것을 크게 읽는 것이 도움이 될 수 있습니다.

n 각 정수를 2로 나눌 수있는 정수 세트의 구성원입니다.

이 경우 컴파일러는 컴파일 타임에 홀수를 전달한 논리 오류를 감지 f하여 프로그램이 처음에 실행되지 않도록합니다.

f(1)    // compiler error

다음은 이러한 요구 사항을 충족 하는 함수를 구현하는 방법에 대한 Scala 경로 종속 유형 을 사용한 예시입니다 f.

case class Integer(v: Int) {
  object IsEven { require(v % 2 == 0) }
  object IsOdd { require(v % 2 != 0) }
}

def f(n: Integer)(implicit proof: n.IsEven.type) =  {
  // do something with n safe in the knowledge it is even
}

val `42` = Integer(42)
implicit val proof42IsEven = `42`.IsEven

val `1` = Integer(1)
implicit val proof1IsOdd = `1`.IsOdd

f(`42`) // OK
f(`1`)  // compile-time error

핵심은 값 n이 다음과 같이 값 유형에 어떻게 나타나는지 확인 하는 proof것입니다 n.IsEven.type.

def f(n: Integer)(implicit proof: n.IsEven.type)
      ^                           ^
      |                           |
    value                       value

유형 n.IsEven.type 에 따라 다르 n므로 용어 종속 유형 이라고 말합니다 .


답변

C ++를 알게된다면 동기를 부여하는 예제를 쉽게 제공 할 수 있습니다.

컨테이너 유형과 인스턴스 두 개가 있다고 가정 해 보겠습니다.

typedef std::map<int,int> IIMap;
IIMap foo;
IIMap bar;

다음 코드 조각을 고려하십시오 (foo가 비어 있지 않다고 가정 할 수 있음).

IIMap::iterator i = foo.begin();
bar.erase(i);

이것은 명백한 쓰레기입니다 (그리고 아마도 데이터 구조를 손상시킬 것입니다). 그러나 “iterator into foo”와 “iterator into bar”는 IIMap::iterator의미 상 완전히 호환되지 않더라도 동일한 유형이기 때문에 유형 검사는 괜찮습니다 .

문제는 반복기 유형이 컨테이너 유형 에만 의존하는 것이 아니라 실제로 컨테이너 객체 에 의존해야 한다는 것입니다. 즉, “비 정적 멤버 유형”이어야합니다.

foo.iterator i = foo.begin();
bar.erase(i);  // ERROR: bar.iterator argument expected

이러한 기능, 즉 용어 (foo)에 의존하는 유형 (foo.iterator)을 표현하는 능력은 정확히 종속 입력이 의미하는 바입니다.

이 기능을 자주 보지 못하는 이유는 많은 수의 웜이 열리기 때문입니다. 컴파일 타임에 두 가지 유형이 동일한 지 확인하기 위해 갑자기 두 가지 표현을 증명해야하는 상황에 처하게됩니다. 동일합니다 (런타임에 항상 동일한 값을 생성 함). 당신은 위키 피 디아의 비교한다면 결과, 의존적으로 입력 된 언어의 목록을 그와 함께 정리 피 인증 목록 당신은 의심스러운 유사성을 알 수 있습니다. 😉


답변

책 유형 및 프로그래밍 언어 (30.5) 인용 :

이 책의 대부분은 다양한 종류의 추상화 메커니즘을 공식화하는 것과 관련이 있습니다. 간단하게 입력 된 람다-미적분에서 우리는 용어를 취하고 하위 용어를 추상화하는 작업을 공식화하여 나중에 다른 용어에 적용하여 인스턴스화 할 수있는 함수를 생성했습니다. System에서는 F용어를 취하고 형식을 추상화하여 다양한 형식에 적용하여 인스턴스화 할 수있는 용어를 생성하는 작업을 고려했습니다. 에λω, 우리는 단순히 형식화 된 람다-미적분 “한 단계 위로”의 메커니즘을 요약하여 형식을 취하고 하위 식을 추상화하여 나중에 다른 형식에 적용하여 인스턴스화 할 수있는 형식 연산자를 얻습니다. 이러한 모든 형태의 추상화를 편리하게 생각할 수있는 방법은 다른 표현에 의해 색인 된 표현 군에 관한 것입니다. 일반적인 람다 추상화 λx:T1.t2는 용어로 [x -> s]t1인덱싱 된 용어 군 s입니다. 마찬가지로 유형 추상화 λX::K1.t2는 유형별로
인덱싱 된 용어 모음이고 유형 연산자는 유형별로 인덱싱 된 유형 모음입니다.

  • λx:T1.t2 용어로 색인 된 용어 군

  • λX::K1.t2 유형별로 색인 된 용어 군

  • λX::K1.T2 유형별로 인덱싱 된 유형 계열

이 목록을 살펴보면 아직 고려하지 않은 한 가지 가능성이 있음이 분명합니다. 용어로 색인 된 유형의 패밀리입니다. 이러한 형태의 추상화는 종속 유형의 루 브릭에 따라 광범위하게 연구되었습니다.


답변