Подтвердить что ты не робот

Почему алгебраические типы Haskell "закрыты"?

Исправьте меня, если я ошибаюсь, но похоже, что алгебраические типы данных в Haskell полезны во многих случаях, когда вы использовали бы классы и наследование в языках OO. Но есть большая разница: как только объявлен тип алгебраических данных, он не может быть расширен в другом месте. Закрыто". В OO вы можете расширить уже определенные классы. Например:

data Maybe a = Nothing | Just a

Невозможно каким-то образом добавить еще один вариант этого типа, не изменяя это объявление. Итак, каковы преимущества этой системы? Похоже, что способ OO был бы гораздо более расширяемым.

4b9b3361

Ответ 1

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

maybeToList :: Maybe a -> [a]
maybeToList Nothing  = []
maybeToList (Just x) = [x]

Если Maybe были открыты, кто-то мог добавить дополнительный конструктор, и функция maybeToList внезапно сломалась.

В OO это не проблема, когда вы используете наследование для расширения типа, потому что, когда вы вызываете функцию, для которой нет конкретной перегрузки, она может просто использовать реализацию для суперкласса. I.e., вы можете просто вызвать printPerson(Person p) с помощью объекта Student, если Student является подклассом Person.

В Haskell вы обычно используете классы инкапсуляции и типа, когда вам нужно расширять ваши типы. Например:

class Eq a where
   (==) :: a -> a -> Bool

instance Eq Bool where
  False == False = True
  False == True  = False
  True  == False = False
  True  == True  = True

instance Eq a => Eq [a] where
  []     == []     = True
  (x:xs) == (y:ys) = x == y && xs == ys
  _      == _      = False

Теперь функция == полностью открыта, вы можете добавить свои собственные типы, сделав ее экземпляром класса Eq.


Обратите внимание, что работа над идеей расширяемых типов данных, но это определенно не является частью Haskell.

Ответ 2

Ответ заключается в том, каким образом код легко распространяться, напряженность между классами и типами алгебраических данных, которые Фил Вадлер назвал "проблемой выражения":

  • С алгебраическими типами данных

    • Очень дешево добавить новую операцию на вещи: вы просто определяете новую функцию. Все старые функции на тех вещах продолжают работать без изменений.

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

  • С классами

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

    • Очень дорого добавить новую операцию для вещей: вам нужно добавить новое объявление метода в суперкласс и потенциально добавить определение метода для каждого существующего подкласса. На практике бремя варьируется в зависимости от метода.

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

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

Ответ 3

Если вы пишете функцию типа

maybeToList Nothing = []
maybeToList (Just x) = [x]

то вы знаете, что он никогда не создаст ошибку времени выполнения, потому что вы рассмотрели все случаи. Это перестает быть истинным, как только тип Maybe будет расширяемым. В тех случаях, когда вам нужен расширяемый тип (и они реже, чем вы думаете), каноническое решение Haskell должно использовать класс типа.

Ответ 4

Отметьте "Открыть типы данных и открытые функции" http://lambda-the-ultimate.org/node/1453

В объектно-ориентированных языках это легко расширить данные, определив новые классов, но трудно добавить новые функции. В функциональном языков, ситуация обратная: добавление новых функций не создает проблемы, но расширение данных (добавление новые конструкторы данных) изменение существующего кода. Проблема поддержки обоих направлений расширяемость известна как выражение. Мы представляем открытые типы данных и открытые функции как легкое решение выражения проблема на языке Хаскелла. Идея заключается в том, что конструкторы открытых данных типы и уравнения открытых функций могут появляться разбросанными по всему программа. В частности, они могут находятся в разных модулях. подразумеваемая семантика такова: программа должна вести себя так, как если бы данные типы и функции были закрыты, определенных в одном месте. Получатель чего-то функциональных уравнений определяется наилучшего соответствия шаблону, где конкретный шаблон имеет приоритет над неспецифический. Мы показываем, что наши решение применимо к проблема с выражением программирования и исключений. Эскиз две реализации. Простой, полученных из семантики, и один на основе взаимно рекурсивных модулей который допускает раздельную компиляцию.

Ответ 5

