[language-agnostic] 실존 유형은 무엇입니까?

Wikipedia 기사 Existential types를 읽었습니다 . 나는 실재 연산자 (∃) 때문에 실재 유형이라고 불렀습니다. 그래도 그 요점이 무엇인지 잘 모르겠습니다. 차이점은 무엇입니까

T = ∃X { X a; int f(X); }

T = ∀x { X a; int f(X); }

?



답변

누군가가 범용 유형 ∀X을 정의하면 다음과 같이 말합니다. 원하는 유형을 연결할 수 있습니다X . 작업을 수행하기 위해 유형에 대해 아무것도 알 필요가 없으며 불투명하게 만 참조합니다 .

누군가가 존재 유형 ∃X을 정의하면 다음과 같이X 말하고 있습니다. 여기에서 원하는 유형을 사용합니다. 유형에 대해서는 아무것도 모르므로으로 만 불투명하게 참조 할 수 있습니다 .

범용 유형을 사용하면 다음과 같은 내용을 작성할 수 있습니다.

void copy<T>(List<T> source, List<T> dest) {
   ...
}

copy함수는 T실제로 무엇이 될지 모르지만 꼭 그럴 필요는 없습니다.

기존 유형을 사용하면 다음과 같은 내용을 작성할 수 있습니다.

interface VirtualMachine<B> {
   B compile(String source);
   void run(B bytecode);
}

// Now, if you had a list of VMs you wanted to run on the same input:
void runAllCompilers(List<∃B:VirtualMachine<B>> vms, String source) {
   for (∃B:VirtualMachine<B> vm : vms) {
      B bytecode = vm.compile(source);
      vm.run(bytecode);
   }
}

목록의 각 가상 머신 구현은 다른 바이트 코드 유형을 가질 수 있습니다. 이 runAllCompilers함수는 바이트 코드 유형이 무엇인지 모르지만 반드시 그럴 필요는 없습니다. 바이트 코드를에서 VirtualMachine.compile로 릴레이하기 만하면 됩니다 VirtualMachine.run.

