타입 클래스와 객체 인터페이스


33

타입 클래스를 이해하지 못한다고 생각합니다. 나는 어딘가에서 타입 클래스를 "인터페이스"(OO에서 나온)라고 생각하는 타입이 구현하는 것이 잘못되고 오도된다는 것을 읽었습니다. 문제는, 나는 그것들을 다른 것으로보고 그것이 어떻게 잘못되었는지 보는 데 문제가 있다는 것입니다.

예를 들어 유형 클래스가있는 경우 (Haskell 구문)

class Functor f where
  fmap :: (a -> b) -> f a -> f b

[1] 인터페이스와 다른 점은 무엇입니까 (Java 구문)

interface Functor<A> {
  <B> Functor<B> fmap(Function<B, A> fn)
}

interface Function<Return, Argument> {
  Return apply(Argument arg);
}

내가 생각할 수있는 한 가지 차이점은 특정 호출에서 사용되는 유형 클래스 구현이 지정되지 않고 환경에서 결정된다는 것입니다. OO 언어로 해결할 수있는 구현 아티팩트 인 것 같습니다. 컴파일러 (또는 런타임)와 같이 유형에 필요한 인터페이스를 제공하는 래퍼 / 확장기 / 원숭이 패치기를 검색 할 수 있습니다.

내가 무엇을 놓치고 있습니까?

[1] f a인수가 fmapOO 언어이기 때문에 인수가 제거되었으므로 객체에서이 메소드를 호출합니다. 이 인터페이스는 f a인수가 수정되었다고 가정합니다 .

답변:


46

기본적으로 형식 클래스는 개체 인터페이스와 다소 유사합니다. 그러나 많은 점에서 훨씬 일반적입니다.

  1. 디스패치는 값이 아닌 유형에 있습니다. 이를 수행하는 데 값이 필요하지 않습니다. 예를 들어, Haskell의 Read클래스 와 같이 결과 유형의 함수에 디스패치를 ​​수행 할 수 있습니다 .

    class Read a where
      readsPrec :: Int -> String -> [(a, String)]
      ...
    

    이러한 파견은 기존의 OO에서는 분명히 불가능합니다.

  2. 유형 클래스는 단순히 여러 매개 변수를 제공하여 자연스럽게 여러 디스패치로 확장됩니다.

    class Mul a b c where
      (*) :: a -> b -> c
    
    instance Mul Int Int Int where ...
    instance Mul Int Vec Vec where ...
    instance Mul Vec Vec Int where ...
    
  3. 인스턴스 정의는 클래스 및 유형 정의와 독립적이므로 더 모듈화됩니다. 모듈 A의 유형 T는 단순히 모듈 M3에 인스턴스를 제공하여 정의를 수정하지 않고 모듈 M2에서 클래스 C로 개조 할 수 있습니다. OO에서는 확장 방법과 같은 좀 더 난해한 언어 기능이 필요합니다.

  4. 유형 클래스는 하위 유형이 아닌 파라 메트릭 다형성을 기반으로합니다. 보다 정확한 타이핑이 가능합니다. 예를 들어 고려

    pick :: Enum a => a -> a -> a
    pick x y = if fromEnum x == 0 then y else x
    

    vs.

    pick(x : Enum, y : Enum) : Enum = if x.fromEnum() == 0 then y else x
    

    전자의 경우 apply pick '\0' 'x'는 type Char이지만 후자의 경우 결과는 Enum이라는 것만 알 수 있습니다. (이것이 오늘날 대부분의 OO 언어가 파라 메트릭 다형성을 통합하는 이유이기도합니다.)

  5. 바이너리 방법의 문제와 밀접한 관련이 있습니다. 타입 클래스는 완전히 자연 스럽습니다.

    class Ord a where
      (<) :: a -> a -> Bool
      ...
    
    min :: Ord a => a -> a -> a
    min x y = if x < y then x else y
    

    서브 타이핑만으로는 Ord인터페이스를 표현할 수 없습니다. "F-bounded quantification"이라고하는 더 복잡하고 재귀적인 형태 또는 파라 메트릭 다형성이 필요합니다. Java Comparable와 그 사용법을 비교하십시오 .

    interface Comparable<T> {
      int compareTo(T y);
    };
    
    <T extends Comparable<T>> T min(T x, T y) {
      if (x.compareTo(y) < 0)
        return x;
      else
        return y;
    }
    

