Почему (или почему нет) экзистенциальные типы считаются плохой практикой в ​​функциональном программировании?

43

Какие методы я мог бы использовать для последовательного рефакторинга кода, удаляя зависимость от экзистенциальных типов? Как правило, они используются, чтобы дисквалифицировать нежелательные конструкции вашего типа, а также разрешить потребление с минимальными знаниями о данном типе (или мое понимание таково).

Кто-нибудь придумал простой последовательный способ убрать зависимость от них в коде, который все еще сохраняет некоторые из преимуществ? Или, по крайней мере, какие-нибудь способы проскальзывания в абстракции, которые позволяют их удаление, не требуя значительного оттока кода, чтобы справиться с изменением?

Вы можете прочитать больше о экзистенциальных типах здесь («если вы смеете ..»).

Петр Пудлак
источник
8
@RobertHarvey Я нашел сообщение в блоге, которое впервые заставило меня задуматься над этим вопросом: Haskell Antipattern: Existential Typeclass . Также в статье, упоминаемой Джои Адамсом, описаны некоторые проблемы с экзистенциалами в разделе 3.1. Если у вас есть противоположные аргументы, пожалуйста, поделитесь ими.
Петр Пудлак
5
@ PetrPudlák: Имейте в виду, что в антипаттерне вообще существуют не экзистенциальные типы, а конкретное их использование, когда что-то более простое и более простое (и лучше поддерживаемое в Haskell) сделало бы ту же работу.
CA Макканн
10
Если вы хотите знать, почему автор определенного сообщения в блоге высказал свое мнение, автор, которого вы, вероятно, должны спросить, является автором.
Эрик Липперт
6
@Ptolemy Это вопрос мнения. Используя Haskell в течение многих лет, я не могу себе представить, чтобы использовать функциональный язык, который не имеет сильной системы типов.
Петр Пудлак
4
@Ptolemy: мантра Хаскелла - «если он компилирует, то работает», мантра Эрланга - «пусть она рухнет». Динамическая типизация хороша как клей, но я лично не стал бы создавать вещи, используя только клей.
Ден

Ответы:

8

Экзистенциальные типы на самом деле не считаются плохой практикой в ​​функциональном программировании. Я думаю, что вас сбивает с толку то , что одним из наиболее часто упоминаемых вариантов использования экзистенциалов является антипаттерн экзистенциального типа , который многие считают плохой практикой.

Этот шаблон часто используется как ответ на вопрос о том, как получить список разнородно типизированных элементов, которые реализуют один и тот же класс типов. Например, вы можете захотеть иметь список значений, которые имеют 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 выбрал плохое решение? Нет! Мотивация в мире Haskell заключается в синтаксисе и удобстве, а не в принципе.

Более математический способ поместить это указывает на то , что AnyShapeтип сверху и Doubleявляются изоморфными -Есть «без потерь преобразования» между ними (ну, за исключением точности с плавающей запятой):

forward :: AnyShape -> Double
forward = area

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

Строго говоря, вы не получаете и не теряете никакой силы, выбирая одно против другого. Это означает, что выбор должен основываться на других факторах, таких как простота использования или производительность.


И имейте в виду, что у экзистенциальных типов есть другие применения вне этого примера гетерогенных списков, поэтому хорошо иметь их. Например, STтип Haskell , который позволяет нам писать функции, которые являются внешне чистыми, но внутренне используют операции мутации памяти, использует технику, основанную на экзистенциальных типах, чтобы гарантировать безопасность во время компиляции.

Таким образом, общий ответ заключается в том, что нет общего ответа. Об использовании экзистенциальных типов можно судить только по контексту, а ответы могут отличаться в зависимости от того, какие функции и синтаксис предоставляются разными языками.

sacundim
источник
Это не изоморфизм.
Райан Райх
2

Я не слишком знаком с Haskell, поэтому я попытаюсь ответить на общую часть вопроса как разработчик не # академического функционала на C #.

После некоторого чтения выясняется, что:

  1. Подстановочные знаки Java похожи на экзистенциальные типы:

    Разница между экзистенциальными типами Scala и подстановочным символом Java на примере

  2. Подстановочные знаки не полностью реализованы в C #: общая дисперсия поддерживается, но не вызывать дисперсию сайта:

    C # Generics: Подстановочные знаки

  3. Вам может не понадобиться эта функция каждый день, но когда вы это сделаете, вы почувствуете ее (например, необходимость ввести дополнительный тип, чтобы заставить вещи работать):

    Подстановочные знаки в общих ограничениях C #

Основываясь на этой информации, экзистенциальные типы / подстановочные знаки полезны при правильной реализации, и в них нет ничего плохого как такового, но их, вероятно, можно использовать не так, как другие языковые функции.

логово
источник