태그 부착 유니언

Tagged union

컴퓨터 과학에서, 변형, 변종 레코드, 선택 유형, 구별결합, 분리결합, 총합 유형 또는 공동 생산물로도 불리는 태그 부착 결합은 여러 가지 다른 고정 유형을 취할 수 있는 값을 보유하는 데 사용되는 데이터 구조입니다.한 번에 사용할 수 있는 타입은 1개뿐입니다.태그 필드는 어떤 타입이 사용되고 있는지를 명확하게 나타냅니다.여러 개의 "케이스"가 있는 유형으로 생각할 수 있으며, 각 케이스는 해당 유형을 조작할 때 올바르게 처리되어야 합니다.이는 값의 일부 구성요소가 값 자체와 동일한 유형을 가질 수 있는 재귀 데이터 유형을 정의하는 데 매우 중요합니다. 예를 들어, 트리 표현 유형을 정의하는 경우, 다중 노드 하위 트리와 탈퇴를 구분해야 합니다.일반 유니언과 마찬가지로 태그 부착 유니언은 한 번에 하나만 사용되므로 각 유형에 대해 저장 영역을 겹쳐서 저장 공간을 절약할 수 있습니다.

묘사

태그 부착 유니언은 ML 및 Haskell같은 기능적 언어에서 가장 중요합니다.여기서 데이터형(대수 데이터형 참조)이라고 불리며 컴파일러는 태그 부착 유니언의 모든 케이스가 항상 처리되는 것을 확인할 수 있어 많은 유형의 오류를 피할 수 있습니다.그러나 그것들은 거의 모든 언어로 구성될 수 있으며, 종종 단순히 노조라고 불리는 태그가 없는 조합보다 훨씬 안전하다. 이들은 유사하지만 어떤 조합원이 현재 사용되고 있는지 명시적으로 추적하지 않는다.

태그 부착 결합에는 종종 유형 생성자의 개념이 수반됩니다. 유형 생성자는 클래스 생성자와 유사하지만 동일하지는 않습니다.유형 생성자는 초기 태그 유형과 해당 유형을 지정하면 태그가 지정된 결합 유형을 생성합니다.

수학적으로 태그 부착 결합은 분리되거나 구별된 결합에 해당하며, 일반적으로 +를 사용하여 작성됩니다.분리된 결합 A + B의 원소가 주어졌을 때, 그것이 A에서 온 인지 B에서 온 것인지 판단할 수 있다.원소가 둘 다일 경우 A + B에 대한 효과적으로 구별되는 복사본이 두 개 있습니다. 하나는 A에서, 다른 하나는 B에서 생성됩니다.

유형 이론에서 태그가 달린 결합을 총합 유형이라고 합니다.Sum 타입은 제품 타입이중입니다.표기법은 다양하지만, 일반적으로 합형 A + B에는 A A + B2 B의 두 가지 도입 형태(표현) 1 있습니다.삭제 폼은 케이스 분석으로 ML 언어에서는 패턴 매칭이라고 불립니다.e타입 A + B, e12 타입각각x: A y: B인 경우, e e f x y { \ \}}\y\2}} 의 \tau 입니다.합 유형은 Curry-Howard 대응 관계에 있는 직관적인 논리 분리에 대응합니다.

열거형 유형은 단위 유형의 태그가 지정된 결합인 축퇴 대소문자로 볼 수 있습니다.이것은 null 생성자 집합에 대응하며 태그 값 외에 추가 데이터를 보유하지 않기 때문에 단순한 태그 변수로 구현될 수 있습니다.

로프, 느린 평가, 클래스 계층(아래 참조), 임의 정밀도 산술, CDR 부호화, 간접 비트 및 다른 종류의 태그 부착 포인터 등을 포함한 많은 프로그래밍 기술과 데이터 구조는 보통 일종의 태그 부착 결합을 사용하여 구현됩니다.

태그 부착 결합은 가장 간단한 종류의 자기 기술 데이터 형식으로 볼 수 있습니다.태그가 달린 유니언의 태그는 가장 단순한 종류의 메타데이터로 볼 수 있습니다.

장점과 단점

