developer tip

OOP 인터페이스와 FP 유형 클래스의 차이점

copycodes 2020. 12. 14. 20:03
반응형

OOP 인터페이스와 FP 유형 클래스의 차이점


중복 가능성 :
Java의 인터페이스와 Haskell의 유형 클래스 : 차이점과 유사점?

Haskell을 배우기 시작했을 때 타입 클래스가 인터페이스와 다르고 더 강력하다는 말을 들었습니다.

1 년 후, 저는 인터페이스와 타입 클래스를 광범위하게 사용했고 아직 그들이 어떻게 다른지에 대한 예제 나 설명을 보지 못했습니다. 그것은 자연스럽게 오는 계시가 아니거나, 명백한 것을 놓쳤거나, 실제로 실제 차이가 없습니다.

인터넷 검색은 중요한 정보를 찾지 못했습니다. 그래서 답이 있습니까?


여러 각도에서 볼 수 있습니다. 다른 사람들은 동의하지 않을 것입니다.하지만 저는 OOP 인터페이스가 타입 클래스를 이해하기 시작하기에 좋은 곳이라고 생각합니다 (확실히 아무것도없는 것에서 시작하는 것과 비교).

사람들은 개념적으로 유형 클래스가 세트와 매우 유사하게 유형을 분류한다는 점을 지적하고 싶어합니다. "이러한 연산을 지원하는 유형 세트와 언어 자체에서 인코딩 할 수없는 다른 기대치"입니다. "특정 요구 사항을 충족하는 경우에만 유형을이 클래스의 인스턴스로 만드십시오"라고 말하면서 메소드없이 유형 클래스를 선언하는 것이 의미가 있으며 때때로 수행됩니다. 이것은 OOP 인터페이스 1 에서는 매우 드물게 발생합니다 .

