통일이란 무엇입니까?


163

Java가 삭제와 함께 매개 변수 다형성 (Generics)을 구현한다는 것을 알고 있습니다. 나는 소거가 무엇인지 이해합니다.

C #이 reification을 통해 파라 메트릭 다형성을 구현한다는 것을 알고 있습니다. 나는 당신이 쓸 수 있음을 알고

public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}

또는 일부 매개 변수화 형식의 형식 매개 변수가 무엇인지 런타임에 알 수 있습니다,하지만 난 그것을 이해하지 않는 것이 있다 .

  • 통합 유형이란 무엇입니까?
  • 통일 된 가치는 무엇입니까?
  • 유형 / 값이 확정되면 어떻게됩니까?

답은 아니지만 어쨌든 도움이 될 수 있습니다. beust.com/weblog/2011/07/29/erasure-vs-reification
heringer

@heringer는 "지우기 란 무엇인가"라는 질문에 상당히 잘 대답하는 것으로 보이며, 기본적으로 "지우지 않음"이란 "지우기 란 무엇인가"라는 대답으로 보입니다.
Martijn

5
... if정리가 이전에 / 에서 ... 로 변환되었을 때 switch구조체를 if/로 다시 변환하는 과정 이라고 생각 했습니다 .elseifelseswitch
Digital Trauma

8
해상도 , 리스 라틴어 인 , 그래서 구체화는 말 그대로 thingification . C #이 용어를 사용하는 한 도움이되는 것은 없지만 그 용어 자체가 나를 사용한다는 사실은 나를 웃게합니다.
KRyan

답변:


209

Reification은 추상적 인 것을 취하고 구체적인 것을 만드는 과정입니다.

C # 제네릭에서 reification 이라는 용어 는 제네릭 형식 정의 와 하나 이상의 제네릭 형식 인수 (추상적 인 것)를 결합하여 새로운 제네릭 형식 (구체적인 것) 을 만드는 프로세스를 나타냅니다 .

문구는 다르게, 그것의 정의를 복용하는 과정입니다 List<T>int및 콘크리트 생산 List<int>유형입니다.