태그 없는 유니언에 비해 태그가 달린 유니언의 주된 장점은 모든 액세스가 안전하며 컴파일러는 모든 케이스가 처리되었는지 확인할 수 있다는 것입니다.태그 없는 유니언은 현재 활성 필드를 올바르게 식별하기 위해 프로그램 논리에 의존합니다.이러한 논리가 실패하면 이상한 동작과 찾기 어려운 버그가 발생할 수 있습니다.

태그 부착 결합이 각 유형의 필드를 포함하는 단순 레코드에 비해 가장 큰 장점은 모든 유형의 스토리지를 겹쳐서 저장 공간을 절약한다는 것입니다.일부 구현에서는 가장 큰 유형을 위해 충분한 스토리지를 예약하는 반면, 다른 구현에서는 태그가 지정된 유니언 값의 크기를 필요에 따라 동적으로 조정합니다.가치가 변하지 않는 경우에는 필요한 만큼의 스토리지를 쉽게 할당할 수 있습니다.

태그 부착 유니언의 주요 단점은 태그가 공간을 차지한다는 것입니다.일반적으로 선택지가 적기 때문에 공간이 발견되면 태그를 2비트 또는 3비트로 압축할 수 있지만 이들 비트조차 사용할 수 없는 경우가 있습니다.이 경우 태그 값이 유니언 필드의 내용에서 동적으로 계산되는 접힘, 계산 또는 부호화된 태그를 사용할 수 있습니다.일반적인 예로는 예약된 값을 사용하는 경우가 있습니다.예를 들어 양수를 반환하는 함수는 실패를 나타내기 위해 -1을 반환하거나 태그가 달린 포인터에서 가장 많이 사용되는 sentinel 값을 반환합니다.

C++에서는 캐스트 재해석이라고 불리는 유형 간의 비트 수준 변환을 수행하기 위해 태그 없이 유니언을 사용하는 경우가 있습니다.태그 부착 결합은 이러한 목적을 위한 것이 아닙니다.일반적으로 태그가 변경될 때마다 새로운 값이 할당됩니다.

많은 언어가 어느 정도 유니버설 데이터형을 지원합니다.유니버설 데이터형은 다른 모든 유형의 값을 포함하는 유형이며, 종종 유니버설 유형의 값의 실제 유형을 테스트하는 방법이 제공됩니다.이것들은 때때로 변종이라고 불립니다.범용 데이터 유형은 태그 부착 결합과 공식 정의에서 비교할 수 있지만, 전형적인 태그 부착 결합은 상대적으로 적은 수의 사례를 포함하며, 이러한 사례는 데이터 구조 노드 또는 명령과 같은 단일 일관성 있는 개념을 표현하는 다른 방법을 형성한다.또, 태그 부착 조합의 가능한 모든 케이스가, 그것을 사용할 때 처리될 것이라는 기대도 있다.범용 데이터 유형의 값은 관련이 없으며 모든 값을 처리할 수 있는 실행 가능한 방법이 없습니다.

옵션 유형 및 예외 처리와 마찬가지로 태그 부착 결합을 사용하여 예외 결과의 발생을 처리하는 경우도 있습니다.이러한 태그는 종종 "예약된 값"으로 접혀지며, 이러한 태그의 발생은 일관성 있게 확인되지 않습니다. 이는 프로그래밍 오류의 매우 일반적인 원인입니다.태그 부착 유니언의 사용은 다음과 같은 기능을 가진 모나드로 공식화할 수 있습니다.

여기서 "value"와 "err"는 결합 유형의 생성자이고, A와 B는 유효한 결과 유형이며, E는 오류 조건 유형입니다.또는 동일한 모나드를 리턴 및 두 가지 추가 기능(fmap join)으로 설명할 수 있습니다.

정수로 이루어진 이진 트리를 만들고 싶다고 가정해 봅시다.ML에서는 다음과 같은 데이터 유형을 생성하여 이 작업을 수행합니다.

데이터형 트리 =                  노드  (인트 * 트리 * 트리) 

이것은 두 가지 경우에 태그가 달린 결합입니다.하나는 리프입니다.리프는 트리의 경로를 종단하는 데 사용되며 명령형 언어에서 null 값이 사용하는 것과 같은 기능을 합니다.다른 브랜치에는 정수와 좌우 서브트리가 포함된 노드가 있습니다.리프 및 노드가 컨스트럭터이며, 이를 통해 다음과 같은 특정 트리를 실제로 생성할 수 있습니다.