다른 한편으로, List<C>서브 타이핑 기반 인터페이스는 자연스럽게 이종 콜렉션을 형성 할 수 있습니다. 예를 들어, 유형 목록 에는 다양한 서브 타입을 가진 멤버가 포함될 수 있습니다 C(다운 캐스트를 사용하지 않고 정확한 유형을 복구 할 수는 없지만). 유형 클래스를 기반으로 동일한 작업을 수행하려면 추가 기능으로 실존 유형이 필요합니다.


아, 그건 말이됩니다. 유형 대 가치 기반 발송은 아마도 내가 제대로 생각하지 않은 큰 것일 것입니다. 파라 메트릭 다형성 및보다 구체적인 타이핑 문제가 의미가 있습니다. 방금 저것과 서브 타이핑 기반 인터페이스를 마음에 모았습니다 (명쾌하게 Java :-/ 생각합니다).
oconnor0

실재 유형 C은 다운 캐스트없이 하위 유형을 작성하는 것과 유사 합니까?
oconnor0

거의. 그것들은 타입 추상을 만드는 수단, 즉 표현을 숨기는 수단입니다. Haskell에서 클래스 제약 조건도 첨부하면 해당 클래스의 메서드를 계속 사용할 수 있지만 다른 것은 사용할 수 없습니다. -다운 캐스트는 실제로 서브 타이핑 및 실존 적 정량화와 별 개인 기능이며 원칙적으로 후자 존재시 추가 될 수 있습니다. 그것을 제공하지 않는 OO 언어가있는 것처럼.
Andreas Rossberg

추신 : FWIW, Java의 와일드 카드 유형은 실재 유형이지만 다소 제한적이며 임시적입니다 (이는 다소 혼란스러운 이유의 일부 일 수 있음).
Andreas Rossberg

1
@didierc, 정적으로 완전히 해결할 수있는 경우로 제한됩니다. 또한 타입 클래스를 일치시키기 위해서는 리턴 타입만으로 구별 할 수있는 오버로드 해상도 형식이 필요합니다 (항목 1 참조).
Andreas Rossberg

6

Andreas의 탁월한 답변 외에도 유형 클래스는 오버로드 를 간소화하기위한 것이므로 전역 네임 스페이스에 영향을 미칩니다. 유형 클래스를 통해 얻을 수있는 것 외에 Haskell에는 과부하가 없습니다. 반대로, 객체 인터페이스를 사용할 때는 해당 인터페이스의 인수를 취하도록 선언 된 함수 만 해당 인터페이스의 함수 이름에 대해 걱정해야합니다. 따라서 인터페이스는 로컬 네임 스페이스를 제공합니다.

예를 들어, fmap"Functor"라는 객체 인터페이스에 있었습니다 . fmap"Structor"와 같이 다른 인터페이스에 다른 인터페이스 를 두는 것이 좋습니다. 각 객체 (또는 클래스)는 구현할 인터페이스를 선택하고 선택할 수 있습니다. 반대로, Haskell에서는 fmap특정 컨텍스트 내에 하나만 가질 수 있습니다 . Functor 및 Structor 유형 클래스를 동일한 컨텍스트로 가져올 수 없습니다.

객체 인터페이스는 유형 클래스보다 표준 ML 서명과 더 유사합니다.


ML 모듈과 Haskell 타입 클래스 사이에는 밀접한 관계가있는 것 같습니다. cse.unsw.edu.au/~chak/papers/DHC07.html
Steven Shaw

1

구체적인 예 (Functor 유형 클래스 사용)에서 Haskell과 Java 구현은 다르게 동작합니다. 어쩌면 데이터 유형이 있고 Functor가 되길 원한다고 상상해보십시오 (하스켈에서 실제로 널리 사용되는 데이터 유형이므로 Java로 쉽게 구현할 수 있음). Java 예제에서는 Maybe 클래스가 Functor 인터페이스를 구현하도록합니다. 따라서 다음을 작성할 수 있습니다 (c # 배경 만 있기 때문에 의사 코드).

Maybe<Int> val = new Maybe<Int>(5);
Functor<Int> res = val.fmap(someFunctionHere);

주목하라 그 res유형 펑터, 아니 어쩌면있다. 따라서 구체적인 유형 정보를 잃어 버리고 캐스트해야하기 때문에 Java 구현을 거의 사용할 수 없습니다. (적어도 나는 유형이 여전히 존재하는 구현을 작성하지 못했습니다). Haskell 타입 클래스를 사용하면 Maybe Int를 얻게됩니다.


이 문제는 Java가 더 높은 종류의 유형을 지원하지 않고 Vs typeclasses 토론 인터페이스와 관련이 없다고 생각합니다. Java가 더 높은 종류를 가지고 있다면 fmap은 a를 잘 반환 할 수 Maybe<Int>있습니다.
dcastro
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.