Во-первых, в качестве встречного ответа на вопрос Чарли это не является неотъемлемой частью функционального программирования. OCaml имеет концепцию открытых союзов или полиморфных вариантов, которые по существу делают то, что вы хотите.

Что касается того, почему, я считаю, этот выбор был сделан для Haskell, потому что

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

Итак, если вы предпочитаете тип data Color r b g = Red r | Blue b | Green g, его легко сделать, и вы можете легко заставить его действовать как монада или функтор, или же другие функции нуждаются.

Ответ 6

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

Невозможно каким-то образом добавить еще один вариант этого типа, не изменяя это объявление. Итак, каковы преимущества этой системы? Похоже, что способ OO был бы гораздо более расширяемым.

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

Преимущество закрытых союзов заключается в их исчерпывающей: если вы исправили все альтернативы во время компиляции, вы можете быть уверены, что не будет никаких непредвиденных случаев, которые ваш код не сможет обработать. Это ценное свойство во многих проблемных областях, например, в абстрактных синтаксических деревьях для языков. Если вы пишете компилятор, выражения языка попадают в предопределенный закрытый набор подкадров - вы не хотите, чтобы люди могли добавлять новые подслуги во время выполнения, которые ваш компилятор не понимает!

Фактически, АСТ-составители компилятора являются одним из классических примеров мотива Gang of Four для шаблона посетителей, который является копией ООП с закрытыми суммами и полным сопоставлением шаблонов. Поучительно задуматься о том, что программисты ОО разработали шаблон для восстановления закрытых сумм.

Аналогично, процедурные и функциональные программисты изобрели шаблоны для получения эффекта сумм. Самым простым является кодировка "запись функций", которая соответствует интерфейсам OO. Запись функций - это, фактически, таблица рассылки. (Обратите внимание, что программисты C используют этот метод целую вечность!) Фокус в том, что очень часто существует множество возможных функций данного типа - часто бесконечно много. Поэтому, если у вас есть тип записи, поля которого являются функциями, то это может легко поддерживать астрономически большой или бесконечный набор альтернатив. И что еще, поскольку записи создаются во время выполнения и могут выполняться гибко на основе условий выполнения, альтернативы опозданы.

Последний комментарий, который я сделал бы, заключается в том, что, по моему мнению, OO слишком много людей полагает, что расширяемость является синонимом позднего связывания (например, возможность добавления новых подкадров в тип во время выполнения), когда это просто обычно не является истиной. Позднее связывание одного метода для расширяемости. Другой метод - состав - создание сложных объектов из фиксированного словаря строительных блоков и правил их сборки. Словарь и правила идеально малы, но разработаны так, что у них есть богатые взаимодействия, которые позволяют создавать очень сложные вещи.

Функциональное программирование и, в частности, статично типизированные вкусы ML/Haskell - имеют длительный оттенок композиции по позднему связыванию. Но на самом деле оба метода существуют в обеих парадигмах и должны быть в наборе хорошего программиста.

Также стоит отметить, что сами языки программирования являются в основном примерами композиции. Язык программирования имеет, по-видимому, простой синтаксис, который позволяет объединять его элементы для записи любой возможной программы. (Это на самом деле восходит к примеру шаблонов компиляторов/посетителя выше и мотивирует его.)

Ответ 7

Другим (более или менее) интуитивным способом просмотра типов данных и классов типов по сравнению с объектно-ориентированными классами является следующее:

Класс Foo на языке OO представляет собой конкретный тип Foo, а также класс всех Foo-типов: те, которые прямо или косвенно получены из Foo.

В языках OO вы просто неявно программируете против класса Foo-типов, который позволяет вам "расширять" Foo.

Ответ 8

Хорошо, здесь, например, здесь вы понимаете, что "можно получить из" и не открывать в смысле Ruby и Smalltalk, где вы можете расширить класс с помощью новых методов во время выполнения, правильно?

В любом случае обратите внимание на две вещи: во-первых, на большинстве языков OO, которые основаны главным образом на наследовании, существует способ объявить класс, чтобы ограничить его способность наследоваться. Java имеет "final", и в С++ есть хаки для этого. Таким образом, это просто делает вариант по умолчанию на других языках OO.

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

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