노드(5, 노드(1, , ), 노드(3, , 노드(4, , ))) 

이는 다음 트리에 해당합니다.

The tree produced by the above constructors

이제 트리 내의 노드 수를 카운트하는 typesafe 함수를 쉽게 작성할 수 있습니다.

재밌어요 카운트 노드() = 0     카운트 노드(노드(인트, 왼쪽, 맞다)) =       1 + 카운트 노드(왼쪽) + 카운트 노드(맞다) 

언어 지원 일정

1960년대

ALGOL 68에서는 태그 부착 유니언은 Unified 모드라고 불리며 태그는 암묵적이며caseconstruct는 태그 부착 필드를 결정하는 데 사용됩니다.

mode node = union (real, int, compl, string);

의 사용 예union casenode:

노드 n : = "1234"; /소문자 n in (real r): print("real:", r), (int i): print("int:), i), (comb c): print("comb:", c), (string s): print("string:", s) out print(?:), n) esacacclint('s)

1970년대와 1980년대

주로 ML(1970년대) 및 Haskell(1990년대)과 같은 기능적 언어만이 태그 부착 조합에 중심적인 역할을 부여하고 모든 사례가 처리되었는지 확인할 수 있는 권한을 가지고 있지만, 다른 언어들도 태그 부착 조합에 대한 지원을 가지고 있다.그러나 실제로는 기능하는 언어 컴파일러에 의한 최적화로 인해 기능하지 않는 언어에서는 태그의 명시적인 체크를 없애고 [citation needed]태그의 명시적인 저장을 피할 수 있기 때문에 효율이 떨어질 수 있습니다.

Pascal, AdaModula-2이들을 바리안트 레코드(Ada에서 공식적으로 식별된 유형)라고 부르며, 다음 Pascal 예시와 같이 태그 필드를 수동으로 만들고 태그 값을 지정해야 합니다.

유형 Shape Kind(형상 종류) = (광장, 직사각형, 원형);      모양. = 기록.                 센터x : 정수;                 중앙의 : 정수;                 사례. 친절한 : Shape Kind(형상 종류)                     광장 : ( : 정수);                    직사각형 : (, 높이 : 정수);                    원형 : (반지름 : 정수);        끝.; 

그리고 이 에이다에 상당하는 것:

유형 쉐이프_친절  (광장, 직사각형, 원형); 유형 모양. (친절한 :쉐이프_친절)  기록.    센터_X : 정수;    센터_Y : 정수;    사례. 친절한        언제 광장 =>           : 정수;       언제 직사각형 =>          , 높이 : 정수;       언제 원형 =>          반지름 : 정수;    끝. 사례.; 종료 레코드;  --존재가 좌우되는 멤버에 대한 접근 시도 -- 판별자의 특정 가치로, 반면 -- 판별자가 예상된 것과 다르므로 오류가 발생합니다. 

C 및 C++에서는 태그가 없는 유니언에서 태그가 항상 체크되는 엄밀한 접근규칙을 사용하여 태그가 달린 유니언을 작성할 수 있습니다.

열거하다 ShapeKind { 광장, 직사각형, 원형 };  구조 모양. {     인트 센터x;     인트 중앙의;     열거하다 ShapeKind 친절한;     조합 {         구조 { 인트 ; };           /* 정사각형 */         구조 { 인트 , 높이; }; /* 직사각형 */         구조 { 인트 반지름; };         /* 동그라미 */     }; };  인트 get Square Side(구조 모양.* s) {     주장하다(s->친절한 == 광장);     돌아가다 s->; }  무효 set Square Side(구조 모양.* s, 인트 ) {     s->친절한 = 광장;     s-> = ; }  /* 등 */ 

유니언 필드는 함수를 통해서만 액세스할 수 있는 한 안전하고 올바르게 액세스할 수 있습니다.부호화된 태그에 대해서도 같은 방법을 사용할 수 있습니다.태그를 디코딩하고 액세스마다 체크하기만 하면 됩니다.이러한 태그 체크의 비효율성이 우려되는 경우 최종 버전에서 자동으로 삭제될 수 있습니다.

또한 C와 C++는 특정 태그가 달린 유니언(possible-null pointer)에 대한 언어 지원을 제공합니다.이것은 다음과 같습니다.optionML 또는MaybeHaskell을 입력합니다.태그 부착 포인터로 표시할 수 있습니다.태그 부착 유니언(부호화된 태그 포함)은 다음 두 가지 유형입니다.

  • 유효한 포인터,
  • 이 하나뿐인 null 포인터 유형입니다.null예외적인 상태를 나타냅니다.

유감스럽게도 C 컴파일러는 늘 케이스가 처리되는 것을 검증하지 않습니다.이는 예외적인 케이스는 무시하는 경향이 있기 때문에 C 코드에서 특히 일반적인 에러 원인입니다.

2000년대

C의 고급 방언 중 하나인 사이클론은 태그 부착 [1]유니언에 대한 광범위한 지원을 가지고 있다.

Rust, Haxe Swift 언어의 열거형은 태그 부착 유니언으로도 기능합니다.

Boost의 변형 라이브러리는 기능 개체를 사용하여 방문할 수 있는 C++ 라이브러리로 안전한 태그 부착 유니언을 구현할 수 있음을 입증했습니다.

구조 표시 : 부스트::static_displays(스태틱_displays)< >무효> {     무효 교환입니다.()(인트 i)     {         표준::외치다 << > 가치가 있는 int입니다. << > i << > 표준::;     }      무효 교환입니다.()(컨스턴트 표준::스트링& s)     {         표준::외치다 << > "그것은 가치가 있는 끈이다" << > s << > 표준::;     } };  부스트::변종< >인트, 표준::스트링> v = 42; 부스트::apply_module(적용(표시(), v);  부스트::변종< >인트, 표준::스트링> v = '헬로 월드'; 부스트::apply_module(적용(표시(), v); 

Scala에는 케이스 클래스가 있습니다.

밀봉된 추상적인 학급 트리 사례. 물건  확장 트리 사례. 학급 노드(가치: 내부, 왼쪽: 트리, 맞다: 트리) 확장 트리   트리 = 노드(5, 노드(1, , ), 노드(3, , 노드(4, , ))) 

클래스 계층이 밀봉되어 있기 때문에 컴파일러는 모든 케이스가 패턴 일치로 처리되는지 확인할 수 있습니다.

트리 경기 {   사례. 노드(x, _, _) => 인쇄("최상위 노드 값: " + x)   사례.           => 인쇄("최상위 노드는 리프") } 

Scala의 케이스 클래스에서는 서브타이핑을 통한 재사용도 가능합니다.

밀봉된 추상적인 학급 모양.(센터X: 내부, 중앙: 내부) 사례. 학급 광장(: 내부, 센터X: 내부, 중앙: 내부) 확장 모양.(센터X, 중앙) 사례. 학급 직사각형(길이: 내부, 높이: 내부, 센터X: 내부, 중앙: 내부) 확장 모양.(센터X, 중앙) 사례. 학급 원형(반지름: 내부, 센터X: 내부, 중앙: 내부) 확장 모양.(센터X, 중앙) 

F#은 유니언을 차별하고 있습니다.

유형 트리 =          노드  가치: 인트 * 왼쪽: 트리 * 맞다: 트리  허락하다 트리 = 노드(5, 노드(1, , ), 노드(3, , 노드(4, , ))) 

정의된 케이스는 모두 포괄적이기 때문에 컴파일러는 모든 케이스가 패턴 일치로 처리되는지 확인할 수 있습니다.

경기 트리 와 함께   노드 (x, _, _) -> 인쇄 "최상위 노드 값: %i" x              -> 인쇄 "최상위 노드는 리프" 

Haxe의 enums는 태그 부착 [2]유니온으로도 작동합니다.

열거하다 색. {   빨간.;   초록의;   파랑색;   RGB(r:내부, g:내부, b:내부); } 

이것들은 스위치식을 사용하여 대조할 수 있습니다.

전환하다 (색.) {   사례. 빨간.: 추적하다("컬러는 빨간색이었다");   사례. 초록의: 추적하다("색상은 녹색이었다");   사례. 파랑색: 추적하다("컬러는 파란색이었다");   사례. RGB(r, g, b): 추적하다("컬러의 빨간색 값은 " 입니다." +r); } 

Nim에는 Pascal 및 Ada와 유사한 객체[3] 변형이 있습니다.

유형   ShapeKind = 열거하다     skSquare, 직각, skCircle(서클)   모양. = 물건     센터X, 중앙: 인트     사례. 친절한: ShapeKind      skSquare:       : 인트      직각:       길이, 높이: 인트      skCircle(서클):       반지름: 인트 

매크로를 사용하여 패턴 매칭을 에뮬레이트하거나 오브젝트 배리언트를 선언하기 위한 구문설탕을 작성할 수 있습니다.여기에는 patty 패키지로 구현되어 있습니다.

수입품 패티  프로세서`~`[A](a: A): 레퍼런스 A =   신규(결과)   결과[] = a  변종 목록.[A]:   제로   단점(x: A, xs: 레퍼런스 목록.[A])  프로세서리스트 헬퍼[A](xs: 인식하다[A]): 목록.[A] =   한다면 xs. == 0: 제로[A]()   또 다른: 단점(xs[0], ~리스트 헬퍼(xs[1 .. xs.높은]))  프로세서목록.[A](xs: 이상[A]): 목록.[A] = 리스트 헬퍼(@xs)  프로세서(xs: 목록.[인트]): 인트 = (블록:   경기 xs:     제로: 0     단점(y, ys): y + (ys[]) )  메아리치다 (목록.(1, 2, 3, 4, 5)) 

2010년대

Scala [4]3에는 Enum이 추가되어 있기 때문에 이전 Scala의 예를 보다 간결하게 다시 쓸 수 있습니다.

열거하다 트리[+T]:   사례.    사례. 노드(x: 내부, 왼쪽: 트리[T], 맞다: 트리[T])  열거하다 모양.(센터X: 내부, 중앙: 내부):   사례. 광장(: 내부, 센터X: 내부, 중앙: 내부) 확장 모양.(중앙, 센터X)   사례. 직사각형(길이: 내부, 높이: 내부, 센터X: 내부, 중앙: 내부) 확장 모양.(센터X, 중앙)   사례. 원형(반지름: 내부, 센터X: 내부, 중앙: 내부) 확장 모양.(센터X, 중앙) 

러스트 언어[5]에넘이라고 불리는 태그 부착 유니언을 광범위하게 지원합니다.예를 들어 다음과 같습니다.

열거하다 트리 {     ,     노드(i64, 박스< >트리>, 박스< >트리>) } 

또한 유니언에 매칭할 수 있습니다.

트리 = 트리 허용:: Node ( 2, 박스:: new (트리::Node(0, 박스::new(트리::리프), 박스::new(트리::리프), 박스::new(트리:: Node(3, 박스::new(트리::리프), 박스: new(트리:: Node(4, 박스): new(트리::리프), 박스::new(트리::리프)), fn add_values(트리:트리) -> i64 {match tree { 트리:: Node(v, a, b) => v + add_values aa) + add_values bb), 트리:리프 => 0 } assert_eq!(add_values(트리), 9);

Rust의 오류 처리 모델은 이러한 태그 부착 조합, 특히Option<T>type, 다음 중 하나입니다.None또는Some(T), 및Result<T, E>type, 다음 중 하나입니다.Ok(T)또는Err(E)를 클릭합니다.[6]

Swift는 또한 [7]열거를 통해 태그 부착 유니언에 대한 상당한 지원을 가지고 있다.예를 들어 다음과 같습니다.

열거하다 트리 {     사례. 잎사귀     간접적인 사례. 노드(내부, 트리, 트리) }  허락하다 트리 = 트리.노드(     2,     .노드(0, .잎사귀, .잎사귀),     .노드(3, .잎사귀, .노드(4, .잎사귀, .잎사귀)) )  기능하다 add_values 값(_ 트리: 트리) -> 내부 {     전환하다 트리 {     사례. 허락하다 .노드(v, a, b):         돌아가다 v + add_values 값(a) + add_values 값(b)      사례. .잎사귀:         돌아가다 0     } }  주장하다(add_values 값(트리) == 9) 

TypeScript를 사용하면 태그 부착 유니언도 작성할 수 있습니다.예를 들어 다음과 같습니다.

인터페이스 리프 { 유형: "leaf", : 문자열, } 인터페이스 노드 { 유형: "node"; 왼쪽:트리(오른쪽):트리; } 트리 = 리프 노드 함수 방문(트리:트리) { 스위치(tree.type) {케이스 "leaf": console.log(tree.value) 브레이크 케이스 "node": visit(tree.left) visit(tree.right) break }}

Python 3.9에서는 태그 부착 유니언 타입(PEP-593)[8]을 정의하는 데 사용할 수 있는 주석 입력 지원이 도입되었습니다.

통화 = 주석 첨부[     타이프딕트('통화', {'실패': 흘러가다, '실패': 흘러가다}, =거짓의),     Tagged Union(태그드 유니언), ] 

C++17에는 std::variable 및 constexpr이 도입되어 있습니다.

사용. 트리 = 표준::변종< >구조 , 구조 노드>;  구조  {   표준::스트링 가치; }; 구조 노드 {   트리* 왼쪽 = 특수;   트리* 맞다 = 특수; };  구조 트랜스버서 {   템플릿< >타이프네임 T>   무효 교환입니다.()(T& & v)   {     한다면 콘스펙트 (표준::is_same_v< >T, &>)     {       표준::외치다 << > v.가치 << > "\n";     }     또 다른 한다면 콘스펙트 (표준::is_same_v< >T, 노드&>)     {       한다면 (v.왼쪽 != 특수)         표준::방문하다(트랜스버서{}, *v.왼쪽);        한다면 (v.맞다 != 특수)         표준::방문하다(트랜스버서{}, *v.맞다);     }     또 다른     {       // !sizeof(T) 식은 항상 false입니다.       static_displays(스태틱_displays)(!크기(T), "비침묵적인 방문자!");     };   } }; /*트리 포레스트 =...; std::visit(트랜스퍼{}, 포레스트);*/ 

태그 부착 유니언으로서의 클래스 계층

객체 지향 프로그래밍의 일반적인 클래스 계층에서는 각 서브 클래스가 해당 클래스에 고유한 데이터를 캡슐화할 수 있습니다.가상 메서드 검색에 사용되는 메타데이터(예를 들어 대부분의 C++ 구현에서는 객체의 vtable 포인터)는 서브 클래스를 식별하므로 인스턴스에서 저장된 특정 데이터를 식별하는 태그로서 효과적으로 작동합니다(RTI 참조).오브젝트의 생성자가 이 태그를 설정하고 오브젝트의 수명 동안 일정하게 유지됩니다.

단, 클래스 계층에는 진정한 서브타입 다형성이 포함됩니다.태그/디스패치 모델에서는 올바르게 처리할 수 없었던 동일한 베이스 타입의 서브클래스를 추가로 생성하여 확장할 수 있습니다.따라서 태그 부착 결합의 경우처럼 하위 객체의 '태그'에 대해 사례 분석이나 디스패치를 수행할 수 없다.Scala와 같은 일부 언어에서는 기본 클래스를 "씰링"할 수 있으며 태그가 지정된 유니언과 씰링된 기본 클래스를 통합합니다.

「 」를 참조해 주세요.

레퍼런스

  1. ^ http://cyclone.thelanguage.org/wiki/Tagged%20Unions
  2. ^ "Using Enums - Haxe - The Cross-platform Toolkit". Haxe Foundation.
  3. ^ "Nim Manual". nim-lang.org. Retrieved 2020-01-23.
  4. ^ "Scala 3 Language Reference: Enumerations". The Scala Team.
  5. ^ "The Rust Programming Language". Mozilla.
  6. ^ "Rust By Example". Mozilla.
  7. ^ "Enumerations — The Swift Programming Language (Swift 5.4)". docs.swift.org. Retrieved 2021-04-28.
  8. ^ "PEP 593 -- Flexible function and variable annotations". Python.org. Retrieved 2021-06-20.

외부 링크