Java 유형 와일드 카드 (예 🙂 List<?>는 매우 제한된 형태의 실존 유형입니다.

업데이트 : 범용 유형으로 실존 유형을 시뮬레이션 할 수 있다는 것을 언급하지 않았습니다. 먼저 유니버설 유형을 래핑하여 유형 매개 변수를 숨 깁니다. 둘째, 인버트 제어 (이것은 위의 정의에서 “당신”과 “I”부분을 효과적으로 교환합니다. 이는 실존과 유니버설의 주요 차이점입니다).

// A wrapper that hides the type parameter 'B'
interface VMWrapper {
   void unwrap(VMHandler handler);
}

// A callback (control inversion)
interface VMHandler {
   <B> void handle(VirtualMachine<B> vm);
}

이제 우리는 보편적으로 유형이 지정된 기능 을 가진 VMWrapper우리 자신 의 전화 를 가질 수 있습니다 . 순 효과는 동일합니다. 코드는 불투명 으로 처리 해야합니다.VMHandlerhandleB

void runWithAll(List<VMWrapper> vms, final String input)
{
   for (VMWrapper vm : vms) {
      vm.unwrap(new VMHandler() {
         public <B> void handle(VirtualMachine<B> vm) {
            B bytecode = vm.compile(input);
            vm.run(bytecode);
         }
      });
   }
}

VM 구현 예 :

class MyVM implements VirtualMachine<byte[]>, VMWrapper {
   public byte[] compile(String input) {
      return null; // TODO: somehow compile the input
   }
   public void run(byte[] bytecode) {
      // TODO: Somehow evaluate 'bytecode'
   }
   public void unwrap(VMHandler handler) {
      handler.handle(this);
   }
}


답변

실존 형 등이 ∃x. F(x) 한 쌍의 일부 함유 유형 x 유형을 F(x). 같은 다형성 형태의 값 반면 ∀x. F(x)A는 함수 몇몇 형식을 사용 x하고 생산 타입의 값 F(x). 두 경우 모두 형식은 일부 형식 생성자 위로 닫힙니다 F.

이보기는 유형과 값을 혼합합니다. 실존 증명은 하나의 유형과 하나의 값입니다. 범용 증명은 유형별로 인덱싱 된 전체 값 계열 (또는 유형에서 값으로의 매핑)입니다.

따라서 지정한 두 유형의 차이점은 다음과 같습니다.

T = ∃X { X a; int f(X); }

의미 : type의 값은 , value 및 function T이라는 유형을 포함 합니다 . 유형의 값의 프로듀서 선택하게 어떤 을 위해 유형을 소비자가에 대해 아무것도 알 수 없다하고 . 한 가지 예가 있고이 값을 에 제공 하여 값으로 바꿀 수 있다는 점을 제외하고 . 다시 말해, type 값은 어떻게 든 생산 방법을 알고 있습니다. 중간 유형을 제거하고 다음 과 같이 말할 수 있습니다.Xa:Xf:X->intTXXaintfTintX

T = int

보편적으로 정량화 된 것은 약간 다릅니다.

T = ∀X { X a; int f(X); }

이 수단 : 유형의 값은 T모든 유형을 할 수는 없으며 X, 그것은 값을 생성합니다 a:X, 그리고 함수 f:X->int 에는 어떤 문제 X입니다 . 즉, type 값을 가진 소비자는에 T대한 모든 유형을 선택할 수 있습니다 X. 그리고 유형의 값을 생산하는 사람 T은 전혀 알 수 없지만 X을 (를) a선택 하여 값을 생성 X할 수 있어야하며 이러한 값을으로 바꿀 수 있어야합니다 int.

상상할 수있는 모든 유형의 값을 생성 할 수있는 프로그램이 없으므로이 유형을 구현하는 것은 불가능합니다. 부조리와 같은 부조리를 허용하지 않는 한 null.

실재는 쌍이기 때문에 실재 인수는 currying을 통해 범용 인수로 변환 될 수 있습니다 .

(∃b. F(b)) -> Int

와 같다:

∀b. (F(b) -> Int)

전자는 2 계급 실재입니다. 이것은 다음과 같은 유용한 속성으로 이어집니다.

실재적으로 정량화 된 모든 유형의 순위 n+1는 보편적으로 정량화 된 유형의 순위 n입니다.

실존을 Skolemization 이라고하는 범용으로 바꾸는 표준 알고리즘이 있습니다 .


답변

두 개념이 상호 보완 적이므로 하나는 다른 유형의 “반대”이기 때문에 실재 유형과 보편적 유형을 함께 설명하는 것이 합리적이라고 생각합니다.

필자는 그 지식이 충분하지 않기 때문에 실재적 유형 (정확한 정의 제공, 가능한 모든 용도 나열, 추상 데이터 유형과의 관계 등)에 대한 모든 세부 사항에 대답 할 수는 없습니다. 이 HaskellWiki 기사 가 실존 유형의 주된 효과로 기술 한 내용 만 (Java를 사용하여) 설명하겠습니다 .

기존 유형은 여러 가지 다른 목적으로 사용될 수 있습니다 . 그러나 그들이하는 일은 오른쪽에 유형 변수를 ‘숨기는 것’입니다. 일반적으로 오른쪽에 나타나는 모든 유형 변수는 왼쪽에도 나타납니다. […]

설정 예 :

다음 의사 코드는 Java를 수정하기가 쉽지만 유효한 Java는 아닙니다. 사실, 이것이 바로이 답변에서 할 것입니다!

class Tree<α>
{
    α       value;
    Tree<α> left;
    Tree<α> right;
}

int height(Tree<α> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

간단히 설명해 드리겠습니다. 우리는 정의하고 있습니다…

  • Tree<α>이진 트리의 노드를 나타내는 재귀 유형 . 각 노드는 value일부 유형의 α를 저장 하고 동일한 유형의 선택적 leftright하위 트리에 대한 참조를 갖습니다 .

  • height리프 노드에서 루트 노드까지의 가장 먼 거리를 반환 하는 함수 입니다 t.

이제 위의 의사 코드를 height적절한 Java 구문으로 바꾸자! (객체 지향 및 접근성 수정 자와 같은 간결성을 위해 약간의 상용구를 생략 할 것입니다.) 두 가지 가능한 해결책을 보여 드리겠습니다.

1. 유니버셜 타입 솔루션 :

가장 확실한 수정은 height형식 매개 변수 α 를 서명 에 도입하여 간단히 일반화하는 것입니다 .

<α> int height(Tree<α> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

이를 통해 원하는 경우 변수를 선언하고 해당 함수 내에 α 유형의 표현식을 작성할 수 있습니다 . 그러나…

2. 기존 유형 솔루션 :

우리의 분석법 본문을 보면, 우리가 실제로 α 유형의 어떤 것에 접근하거나 협력하고 있지 않음을 알 수 있습니다 ! 해당 유형의 표현식이나 해당 유형으로 선언 된 변수가 없습니다. 왜 height일반화해야합니까? 왜 우리는 단순히 α 를 잊을 수 없습니까? 결과적으로 다음과 같이 할 수 있습니다.

int height(Tree<?> t)
{
    return (t != null)  ?  1 + max( height(t.left), height(t.right) )
                        :  0;
}

이 답변의 맨 처음에 썼 듯이 실존적이고 보편적 인 유형은 본질적으로 보완 적 / 이중적입니다. 따라서, 범용 타입 솔루션이 height 좀 더 포괄적 인 것이된다면, 존재하는 타입이 반대 효과를 가질 것으로 예상해야합니다. 즉, 타입 매개 변수 α를 숨기거나 제거하여 일반적인 것으로 만듭니다 .

결과적으로 더 이상 t.value이 메소드에서 유형을 참조하거나 해당 유형의 표현식을 조작 할 수 없습니다. 식별자가 바인드되어 있지 않기 때문입니다. ( ?와일드 카드 는 유형을 “캡처”하는 식별자가 아닌 특수 토큰입니다.) t.value사실상 불투명하게되었습니다. 아마도 당신이 여전히 할 수있는 유일한 일은 유형 캐스팅 Object입니다.

요약:

===========================================================
                     |    universally       existentially
                     |  quantified type    quantified type
---------------------+-------------------------------------
 calling method      |
 needs to know       |        yes                no
 the type argument   |
---------------------+-------------------------------------
 called method       |
 can use / refer to  |        yes                no
 the type argument   |
=====================+=====================================


답변

이것들은 모두 좋은 예이지만, 조금 다르게 대답하기로 선택합니다. 수학에서 ∀x를 상기하십시오. P (x)는 “모든 x에 대해 P (x)를 증명할 수 있습니다”를 의미합니다. 다시 말해, 그것은 일종의 함수입니다. 당신은 나에게 x를 주었고 나는 당신을 위해 그것을 증명할 방법이 있습니다.

유형 이론에서 우리는 증거에 대한 것이 아니라 유형에 대해 이야기하고 있습니다. 그래서이 공간에서 우리는 “당신이 나에게 준 X 타입에 대해, 나는 당신에게 특정 타입 P를 줄 것입니다”를 의미합니다. 이제 X는 X라는 유형 외에 X에 대한 많은 정보를 제공하지 않기 때문에 P는 많은 것을 할 수 없지만 몇 가지 예가 있습니다. P는 “동일한 유형의 모든 쌍”유형을 작성할 수 있습니다 P<X> = Pair<X, X> = (X, X). 또는 옵션 유형을 만들 수 있습니다. P<X> = Option<X> = X | Nil여기서 Nil은 null 포인터의 유형입니다. 목록을 만들 수 있습니다 : List<X> = (X, List<X>) | Nil. 마지막 요소는 재귀 적이며, 값은 List<X>첫 번째 요소가 X이고 두 번째 요소가 a 인 경우 쌍입니다. List<X>그렇지 않으면 널 포인터입니다.

이제 수학 ∃x에서. P (x)는 “P (x)가 참인 특정 x가 있음을 증명할 수 있습니다”를 의미합니다. 많은 x가있을 수 있지만 그것을 증명하기에는 충분합니다. 그것을 생각하는 또 다른 방법은 비어 있지 않은 증거와 증거 쌍 {(x, P (x))}이 있어야한다는 것입니다.

유형 이론으로 번역 : 패밀리 ∃X.P<X>의 유형은 X 유형과 해당 유형 P<X>입니다. 우리가 X에게 P를주기 전에 (X를 제외하고는 X에 관한 모든 것을 거의 알지 못했음) 지금은 그 반대가 사실입니다. P<X>X에 대한 정보를 제공하겠다고 약속하지는 않습니다. X가 있고 실제로 유형이라는 것입니다.

이것이 어떻게 유용합니까? P는 내부 유형 X를 노출시키는 방법이 될 수 있습니다. 예를 들어 상태 X의 내부 표현을 숨기는 객체가 있습니다. 직접 조작 할 방법은 없지만 그 효과를 관찰 할 수는 있습니다. 이 유형에는 여러 가지 구현이있을 수 있지만 어떤 유형을 선택했는지에 관계없이 이러한 유형을 모두 사용할 수 있습니다.


답변

질문에 직접 대답하려면 :

범용 유형의 T경우 type 매개 변수 를 사용해야합니다 X. 예를 들어 T<String>또는 T<Integer>. 존재 T하는 유형의 경우 알 수 없거나 관련이 없으므로 해당 유형 매개 변수를 포함하지 마십시오 T(또는 Java에서 T<?>).

추가 정보 :

범용 / 추상 유형과 실존 유형은 객체 / 함수의 소비자 / 클라이언트와 객체 / 함수의 생산자 / 구현 사이의 이중성의 관점입니다. 한쪽이 범용 유형을 볼 때 다른 쪽은 실존 유형을 볼 수 있습니다.

Java에서는 일반 클래스를 정의 할 수 있습니다.

public class MyClass<T> {
   // T is existential in here
   T whatever;
   public MyClass(T w) { this.whatever = w; }

   public static MyClass<?> secretMessage() { return new MyClass("bazzlebleeb"); }
}

// T is universal from out here
MyClass<String> mc1 = new MyClass("foo");
MyClass<Integer> mc2 = new MyClass(123);
MyClass<?> mc3 = MyClass.secretMessage();
  • a의 관점에서 고객MyClass, T당신이 모든 유형을 대체 할 수 있기 때문에 보편적 T해당 클래스를 사용할 때 당신은의 인스턴스를 사용할 때마다 당신은 T의 실제 유형을 알고 있어야합니다MyClass
  • 인스턴스 메소드 MyClass자체 의 관점 T에서 실제 유형을 알지 못하기 때문에 실존 적입니다.T
  • Java에서는 ?실존 유형을 나타냅니다. 따라서 클래스 내부에있을 때는 T기본적으로 ?입니다. 당신의 인스턴스를 처리하는 경우 MyClassT실존을, 당신은 선언 할 수 MyClass<?>같이 secretMessage()위의 예.

존재하는 유형은 때때로 다른 곳에서 논의 된 것처럼 무언가의 구현 세부 사항을 숨기는데 사용됩니다. 이것의 Java 버전은 다음과 같습니다.

public class ToDraw<T> {
    T obj;
    Function<Pair<T,Graphics>, Void> draw;
    ToDraw(T obj, Function<Pair<T,Graphics>, Void>
    static void draw(ToDraw<?> d, Graphics g) { d.draw.apply(new Pair(d.obj, g)); }
}

// Now you can put these in a list and draw them like so:
List<ToDraw<?>> drawList = ... ;
for(td in drawList) ToDraw.draw(td);

Java가 아닌 일종의 기능적 프로그래밍 언어 인 척하기 때문에 이것을 올바르게 캡처하는 것은 약간 까다 롭습니다. 그러나 여기서 요점은 어떤 종류의 상태와 그 상태에서 작동하는 함수 목록을 캡처하고 실제 상태 부분을 알지 못하지만 함수는 이미 해당 유형과 일치했기 때문에 함수입니다 .

이제 Java에서는 모든 비 최종 비 기본 유형이 부분적으로 존재합니다. 이상하게 들릴 수도 있지만, 대신 선언 된 변수 ObjectObject대신 서브 클래스 일 수 있으므로 “이 유형 또는 서브 클래스”만 특정 유형을 선언 할 수 없습니다. 따라서 객체는 비트 상태와 해당 상태에서 작동하는 함수 목록으로 표시됩니다. 호출 할 함수는 런타임에 조회에 의해 결정됩니다. 이것은 존재 상태 부분과 해당 상태에서 작동하는 함수가있는 위의 존재 유형을 사용하는 것과 매우 유사합니다.

서브 타이핑 및 캐스트가없는 정적으로 유형이 지정된 프로그래밍 언어에서 실존 유형을 사용하면 다른 유형의 객체 목록을 관리 할 수 ​​있습니다. 의 목록은 T<Int>을 포함 할 수 없습니다 T<Long>. 그러나의 목록 T<?>에는의 변형이 포함될 수 있으므로 T많은 다른 유형의 데이터를 목록에 넣고 필요에 따라 모두 int로 변환하거나 데이터 구조 내에서 제공되는 모든 작업을 수행 할 수 있습니다.

클로저를 사용하지 않고 항상 존재 유형이있는 레코드를 레코드로 변환 할 수 있습니다. 클로저는 또한 존재하는 변수로, 클로저가 닫힌 자유 변수는 호출자에게 숨겨져 있습니다. 따라서 클로저를 지원하지만 실존 유형은 지원하지 않는 언어를 사용하면 객체의 실존 부분에 넣은 것과 동일한 숨겨진 상태를 공유하는 클로저를 만들 수 있습니다.


답변

실재 유형은 불투명 유형입니다.

유닉스에서 파일 핸들을 생각하십시오. 당신은 그 타입이 int라는 것을 알고 있으므로 쉽게 위조 할 수 있습니다. 예를 들어, 핸들 43에서 읽으려고 시도 할 수 있습니다. 프로그램이이 특정 핸들로 열린 파일을 가지고있는 경우,이를 읽습니다. 코드는 악의적 일 필요가없고 조잡 할 필요가 없습니다 (예 : 핸들이 초기화되지 않은 변수 일 수 있음).

실재 유형은 프로그램에서 숨겨집니다. 경우 fopen실존 유형을 반환, 당신이 그것으로 할 수있는 모든이 존재 유형을 받아 일부 라이브러리 기능을 사용하는 것입니다. 예를 들어 다음 의사 코드는 컴파일됩니다.

let exfile = fopen("foo.txt"); // No type for exfile!
read(exfile, buf, size);

“읽기”인터페이스는 다음과 같이 선언됩니다.

다음과 같은 유형 T가 있습니다.

size_t read(T exfile, char* buf, size_t size);

변수 exfile은 int가 아니고 char*struct 파일 이 아닌 int가 아닙니다 . 타입 시스템에서 표현할 수있는 것은 없습니다. 유형을 알 수없는 변수를 선언 할 수 없으며 알 수없는 유형으로 포인터를 캐스트 할 수 없습니다. 언어는 당신을 허용하지 않습니다.


답변

내가 조금 늦게 온 것 같지만 어쨌든이 문서는 실존 유형이 무엇인지에 대한 또 다른 견해를 추가하지만, 언어에 구애받지 않지만 실존 유형을 이해하는 것이 훨씬 쉬워야 합니다. http : //www.cs.uu .nl / groups / ST / Projects / ehc / ehc-book.pdf (8 장)

보편적으로 정량화 된 유형과 실재적으로 정량화 된 유형의 차이점은 다음과 같은 관찰로 특징 지을 수 있습니다.

  • ∀ 정량화 된 유형의 값을 사용하면 정량화 된 유형 변수의 인스턴스화를 위해 선택할 유형이 결정됩니다. 예를 들어, ID 함수 “id :: ∀aa → a”의 호출자는이 특정 id의 응용에 대해 유형 변수 a에 대해 선택할 유형을 결정합니다. 함수 어플리케이션 “id 3″의 경우이 유형은 Int와 같습니다.

  • ∃ 정량화 된 유형으로 값을 생성하면 정량화 된 유형 변수의 유형이 결정되고 숨겨집니다. 예를 들어, “∃a. (a, a → Int)”의 작성자는 “(3, λx → x)”에서 해당 유형의 값을 구성했을 수 있습니다. 다른 제작자는“( ‘x’, λx → ord x)”와 동일한 유형의 값을 구성했습니다. 사용자 관점에서 두 값은 동일한 유형을 가지므로 상호 교환이 가능합니다. 값은 유형 변수 a에 대해 선택된 특정 유형을 가지고 있지만 어떤 유형을 알지 못하므로이 정보를 더 이상 이용할 수 없습니다. 이 값 특정 유형 정보는 ‘잊혀졌습니다’. 우리는 그것이 존재한다는 것을 알고 있습니다.