좋은 제네릭 형식 시스템


29

Java 제네릭이 몇 가지 중요한 방식으로 실패한 것으로 일반적으로 인정됩니다. 와일드 카드와 바운드의 조합으로 인해 읽을 수없는 코드가 생겼습니다.

그러나 다른 언어를 볼 때 프로그래머가 만족하는 일반적인 유형 시스템을 찾지 못하는 것 같습니다.

이러한 유형 시스템의 설계 목표로 다음을 고려하면 :

  • 항상 읽기 쉬운 타입 선언을 생성합니다
  • 배우기 쉬움 (공분산, 반 분산 등을 정리할 필요가 없음)
  • 컴파일 타임 오류 수를 최대화

제대로 된 언어가 있습니까? 내가 구글이라면, 내가 보는 유일한 것은 타입 시스템이 언어 X를 어떻게 빨아들이는지에 대한 불만이다. 이런 종류의 복잡성은 일반적인 타이핑에 내재되어 있는가? 컴파일 타임에 타입 안전을 100 % 검증하려고 포기해야합니까?

저의 주요 질문은이 세 가지 목표와 관련하여 가장 잘 맞는 언어입니다. 나는 그것이 주관적이라는 것을 알고 있지만, 지금까지 프로그래머가 일반 타입 시스템이 엉망이라는 데 동의하지 않는 언어를 찾을 수는 없습니다.

부록 : 언급 한 바와 같이, 서브 타이핑 / 상속과 제네릭의 조합이 복잡성을 만드는 것이므로, 저는 둘 다를 결합하고 복잡성의 폭발을 피하는 언어를 찾고 있습니다.


2
무슨 소리 야 easy-to-read type declarations? 세 번째 기준도 모호합니다. 예를 들어, 컴파일 타임에 인덱스를 계산할 수 없다면 배열 인덱스를 허용하지 않으면 배열 인덱스를 범위 예외에서 컴파일 타임 오류로 바꿀 수 있습니다. 또한 두 번째 기준은 하위 유형을 배제합니다. 반드시 나쁜 것은 아니지만 요청한 내용을 알고 있어야합니다.
Doval


9
@ gnat, 이것은 Java에 대한 보증이 아닙니다. 나는 거의 독점적으로 Java로 프로그래밍합니다. 제 요점은 일반적으로 Java 커뮤니티 내에서 Generics에 결함이 있다는 것 (전체 실패는 아니지만 부분적인 결함) 으로 받아 들여 지므로 구현 방법을 묻는 것은 논리적 인 질문입니다. 왜 그들이 틀렸고 다른 사람들이 그것들을 올바르게 얻었습니까? 아니면 제네릭을 절대적으로 올바르게 얻는 것이 실제로 불가능합니까?
Peter

1
모두 C #에서 훔친 것이 불만이 적습니다. 특히 Java는 복사를 통해 따라 잡을 수있는 위치에 있습니다. 대신 그들은 열등한 해결책을 결정합니다. Java 디자인위원회가 여전히 논의하고있는 많은 질문들이 이미 C #에서 결정되고 구현되었습니다. 그들은 심지어 보이지 않는 것 같습니다.
usr

2
@emodendroket : C # 제네릭에 대한 두 가지 가장 큰 불만은 "슈퍼 타입"제약 조건 (예 :)을 적용 할 방법 이없고 로 변환 할 수 없는Foo<T> where SiameseCat:T 제네릭 유형을 가질 가능성이 없다는 것입니다 . IMHO, .NET은 구조와 비슷하지만 더 뼈가 큰 집계 유형의 이점을 누릴 수 있습니다. 경우 이러한 유형했다, 다음은 에 캐스팅 될 수 있지만, 유형 박스 할 수없는 경우에만 가능합니다. ObjectKeyValuePair<TKey,TValue>IEnumerable<KeyValuePair<SiameseCat,FordFocus>>IEnumerable<KeyValuePair<Animal,Vehicle>>
supercat

답변:


24