더 이해하려면 다음 방법을 비교하십시오.

  • Java 제네릭에서 제네릭 형식 정의는 기본적으로 허용되는 모든 형식 인수 조합에서 공유되는 하나의 구체적 제네릭 형식으로 변환됩니다. 따라서 여러 (소스 코드 레벨) 유형이 하나의 (이진 레벨) 유형에 맵핑되지만 결과적 으로 인스턴스의 유형 인수에 대한 정보는 해당 인스턴스에서 삭제됩니다 (유형 소거) .

    1. 이 구현 기법의 부작용으로 기본적으로 허용되는 유일한 일반 형식 인수는 구체적 형식의 이진 코드를 공유 할 수있는 형식입니다. 이는 저장 위치가 상호 교환 가능한 표현을 갖는 유형을 의미합니다. 이는 참조 유형을 의미합니다. 값 형식을 일반 형식 인수로 사용하려면 해당 형식을 상자에 넣어야합니다 (단순 참조 형식 래퍼에 배치).
    2. 이런 식으로 제네릭을 구현하기 위해 코드가 복제되지 않습니다.
    3. 리플렉션을 사용하여 런타임에 사용 가능했던 유형 정보가 손실됩니다. 이는 제네릭 형식의 특수화 ( 특정 제네릭 인수 조합에 대해 특수 소스 코드 를 사용하는 기능 )가 매우 제한되어 있음을 의미합니다.
    4. 이 메커니즘은 런타임 환경의 지원이 필요하지 않습니다.
    5. Java 프로그램 또는 JVM 기반 언어가 사용할 수있는 유형 정보를 유지 하는 몇 가지 해결 방법이 있습니다.
  • C # 제네릭에서 제네릭 형식 정의는 런타임에 메모리에 유지됩니다. 새 콘크리트 유형이 필요할 때마다 런타임 환경은 일반 유형 정의와 유형 인수를 결합하여 새 유형을 작성합니다 (확인). 그래서 우리 는 런타임에 형식 인수의 각 조합에 대해 새로운 형식을 얻습니다 .

    1. 이 구현 기법을 사용하면 모든 유형의 인수 조합을 인스턴스화 할 수 있습니다. 값 형식을 일반 형식 인수로 사용하면 해당 형식이 자체 구현되므로 boxing이 발생하지 않습니다. ( 권투는 여전히 C #에 존재 하지만 이것은 다른 시나리오에서는 발생하지 않습니다.)
    2. 코드 복제는 문제가 될 수 있지만 실제로는 스마트 한 구현 ( Microsoft .NETMono 포함 )이 일부 인스턴스화를 위해 코드를 공유 할 수 있기 때문에 실제로는 그렇지 않습니다 .
    3. 유형 정보가 유지되므로 리플렉션을 사용하여 유형 인수를 검사하여 어느 정도 전문화 할 수 있습니다. 그러나 일반화 유형 정의가 reification이 발생 하기 전에 컴파일 되기 때문에 전문화의 정도가 제한됩니다 (이는 유형 매개 변수의 제한 조건에 대해 정의컴파일하여 수행 되므로 컴파일러는 특정 유형 인수가없는 경우에도 정의를 "이해"하십시오 .
    4. 이 구현 기술은 런타임 지원 및 JIT 컴파일에 크게 의존합니다 (따라서 C # 제네릭이 iOS와 같은 플랫폼 에서 동적 코드 생성이 제한되는 경우가 있음).
    5. C # 제네릭의 맥락에서 런타임 환경에 따라 수정이 수행됩니다. 그러나 제네릭 형식 정의와 구체적인 제네릭 형식의 차이점을 더 직관적으로 이해 하려면 System.Type클래스를 사용하여 언제든지 직접 리파이닝을 수행 할 수 있습니다 (인스턴스화하는 특정 제네릭 형식 인수 조합이 ' t는 소스 코드에 직접 나타납니다).
  • C ++ 템플릿에서 템플릿 정의는 컴파일 타임에 메모리에 유지됩니다. 소스 코드에서 템플릿 유형의 새 인스턴스화가 필요할 때마다 컴파일러는 템플릿 정의와 템플릿 인수를 결합하여 새 유형을 만듭니다. 따라서 컴파일 타임에 템플릿 인수의 각 조합에 대해 고유 한 유형을 얻 습니다 .

    1. 이 구현 기술은 모든 종류의 유형 인수 조합을 인스턴스화 할 수 있습니다.
    2. 이것은 바이너리 코드를 복제하는 것으로 알려져 있지만 충분히 똑똑한 툴 체인은 여전히 ​​이것을 감지하고 일부 인스턴스화를 위해 코드를 공유 할 수 있습니다.
    3. 템플릿 정의 자체는 "컴파일"되지 않으며 구체적인 인스턴스화 만 실제로 컴파일 됩니다. 이것은 컴파일러에 대한 제약이 적고보다 큰 템플릿 전문화를 허용합니다 .
    4. 템플릿 인스턴스화는 컴파일 타임에 수행되므로 여기에서도 런타임 지원이 필요하지 않습니다.
    5. 이 프로세스는 최근 에 특히 Rust 커뮤니티에서 단형 화 라고합니다 . 이 단어는 파라 메트릭 다형성 과 대조적으로 사용 되는데, 이는 제네릭이 유래 한 개념의 이름입니다.

7
C ++ 템플릿과의 훌륭한 비교 ... C #과 Java의 제네릭 사이에있는 것 같습니다. C #에서와 같이 서로 다른 특정 제네릭 형식을 처리하기위한 코드와 구조가 다르지만 모두 Java에서와 같이 컴파일 타임에 수행됩니다.
Luaan

3
또한 C ++에서는 각 (또는 일부) 콘크리트 유형이 다른 구현을 가질 수있는 템플릿 전문화를 도입 할 수 있습니다. 분명히 Java에서는 가능하지 않지만 C #에서는 불가능합니다.
quetzalcoatl

@quetzalcoatl을 사용하는 한 가지 이유는 포인터 유형으로 생성 된 코드의 양을 줄이는 것이므로 C #은 뒤에서 참조 유형과 비슷한 것을 수행합니다. 그럼에도 불구하고, 그것이 그것을 사용하는 가지 이유 뿐이며 , 템플릿 전문화가 좋을 때가 있습니다.
Jon Hanna

Java의 경우 유형 정보가 지워지는 동안 컴파일러가 캐스트를 추가하여 바이트 코드를 일반 제네릭 바이트 코드와 구분할 수 없도록합니다.
Rusty Core

27

통일 이란 일반적으로 "컴퓨터 과학 이외의 것"을 "실제로 만들기"를 의미합니다.

프로그래밍 에서 언어 자체의 정보에 액세스 할 수 있으면 무언가가 구체화 됩니다.

C #이 수행하고 구체화하지 않은 제네릭과 관련이없는 두 가지 예를 들어, 메소드와 메모리 액세스를 봅시다.

OO 언어에는 일반적으로 메소드 가 있으며 클래스에 바인딩되지는 않지만 유사한 함수 를 갖지 않는 메소드가 많이 있습니다. 따라서 그러한 언어로 메소드를 정의하고 호출하거나 대체 할 수 있습니다. 이러한 모든 언어를 사용하면 실제로 메소드 자체를 프로그램의 데이터로 처리 할 수 ​​있습니다. C # (및 실제로 C #이 아닌 .NET)을 사용 MethodInfo하면 메서드를 나타내는 개체를 사용할 수 있으므로 C # 메서드가 구체화됩니다. C #의 메소드는 "퍼스트 클래스 객체"입니다.

모든 실제 언어에는 컴퓨터의 메모리에 액세스 할 수있는 수단이 있습니다. C와 같은 저수준 언어에서는 컴퓨터가 사용하는 숫자 주소 간의 매핑을 직접 처리 할 수 ​​있으므로 이와 같은 방식으로 int* ptr = (int*) 0xA000000; *ptr = 42;메모리 주소 0xA000000에 액세스하는 것이 의심 될만한 이유가 있다면 무언가를 날려 버립니다). C #에서는 이것이 합리적이지 않습니다 (우리는 .NET에서 강제 할 수는 있지만 .NET 메모리 관리를 사용하면 유용하지 않을 것입니다). C #에는 통합 메모리 주소가 없습니다.

그래서,로 refied 수단은 "구체화 유형" "진짜을했다"유형 우리가 할 수있는 "이야기"입니다 해당 언어이다.

일반적으로 이것은 두 가지를 의미합니다.

하나는 List<string>그대로 string또는 그대로 유형 int입니다. 해당 유형을 비교하고 이름을 확인한 후 문의 할 수 있습니다.

Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True

그 결과 메소드 자체 내에서 제네릭 메소드 (또는 제네릭 클래스의 메소드) 매개 변수 유형을 "토론"할 수 있습니다.

public static void DescribeType<T>(T element)
{
  Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
  DescribeType(42);               // System.Int32
  DescribeType(42L);              // System.Int64
  DescribeType(DateTime.UtcNow);  // System.DateTime
}

일반적으로이 작업을 너무 많이하는 것은 "취약한"것이지만 많은 유용한 경우가 있습니다. 예를 들어, 다음을보십시오.

public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
  if (source == null) throw Error.ArgumentNull("source");
  Comparer<TSource> comparer = Comparer<TSource>.Default;
  TSource value = default(TSource);
  if (value == null)
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      do
      {
        if (!e.MoveNext()) return value;
        value = e.Current;
      } while (value == null);
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (x != null && comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  else
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      if (!e.MoveNext()) throw Error.NoElements();
      value = e.Current;
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  return value;
}

이는 유형 사이의 비교를 많이하지 않는 TSource다른 행동에 대한 여러 유형의 (모든에서 제네릭을 사용하지 것이 일반적으로 기호)하지만이 될 수 종류의 코드 경로 사이의 분할을한다 null(반환해야 null하는 경우 어떤 요소를 발견하고, 비교 요소 중 하나 인 경우 최소 찾을 비교를 안 null) 및 수없는 유형의 코드 경로 null에는 요소를 찾을 수없는 경우 throw 할 필요가을 (, 그리고 가능성에 대해 걱정하지 않는 null요소 ).

때문에 TSource이 방법에서 "진짜"입니다,이 비교는 런타임 또는 jitting시 중 하나를 만들 수 있습니다 (일반적으로 확실히 위의 경우는 시간을 jitting에서 그렇게 할 것 아닌 경로가 취해지지에 대한 기계 코드를 생성, 시간을 jitting) 그리고 우리는이 각 경우에 대해 별도의 "실제"버전의 방법. (최적화 임에도 불구하고 머신 코드는 다른 참조 유형 유형 매개 변수에 대해 서로 다른 메소드에 대해 공유됩니다. 이는 영향을 미치지 않을 수 있기 때문에 머신 코드의 양을 줄일 수 있습니다).

C #으로 우리가 당연이 구체화을하기 때문에 당신은 또한, 자바와 거래를하지 않는 (그것은 C #에서 일반적인 유형의 구체화에 대해 이야기하는 것이 일반적 아니라, 모든 유형의 구체화되는 자바가 아닌 일반적인 유형이라고합니다. 구체화 그 때문에 그들과 일반 유형의 차이입니다).


Min위의 유용한 기능 을 수행 할 수 없다고 생각 하십니까? 그렇지 않으면 문서화 된 동작을 수행하기가 매우 어렵습니다.
존 한나

나는 버그를 (문서화되지 않은) 행동으로 간주하고 그 행동이 유용하다는 의미를 고려합니다 ( Enumerable.Min<TSource>비공식으로, 빈 컬렉션에서 비 참조 유형을 던지지 않지만 기본값을 반환한다는 점에서 동작 은 다릅니다) (TSource)이며 "일반 시퀀스의 최소값을 반환합니다."로만 문서화되어 있습니다. 빈 컬렉션을 모두 던져야하거나 "제로"요소를 기준선으로 전달해야하며 비교기 / 비교 함수는 항상 전달되어야합니다.)
Martijn

1
null이 아닌 유형에서는 불가능을 시도하지 않고 nullable 유형의 일반적인 db 동작과 일치하는 현재 Min보다 훨씬 유용하지 않습니다. (기준 아이디어는 불가능하지는 않지만 소스에 절대 포함되지 않는 값이 없으면 유용하지 않습니다.)
Jon Hanna

1
Thingification 이 더 나은 이름 이었을 것입니다. :)
tchrist