구체적인 차이점과 관련하여 유형 클래스가 OOP 인터페이스보다 강력한 여러 가지 방법이 있습니다.

  • 가장 큰 것은 타입 클래스가 타입 자체의 선언에서 인터페이스를 구현한다는 선언을 분리한다는 것입니다. OOP 인터페이스를 사용하면 정의 할 때 유형이 구현하는 인터페이스를 나열하며 나중에 더 추가 할 방법이 없습니다. 타입 클래스를 사용하면 주어진 타입 "up the module hierarchy"가 구현할 수 있지만 알지 못하는 새로운 타입 클래스를 만들면 인스턴스 선언을 작성할 수 있습니다. 서로에 대해 모르는 별도의 타사의 유형과 유형 클래스가있는 경우 인스턴스 선언을 작성할 수 있습니다. OOP 인터페이스와 유사한 경우에, OOP 언어는 한계를 극복하기 위해 "디자인 패턴"(어댑터)을 발전 시켰지만 대부분은 막혀 있습니다.

  • 다음으로 큰 것 (물론 주관적입니다)은 개념적으로 OOP 인터페이스는 인터페이스를 구현하는 객체에서 호출 할 수있는 메서드의 묶음이지만 유형 클래스는 멤버 인 유형과 함께 사용할 수있는 메서드의 묶음이라는 것입니다. 수업의. 구별이 중요합니다. 형식 클래스 메서드는 개체가 아닌 형식을 참조하여 정의되기 때문에 형식의 여러 개체를 매개 변수 (동등 및 비교 연산자)로 사용하거나 결과로 형식의 개체를 반환하는 메서드를 갖는 데 장애가 없습니다 ( 다양한 산술 연산) 또는 유형의 상수 (최소 및 최대 경계). OOP 인터페이스는이를 수행 할 수 없으며 OOP 언어는 한계를 극복하기 위해 설계 패턴 (예 : 가상 복제 방법)을 발전 시켰습니다.

  • OOP 인터페이스는 유형에 대해서만 정의 할 수 있습니다. 유형 클래스는 "유형 생성자"라고하는 것에 대해 정의 할 수도 있습니다. 다양한 C 파생 OOP 언어에서 템플릿과 제네릭을 사용하여 정의 된 다양한 컬렉션 유형은 유형 생성자입니다. List는 유형 T을 인수로 취하고 유형 을 구성합니다 List<T>. 유형 클래스를 사용하면 유형 생성자에 대한 인터페이스를 선언 할 수 있습니다. 예를 들어, 컬렉션의 각 요소에 대해 제공된 함수를 호출하고 결과를 컬렉션의 새 복사본에서 수집하는 컬렉션 유형에 대한 매핑 작업-잠재적으로 다른 요소 유형을 사용합니다! 다시 말하지만 OOP 인터페이스로는이를 수행 할 수 없습니다.

  • 지정된 매개 변수가 여러 인터페이스를 구현해야하는 경우 유형 클래스를 사용하여 멤버가 될 인터페이스를 쉽게 나열 할 수 있습니다. OOP 인터페이스를 사용하면 주어진 포인터 또는 참조의 유형으로 단일 인터페이스 만 지정할 수 있습니다. 더 많은 것을 구현하기 위해 필요한 경우 유일한 옵션은 서명에 하나의 인터페이스를 작성하고 다른 인터페이스로 캐스팅하거나 각 인터페이스에 대해 별도의 매개 변수를 추가하고 동일한 객체를 가리 키도록 요구하는 것과 같이 매력적이지 않은 옵션입니다. 조상을 구현하기 때문에 유형이 새 인터페이스를 구현하는 것으로 자동으로 간주되지 않기 때문에 필요한 인터페이스를 상속하는 새 빈 인터페이스를 선언하여 해결할 수도 없습니다. (사실 이후에 구현을 선언 할 수 있다면 이것은 문제가되지 않을 것입니다.

  • 정렬 위의 하나의 역 케이스, 당신은 두 개의 매개 변수가 특정 인터페이스를 구현 유형을 가질 것을 요구할 수 그들이 동일한 유형의 것이. OOP 인터페이스를 사용하면 첫 번째 부분 만 지정할 수 있습니다.

  • 유형 클래스에 대한 인스턴스 선언이 더 유연합니다. OOP 인터페이스를 사용하면 "X 유형을 선언하고 인터페이스 Y를 구현합니다"라고만 말할 수 있습니다. 여기서 X와 Y는 구체적입니다. 유형 클래스를 사용하면 "요소 유형이 이러한 조건을 충족하는 모든 목록 유형은 Y의 멤버입니다"라고 말할 수 있습니다. (Haskell에서는 여러 가지 이유로 문제가 있지만 "X 및 Y의 구성원 인 모든 유형은 Z의 구성원이기도합니다"라고 말할 수도 있습니다.)

  • 소위 "수퍼 클래스 제약"은 단순한 인터페이스 상속보다 더 유연합니다. OOP 인터페이스를 사용하면 "유형이이 인터페이스를 구현하려면 이러한 다른 인터페이스도 구현해야합니다"라고만 말할 수 있습니다. 이는 유형 클래스에서도 가장 일반적인 경우이지만 수퍼 클래스 제약 조건을 사용하면 "SomeTypeConstructor는 그저 그런 인터페이스를 구현해야합니다."또는 "유형에 적용된이 유형 함수의 결과는 그저 그렇게 만족해야합니다. 제약 "등.

  • 이것은 현재 Haskell의 언어 확장 (유형 함수와 마찬가지로)이지만 여러 유형을 포함하는 유형 클래스를 선언 할 수 있습니다. 예를 들어, isomorphism 클래스 : 정보 손실없이 하나에서 다른 것으로 변환 할 수있는 유형 쌍의 클래스입니다. 다시 말하지만 OOP 인터페이스로는 불가능합니다.

  • 더 많은 것이 있다고 확신합니다.

제네릭을 추가 OOP 언어에서 그 지적이의 가치는 이러한 제한의 일부 (제 4, 제 5, 지울 수 있습니다 아마도 두 번째 점).

다른 한편으로는 OOP 인터페이스가 할 수있는 것과 타입 클래스가 기본적으로하지 않는 두 가지 중요한 일이 있습니다.

  • 런타임 동적 디스패치. OOP 언어에서는 인터페이스를 구현하는 객체에 대한 포인터를 전달하고 저장하고 런타임에 객체의 동적 런타임 유형에 따라 해결 될 메서드를 호출하는 것은 간단합니다. 대조적으로 타입 클래스 제약은 기본적으로 모두 컴파일 타임에 결정됩니다. 놀랍게도 대부분의 경우 이것이 필요한 전부입니다. 동적 디스패치를 ​​필요로하는 경우 존재 유형 (현재 Haskell의 언어 확장)이라고하는 것을 사용할 수 있습니다. 객체 유형이 무엇인지 "잊고"(귀하의 선택에 따라) 기억하는 구성 특정 유형 클래스 제약을 준수했습니다. 그 시점부터 기본적으로 OOP 언어로 인터페이스를 구현하는 객체에 대한 포인터 또는 참조와 똑같은 방식으로 작동합니다. 유형 클래스는이 영역에서 적자가 없습니다. (동일한 유형 클래스를 구현하는 두 개의 실존 정보와 해당 유형의 두 매개 변수가 필요한 유형 클래스 메서드가있는 경우 존재 여부를 알 수 없기 때문에 실존 정보를 매개 변수로 사용할 수 없습니다. 실존 적 유형은 동일하지만 처음에는 이러한 방법을 가질 수없는 OOP 언어에 비해 손실이 없습니다.)

  • Runtime casting of objects to interfaces. In OOP languages, you can take a pointer or reference at runtime and test whether it implements an interface, and "cast" it to that interface if it does. Type classes don't natively have anything equivalent (which is in some respects an advantage, because it preserves a property called parametricity, but I won't get into that here). Of course, there's nothing stopping you from adding a new type class (or augmenting an existing one) with methods to cast objects of the type to existentials of whichever type classes you want. (You can also implement such a capability more generically as a library, but it's considerably more involved. I plan to finish it and upload it to Hackage someday, I promise!)