제네릭은 함수형 프로그래밍 커뮤니티에서 수십 년 동안 주류를 유지해 왔지만 객체 지향 프로그래밍 언어에 제네릭을 추가하면 몇 가지 고유 한 문제, 특히 서브 타이핑과 제네릭의 상호 작용이 발생합니다.

그러나 객체 지향 프로그래밍 언어, 특히 Java에 중점을 두더라도 훨씬 우수한 제네릭 시스템을 설계 할 수 있습니다.

  1. 다른 유형이있는 곳에서는 일반 유형을 사용할 수 있어야합니다. 특히 T유형 매개 변수 인 경우 다음 표현식은 경고없이 컴파일되어야합니다.

    object instanceof T; 
    T t = (T) object;
    T[] array = new T[1];
    

    예, 언어의 다른 모든 유형과 마찬가지로 제네릭을 통일해야합니다.

  2. 제네릭 형식의 공분산과 반공란은 제네릭 형식을 사용할 때마다가 아니라 선언에 지정하거나 유추해야합니다.

    Future<Provider<Integer>> s;
    Future<Provider<Number>> o = s; 
    

    오히려

    Future<? extends Provider<Integer>> s;
    Future<? extends Provider<? extends Number>> o = s;
    
  3. 제네릭 형식은 다소 길어질 수 있으므로 중복 적으로 지정할 필요가 없습니다. 즉, 우리는 쓸 수 있어야합니다

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (var e : map.values()) {
        for (var list : e.values()) {
            for (var person : list) {
                greet(person);
            }
        }
    }
    

    오히려

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (Map<String, List<LanguageDesigner>> e : map.values()) {
        for (List<LanguageDesigner> list : e.values()) {
            for (LanguageDesigner person : list) {
                greet(person);
            }
        }
    }
    
  4. 모든 유형은 참조 유형뿐만 아니라 유형 매개 변수로 허용되어야합니다. (을 가질 수 있다면 왜을 가질 int[]List<int>없습니까?)

이 모든 것은 C #에서 가능합니다.


1
이것도 자기 참조 제네릭을 제거합니까? 비교 가능한 객체가 동일한 유형이나 하위 클래스의 객체와 비교할 수 있다고 말하고 싶다면 어떻게해야합니까? 그렇게 할 수 있습니까? 또는 비슷한 객체가있는 목록을 허용하는 정렬 메소드를 작성하면 모두 서로 비교할 수 있어야합니다. Enum은 또 다른 좋은 예입니다. Enum <E는 Enum <E >>를 확장합니다. 형식 시스템이 이러한 작업을 수행 할 수 있어야한다고 말하는 것이 아니라 C #이 이러한 상황을 처리하는 방법이 궁금합니다.
Peter

1
Java 7의 제네릭 형식 유추C ++ 의 이러한 자동 문제는 이러한 문제 중 일부에 도움이되지만 구문 설탕이며 기본 메커니즘을 변경하지 않습니다.

@Snowman Java의 형식 유추에는 익명 클래스를 전혀 사용하지 않고 일반 메서드를 다른 일반 메서드에 대한 인수로 평가할 때 와일드 카드에 대한 올바른 경계를 찾지 못하는 것과 같이 정말 모호한 코너 사례가 있습니다.
Doval

@Doval은 이것이 우려 사항 중 일부에 도움이된다고 말한 이유입니다. Java 제네릭에는 많은 문제가 있습니다. 원시 유형보다 낫지 만 많은 두통을 유발합니다.

34

하위 유형을 사용하면 일반 프로그래밍을 수행 할 때 많은 문제가 발생합니다. 하위 유형이있는 언어를 사용한다고 주장하는 경우 일반 프로그래밍에 고유 한 복잡성이 있음을 인정해야합니다. 일부 언어는 다른 언어보다 더 잘하지만 지금까지만 사용할 수 있습니다.

