Екзистенціальні типи насправді не вважаються поганою практикою функціонального програмування. Я думаю, що вас спонукає те, що одне з найбільш часто цитованих застосувань для екзистенціалів - це екзистенційний антикласичний тип , який багато хто вважає поганою практикою.
Ця закономірність часто формулюється як відповідь на питання про те, як скласти список різнорідно набраних елементів, які реалізують один і той же тип класу. Наприклад, ви можете мати список значень, що мають 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 вони мають особливість, яку називають об'єктами ознаки, яку люди часто описують як екзистенціальний тип над ознакою (версії Руста про класи класів). Якщо екзистенційні типи класів є антипатерном у Хаскеллі, чи це означає, що Руст вибрав погане рішення? Немає! Мотивація в світі Хаскелла полягає в синтаксисі та зручності, а не в принципі.
Більш математичним способом цього є вказівка на те, що AnyShape
тип зверху Double
є ізоморфним - між ними існує "конверсія без втрат" (ну, крім економії для точності з плаваючою комою):
forward :: AnyShape -> Double
forward = area
backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))
Отже, строго кажучи, ви не отримуєте і не втрачаєте жодної сили, обираючи один проти іншого. Що означає, що вибір повинен базуватися на інших чинниках, таких як зручність використання та продуктивність.
І майте на увазі, що екзистенціальні типи мають інші види використання поза цим прикладом неоднорідних списків, тому їх добре мати. Наприклад, ST
тип Haskell , який дозволяє нам записувати функції, які є зовні чистими, але внутрішньо використовують операції мутації пам'яті, використовує техніку, засновану на екзистенційних типах, щоб гарантувати безпеку під час компіляції.
Тож загальна відповідь полягає в тому, що загальної відповіді немає. Про використання екзистенціальних типів можна судити лише в контексті, і відповіді можуть бути різними залежно від того, які особливості та синтаксис надаються різними мовами.