Экзистенциальные типы на самом деле не считаются плохой практикой в функциональном программировании. Я думаю, что вас сбивает с толку то , что одним из наиболее часто упоминаемых вариантов использования экзистенциалов является антипаттерн экзистенциального типа , который многие считают плохой практикой.
Этот шаблон часто используется как ответ на вопрос о том, как получить список разнородно типизированных элементов, которые реализуют один и тот же класс типов. Например, вы можете захотеть иметь список значений, которые имеют 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 выбрал плохое решение? Нет! Мотивация в мире Haskell заключается в синтаксисе и удобстве, а не в принципе.
Более математический способ поместить это указывает на то , что AnyShape
тип сверху и Double
являются изоморфными -Есть «без потерь преобразования» между ними (ну, за исключением точности с плавающей запятой):
forward :: AnyShape -> Double
forward = area
backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))
Строго говоря, вы не получаете и не теряете никакой силы, выбирая одно против другого. Это означает, что выбор должен основываться на других факторах, таких как простота использования или производительность.
И имейте в виду, что у экзистенциальных типов есть другие применения вне этого примера гетерогенных списков, поэтому хорошо иметь их. Например, ST
тип Haskell , который позволяет нам писать функции, которые являются внешне чистыми, но внутренне используют операции мутации памяти, использует технику, основанную на экзистенциальных типах, чтобы гарантировать безопасность во время компиляции.
Таким образом, общий ответ заключается в том, что нет общего ответа. Об использовании экзистенциальных типов можно судить только по контексту, а ответы могут отличаться в зависимости от того, какие функции и синтаксис предоставляются разными языками.