예를 들어 Haskell의 제네릭과 대조하십시오. 형식 유추를 사용하는 경우 우연히 올바른 일반 함수를 작성할 수있을 정도로 간단 합니다. 당신은 하나의 유형을 지정하는 경우 사실, 컴파일러는 종종 자신에게 말한다, "글쎄, 내가 했다 그래서 어떤이는 일반적인 만들려고하지만, 당신은 단지의 int을 위해 그것을하기 위해 나에게 물었다."

분명히 사람들 Haskell의 유형 시스템을 놀랍도록 복잡한 방식으로 사용하여 모든 초보자의 골칫거리가되었지만 기본 유형 시스템 자체는 우아하고 존경받습니다.


1
이 답변에 감사드립니다. 이 기사는 제네릭이 너무 복잡 해지는 Joshua Bloch의 예 ( artima.com/weblogs/viewpost.jsp?thread=222021)로 시작 합니다. 이것이 자바와 하스켈의 문화적 차이입니까? 하스켈에서 그러한 구조가 잘 고려되는 곳입니까? 아니면 그러한 상황을 피하는 하스켈의 유형 시스템과 실제로 다른 점이 있습니까?
Peter

10
@Peter Haskell은 서브 타이핑이 없으며 Karl과 같이 컴파일러는 "type a은 일종의 정수 여야한다 "와 같은 제약 조건을 포함하여 타입을 자동으로 추론 할 수 있다고 말했다 .
Doval

다시 말해 스칼라와 같은 언어의 공분산 입니다.
Paul Draper

14

약 20 년 전에 제네릭과 서브 타이핑을 결합하는 것에 대한 많은 연구가있었습니다. MIT의 Barbara Liskov 연구 그룹이 개발 한 Thor 프로그래밍 언어에는 매개 변수화 할 유형의 요구 사항을 지정할 수있는 "where"절이라는 개념이 있습니다. (이것은 C ++이 Concepts 와 관련이있는 것과 유사합니다 .)

Thor의 제네릭과 Thor의 하위 유형과 상호 작용하는 방법을 설명하는 논문은 다음과 같습니다. Day, M; 그루버 (Gruber), R; Liskov, B; Myers, AC : Subtypes vs. where 절 : 파라 메트릭 다형성 제한 , Obj-Oriented Prog에 대한 ACM Conf, Sys, Lang and Apps , (OOPSLA-10) : 156-158, 1995.

나는 그들이 1980 년대 후반 에메랄드에서 행한 일을 바탕으로했다고 믿는다. (저는 그 연구를 읽지 못했지만 참고 문헌은 Black, A; Hutchinson, N; Jul, E; Levy, H; Carter, L : Emerald의 배포 및 추상 유형 , _IEEE T. Software Eng., 13 ( 1) : 65-76, 1987.

Thor와 Emerald는 모두 "학문적 언어"였으므로 아마도 사람들이 where 절 (개념)이 실제 문제를 해결하는지 여부를 실제로 이해하기에는 충분한 사용법을 얻지 못했을 것입니다. C ++에서 Concepts에 대한 첫 번째 시도가 실패한 이유에 대한 Bjarne Stroustrup의 기사를 읽는 것이 흥미 롭습니다. Stroustrup, B : C ++ 0x "개념 제거"결정 , Dr Dobbs , 2009 년 7 월 22 일. ( Stroustrup의 홈 페이지 에 대한 추가 정보 . )

사람들이 시도하는 것처럼 보이는 또 다른 방향은 특성 이라고하는 것 입니다. 예를 들어 Mozilla의 Rust 프로그래밍 언어 는 특성을 사용합니다. 내가 이해하는 것처럼 (완전히 잘못되었을 수도 있음), 클래스가 특성을 만족 시킨다고 선언하는 것은 클래스가 인터페이스를 구현한다고 말하는 것과 매우 비슷하지만 "is는"이 아니라 "처럼 행동한다"고 말하는 것입니다. Apple의 새로운 Swift 프로그래밍 언어는 비슷한 프로토콜 개념 을 사용하여 매개 변수에 대한 제약 조건을 generics에 지정 하는 것 같습니다 .

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