@tchrist 일이 비현실적 일 수 있습니다.
Jon Hanna

15

마찬가지로 duffymo 이미 언급 , "구체화는"키 차이가 없습니다.

Java에서 제네릭은 기본적으로 컴파일 타임 지원을 향상시키기 위해 존재합니다. 코드에서 강력한 형식의 예를 들어 컬렉션을 사용하고 형식 안전성을 처리 할 수 ​​있습니다. 그러나 이것은 컴파일 타임에만 존재합니다-컴파일 된 바이트 코드는 더 이상 제네릭 개념을 갖지 않습니다. 모든 제네릭 형식은 "콘크리트"형식으로 변환되어 ( object제네릭 형식이 제한되지 않은 경우 사용 ) 필요에 따라 형식 변환 및 형식 검사를 추가합니다.

.NET에서 제네릭은 CLR의 필수 기능입니다. 제네릭 형식을 컴파일하면 생성 된 IL에서 제네릭 형식으로 유지됩니다. Java 에서처럼 제네릭이 아닌 코드로 변환 된 것이 아닙니다.

이것은 제네릭이 실제로 작동하는 방식에 몇 가지 영향을 미칩니다. 예를 들면 다음과 같습니다.

  • Java는 SomeType<?>주어진 제네릭 형식의 구체적인 구현을 전달할 수 있어야합니다. C #에서는이 작업을 수행 할 수 없습니다. 모든 특정 ( reified ) 제네릭 형식은 자체 형식입니다.
  • Java에서 무제한 제네릭 형식은 해당 값이로 저장됨을 의미합니다 object. 이러한 제네릭에서 값 형식을 사용할 때 성능에 영향을 줄 수 있습니다. C #에서 제네릭 형식의 값 형식을 사용하면 값 형식이 유지됩니다.

