함수형 프로그래밍에서 실존 형은 실제로 나쁜 습관으로 간주되지 않습니다. 나는 당신에게 걸려 넘어지는 것은 실존에 가장 일반적으로 인용되는 용도 중 하나는 실존 유형 클래스 반 패턴 이며, 많은 사람들이 나쁜 습관이라고 생각합니다.
이 패턴은 종종 동일한 유형 클래스를 구현하는 유형이 다른 유형의 요소 목록을 갖는 방법에 대한 질문으로 트로트됩니다. 예를 들어, Show
인스턴스 가있는 값 목록을 원할 수 있습니다 .
{-# LANGUAGE ExistentialTypes #-}
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
area (AnyShape x) = area x
example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]
이와 같은 코드의 문제점은 다음과 같습니다.
- 에서 수행 할 수있는 유용한 작업
AnyShape
은 해당 영역을 얻는 것입니다.
AnyShape
셰이프 유형 중 하나를 유형으로 가져 오려면 여전히 생성자 를 사용해야합니다 AnyShape
.
결과적으로, 그 코드 조각은이 짧은 코드가 가지고 있지 않은 것을 실제로 얻지 못합니다.
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]
다중 메서드 클래스의 경우와 같은 Shape
형식 클래스를 사용하는 대신 "메서드 레코드"인코딩을 사용하여 동일한 효과를보다 간단하게 얻을 수 있습니다. 필드가 형식의 "방법"인 레코드 형식을 정의 Shape
합니다. 원과 사각형을 Shape
s 로 변환하는 함수를 작성합니다 .
그렇다고 실존 유형이 문제가되는 것은 아닙니다! 예를 들어, Rust에는 사람들이 종종 특성에 대한 실존 유형으로 설명하는 특성 오브젝트 라는 특성이 있습니다 (Rust의 유형 클래스 유형). Haskell에서 실존 형 클래스가 반 패턴이라면 Rust가 잘못된 솔루션을 선택했음을 의미합니까? 아니! 하스켈 세계의 동기는 실제로 원칙이 아니라 구문과 편의성에 관한 것입니다.
이 퍼팅의 더 수학적 방법은 지적되고 AnyShape
위의 유형 Double
이다 동형 저기가 그들 사이에 "무손실 변환"(소수점 정밀도 부동 저장 잘)이다 :
forward :: AnyShape -> Double
forward = area
backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))
엄밀히 말하면, 당신은 하나를 선택하여 다른 힘을 얻거나 잃지 않습니다. 이는 사용 편의성 또는 성능과 같은 다른 요소를 기반으로 선택해야 함을 의미합니다.
그리고 존재 유형은 이기종 목록 예제 이외의 다른 용도로 사용되므로 그 유형을 갖는 것이 좋습니다. 예를 들어, ST
외부 적으로 순수하지만 내부적으로 메모리 변이 연산을 사용하는 함수를 작성할 수있는 Haskell 타입은 컴파일 타임에 안전성을 보장하기 위해 실존 타입에 기반한 기술을 사용합니다.
따라서 일반적인 답변은 일반적인 답변이 없다는 것입니다. 실존 유형의 사용은 상황에 따라 판단 될 수 있으며 응답은 언어에 따라 제공되는 기능과 구문에 따라 다를 수 있습니다.