I should point out that while you can do these things, many people consider emulating OOP that way bad style and suggest you use more straightforward solutions, such as explicit records of functions instead of type classes. With full first-class functions, that option is no less powerful.

Operationally, OOP interfaces are usually implemented by storing a pointer or pointers in the object itself which point to tables of function pointers for the interfaces the object implements. Type classes are usually implemented (for languages which do polymorphism-by-boxing, like Haskell, rather than polymorphism-by-multiinstantiation, like C++) by "dictionary passing": the compiler implicitly passes the pointer to the table of functions (and constants) as a hidden parameter to each function which uses the type class, and the function gets one copy no matter how many objects are involved (which is why you get to do the things mentioned in the second point above). The implementation of existential types looks a lot like what OOP languages do: a pointer to the type class dictionary is stored along with the object as "evidence" that the "forgotten" type is a member of it.

If you've ever read about the "concepts" proposal for C++ (as it was originally proposed for C++11), it's basically Haskell's type classes reimagined for C++'s templates. I sometimes think it would be nice to have a language which simply takes C++-with-concepts, rips out the object-oriented and virtual functions half of it, cleans up the syntax and other warts, and adds existential types for when you need runtime type-based dynamic dispatch. (Update: Rust is basically this, with many other nice things.)

1Serializable in Java is an interface without methods or fields and thus one of those rare occurrences.


I assume you are talking about Haskell type classes. It's not really the difference between interfaces and type classes. As the name states, a type class is just a class of types with a common set of functions (and associated types, if you enable the TypeFamilies extension).

However, Haskell's type system is in itself more powerful than, for example, C#'s type system. That allows you to write type classes in Haskell, which you can't express in C#. Even a type class as simple as Functor cannot be expressed in C#:

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

The problem with C# is that generics can't be generic themselves. In other words, in C# only types of kind * can be polymorphic. Haskell allows polymorphic type constructors, so types of any kind can be polymorphic.

This is the reason why many of the powerful generic functions in Haskell (mapM, liftA2, etc.) can't be expressed in most languages with a less powerful type system.


The main difference - which makes type-classes much more flexible than interfaces - is that type-classes are independent from its data types and can be added afterwards. Another difference (at least to Java) is that you can provide default implementations. An example:

//Java
public interface HasSize {
   public int size();
   public boolean isEmpty();
}

Having this interface is nice, but there is no way to add it to an existing class without changing it. If you are lucky, the class is non-final (say ArrayList), so you could write a subclass implementing the interface for it. If the class is final (say String) you are out of luck.

Compare this to Haskell. You can write the type-class:

--Haskell
class HasSize a where
  size :: a -> Int
  isEmpty :: a -> Bool
  isEmpty x = size x == 0

And you can add existing data types to the class without touching them:

instance HasSize [a] where
   size = length

Another nice property of type-classes is the implicit invocation. E.g. if you have a Comparator in Java, you need to pass it in as explicit value. In Haskell, the equivalent Ord can be used automatically, as soon as an appropriate instance is in scope.

참고URL : https://stackoverflow.com/questions/8122109/difference-between-oop-interfaces-and-fp-type-classes

반응형