샘플을 제공하기 위해 List하나의 제네릭 인수가 있는 제네릭 형식 이 있다고 가정하겠습니다 . 자바에서 List<String>List<Int>런타임에 동일한 유형 끝나게됩니다 - 일반 유형은 정말 컴파일 타임 코드 존재한다. 예를 들어 모든 호출 GetValue은 각각 (String)GetValue및 로 변환됩니다 (Int)GetValue.

C #에서, List<string>그리고 List<int>서로 다른 두 가지 종류가 있습니다. 그것들은 서로 바꿔 쓸 수 없으며 런타임에도 타입 안전이 시행됩니다. 아니 당신이 무슨 상관 new List<int>().Add("SomeString")것이다 결코 일을 -의 기본 스토리지 List<int>입니다 정말 자바, 그것은 반드시 반면, 일부 정수 배열 object배열입니다. C #에서는 캐스트가없고 권투 등이 없습니다.

이것은 또한 C #이 Java와 같은 일을 할 수없는 이유를 분명히해야합니다 SomeType<?>. Java에서 "유래 된"모든 일반 유형 SomeType<?>은 정확히 동일한 유형입니다. C #에서 모든 다양한 특정 SomeType<T>은 고유 한 개별 유형입니다. 컴파일 타임 검사를 제거하면 SomeType<Int>대신에 전달할 수 있습니다 SomeType<String>(실제로 SomeType<?>"특정 유형에 대한 컴파일 타임 검사 무시"). C #에서는 파생 형식조차 불가능합니다 (즉, 에서 파생 된 List<object> list = (List<object>)new List<string>();경우에도 수행 할 수 없음 ).stringobject

