함수형 프로그래밍에서 실존 형이 나쁜 관행으로 간주되는 이유는 무엇입니까? 데 사용됩니다 (또는 내 이해도 마찬가지입니다). 누구든지

존재 유형에 대한 의존성을 제거하면서 코드를 일관되게 리팩토링하는 데 사용할 수있는 기술은 무엇입니까? 일반적으로 이들은 원하는 유형의 원하지 않는 구성을 실격 처리하고 주어진 유형에 대한 최소한의 지식으로 소비를 허용하는 데 사용됩니다 (또는 내 이해도 마찬가지입니다).

누구든지 코드의 일부에 대한 의존을 제거하여 여전히 이점 중 일부를 유지하는 간단한 일관된 방법을 생각해 보셨습니까? 또는 변경에 대처하기 위해 상당한 코드 변동을 요구하지 않고 제거 할 수있는 추상화에서 미끄러지는 방법이 있습니까?

실존 유형에 대한 자세한 내용은 여기 ( “만약 있다면”)를 참조하십시오.



답변

함수형 프로그래밍에서 실존 형은 실제로 나쁜 습관으로 간주되지 않습니다. 나는 당신에게 걸려 넘어지는 것은 실존에 가장 일반적으로 인용되는 용도 중 하나는 실존 유형 클래스 반 패턴 이며, 많은 사람들이 나쁜 습관이라고 생각합니다.

이 패턴은 종종 동일한 유형 클래스를 구현하는 유형이 다른 유형의 요소 목록을 갖는 방법에 대한 질문으로 트로트됩니다. 예를 들어, 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)]

이와 같은 코드의 문제점은 다음과 같습니다.

  1. 에서 수행 할 수있는 유용한 작업 AnyShape은 해당 영역을 얻는 것입니다.
  2. 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합니다. 원과 사각형을 Shapes 로 변환하는 함수를 작성합니다 .


그렇다고 실존 유형이 문제가되는 것은 아닙니다! 예를 들어, Rust에는 사람들이 종종 특성에 대한 실존 유형으로 설명하는 특성 오브젝트 라는 특성이 있습니다 (Rust의 유형 클래스 유형). Haskell에서 실존 형 클래스가 반 패턴이라면 Rust가 잘못된 솔루션을 선택했음을 의미합니까? 아니! 하스켈 세계의 동기는 실제로 원칙이 아니라 구문과 편의성에 관한 것입니다.

이 퍼팅의 더 수학적 방법은 지적되고 AnyShape위의 유형 Double이다 동형 저기가 그들 사이에 “무손실 변환”(소수점 정밀도 부동 저장 잘)이다 :

forward :: AnyShape -> Double
forward = area

backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))

엄밀히 말하면, 당신은 하나를 선택하여 다른 힘을 얻거나 잃지 않습니다. 이는 사용 편의성 또는 성능과 같은 다른 요소를 기반으로 선택해야 함을 의미합니다.


그리고 존재 유형은 이기종 목록 예제 이외의 다른 용도로 사용되므로 그 유형을 갖는 것이 좋습니다. 예를 들어, ST외부 적으로 순수하지만 내부적으로 메모리 변이 연산을 사용하는 함수를 작성할 수있는 Haskell 타입은 컴파일 타임에 안전성을 보장하기 위해 실존 타입에 기반한 기술을 사용합니다.

따라서 일반적인 답변은 일반적인 답변이 없다는 것입니다. 실존 유형의 사용은 상황에 따라 판단 될 수 있으며 응답은 언어에 따라 제공되는 기능과 구문에 따라 다를 수 있습니다.


답변

저는 Haskell에 대해 잘 알고 있지 않으므로 학문적이지 않은 C # 개발자로서 질문의 일반적인 부분에 답변하려고합니다.

약간의 독서를 한 후에, 그것은 밝혀졌습니다 :

  1. Java 와일드 카드는 실존 유형과 유사합니다.

    스칼라의 실존 형과 자바의 와일드 카드의 차이점

  2. 와일드 카드는 C #에서 완전히 구현되지 않습니다. 일반 분산은 지원되지만 콜 사이트 분산은 지원되지 않습니다.

    C # 제네릭 : 와일드 카드

  3. 이 기능이 매일 필요하지는 않지만 필요할 때 느낄 수 있습니다 (예 : 작동하도록 추가 유형을 도입해야 함).

    C # 일반 제약 조건의 와일드 카드

이 정보를 기반으로 실존 유형 / 와일드 카드는 올바르게 구현할 때 유용하며 그 자체로는 아무런 문제가 없지만 다른 언어 기능과 마찬가지로 오용 될 수 있습니다.


답변