두 구현 모두 장단점이 있습니다. SomeType<?>C #에서 인수로 허용 할 수 있기를 좋아했을 때가 몇 번 있었지만 C # 제네릭의 작동 방식은 이해가되지 않습니다.


2
글쎄, C #에서 types List<>등 을 사용할 수 Dictionary<,>있지만 주어진 콘크리트 목록이나 사전 사이의 간격은 브리지에 약간의 반영이 필요합니다. 인터페이스의 차이는 한 번은 그러한 차이를 쉽게 극복하고 싶었던 일부 경우에 도움이되지만 전부는 아닙니다.
Jon Hanna

2
@JonHanna List<>새로운 특정 제네릭 형식을 인스턴스화하는 데 사용할 수 있지만 여전히 원하는 특정 형식을 만드는 것을 의미합니다. 그러나 List<>예를 들어 인수로 사용할 수 없습니다 . 그러나 예, 적어도 이것은 반사를 사용하여 격차를 메울 수 있습니다.
Luaan

.NET Framework에는 스토리지 위치 유형이 아닌 세 가지 하드 코딩 된 일반 제약 조건이 있습니다. 다른 모든 제약 조건은 저장소 위치 유형이어야합니다. 또한, 일반 타입 만 번 T저장 로케이션 형 제약을 만족시킬 수는 U때이다 TU동일한 유형 또는 U인스턴스에 대한 참조를 저장할 수있는 타입이다 T. 유형의 저장 위치를 ​​의미있게 가질 수는 SomeType<?>없지만 이론적으로 해당 유형의 일반적인 제약 조건을 가질 수 있습니다.
supercat

1
컴파일 된 Java 바이트 코드에 제네릭 개념이 없다는 것은 사실이 아닙니다. 클래스 인스턴스 에는 제네릭 개념이 없습니다. 이것은 중요한 차이점입니다. 내가 관심이 있다면 programmers.stackexchange.com/questions/280169/… 에 대해 이전에 썼습니다 .
ruakh

2

Reification은 객체 지향 모델링 개념입니다.

Reify는 "추상적 인 것을 현실로 만든다" 라는 의미의 동사입니다 .

객체 지향 프로그래밍을 할 때 실제 객체를 소프트웨어 컴포넌트 (예 : 창, 버튼, 사람, 은행, 차량 등)로 모델링하는 것이 일반적입니다.

추상적 개념을 컴포넌트 (예 : WindowListener, Broker 등)로 구체화하는 것도 일반적입니다.


2
Reification은 "실제로 만들기"라는 일반적인 개념으로, 객체 지향 모델링에 적용 할 수 있지만 제네릭 구현의 맥락에서도 의미가 있습니다.
Jon Hanna

2
그래서 나는이 답을 읽고 교육을 받았습니다. 답변을 수정하겠습니다.
duffymo

2
이 답변은 제네릭과 파라 메트릭 다형성에 대한 OP의 관심사를 다루지 않습니다.
Erick G. Hagstrom 4

이 의견은 다른 사람의 관심사를 해결하거나 담당자를 높이는 데 아무런 도움이되지 않습니다. 나는 당신이 아무 것도 제안하지 않은 것을 본다. 내 대답은 첫 번째 대답이었으며, 통일을 더 넓은 것으로 정의했습니다.
duffymo

1
귀하의 답변이 첫 번째 였을 수도 있지만 OP의 질문이 아닌 다른 질문에 답변하셨습니다. 질문의 내용과 해당 태그에서 명확했을 것입니다. 아마도 답을 쓰기 전에 질문을 완전히 읽지 않았거나 "통합"이라는 용어가 제네릭의 맥락에서 확립 된 의미를 가지고 있다는 것을 모를 수도 있습니다. 어느 쪽이든, 당신의 대답은 유용하지 않습니다. 공감.
jcsahnwaldt Reinstate Monica
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.