Почему дискриминационные союзы связаны с функциональным программированием?

30

За многие годы ОО-программирования я понял, что такое дискриминационные союзы, но я никогда не пропускал их. Недавно я занимался некоторым функциональным программированием на C #, и теперь я продолжаю желать, чтобы они у меня были. Это сбивает с толку меня, потому что, на первый взгляд, концепция дискриминируемых союзов кажется совершенно независимой от дихотомии функционал / OO.

Есть ли в функциональном программировании что-то, что делает дискриминационные союзы более полезными, чем они были бы в ОО, или же, заставляя себя анализировать проблему «лучше», я просто поднял свои стандарты и теперь требую лучшего модель?

Энди
источник
Проблема выражения en.wikipedia.org/wiki/Expression_problem может быть актуальной
xji
Это не совсем правильный ответ на вопрос, поэтому я отвечу в качестве комментария, но язык программирования Цейлона имеет типы объединения, и они, по-видимому, проникают в другие языки OO / смешанной парадигмы - мне приходят в голову TypeScript и Scala. Также перечисления языка Java могут использоваться как своего рода реализация различающихся объединений.
Роланд

Ответы:

45

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

В чистом ОО различия в поведении должны определяться самими типами (объектами) и инкапсулироваться. Таким образом, эквивалентность сопоставлению с образцом будет заключаться в вызове одного метода для самого объекта, который затем перегружается рассматриваемыми подтипами для определения другого поведения. Проверка типа объекта извне (что и делает сопоставление с образцом) считается антипаттерном.

Принципиальное отличие состоит в том, что данные и поведение являются отдельными в функциональном программировании, тогда как данные и поведение объединяются в ОО.

Это историческая причина. Язык, подобный C #, развивается от классического ОО-языка к языку с множеством парадигм, добавляя все больше и больше функций.

JacquesB
источник
6
Различаемый тип union / sum не похож на дерево наследования или несколько классов, реализующих интерфейс; это один тип со многими видами значений. Они также не предотвращают инкапсуляцию; клиент не должен знать, что у вас есть несколько видов значений. Рассмотрим связанный список, который может быть либо пустым узлом, либо значением и ссылкой на другой узел. Без суммируемых типов вы в конечном итоге все равно будете хакировать различающееся объединение, имея переменные для значения и ссылки, но обрабатывая объект с нулевой ссылкой как пустой узел и притворяясь, что переменная значения не существует.
Довал
2
В общем, вы заканчиваете тем, что включаете переменные для всех возможных типов значений в одном классе, плюс какой-то флаг или перечисление, чтобы сказать вам, какое значение имеет этот экземпляр, и танцуете вокруг переменных, которые не соответствуют этому вид.
Довал
7
@Doval Существует «стандартное» кодирование алгебраического типа данных в классы с помощью интерфейса / абстрактного базового класса, представляющего тип в целом, и наличия подкласса для каждого случая типа. Для обработки сопоставления с образцом у вас есть метод, который принимает функцию для каждого случая, поэтому для списка, который вы бы имели на интерфейсе верхнего уровня, List<A>это метод B Match<B>(B nil, Func<A,List<A>,B> cons). Например, это именно тот шаблон, который Smalltalk использует для логических значений. Это также в основном то, как Scala справляется с этим. Использование нескольких классов является деталью реализации, которую не нужно раскрывать.
Дерек Элкинс
3
@Doval: Явная проверка на наличие нулевых ссылок также не считается идиоматической ОО.
JacquesB
@JacquesB Нулевая проверка - это деталь реализации. Для клиента связанного списка обычно есть isEmptyметод, который проверяет, является ли ссылка на следующий узел нулевой.
Довал
35

Запрограммировавшись на Паскале и в Аде, прежде чем изучать функциональное программирование, я не ассоциирую дискриминационные союзы с функциональным программированием.

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

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

AProgrammer
источник
7

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

  1. Преуспеть или бросить исключение,
  2. Вернуться, nullчтобы указать «нет значения» или сбой.

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

Дискриминационные союзы отвечают всем этим сложным типам. Например, в первом случае вы можете вернуть trueили какую-то структуру данных, описывающую сбой. Во втором случае - объединение, содержащее значение, или noneи nilт. Д. Во втором случае настолько распространено, что многие функциональные языки имеют встроенный тип типа «возможно» или «опция», представляющий это значение / нет объединения.

Переключаясь на функциональный стиль, например, с помощью C #, вы быстро обнаружите необходимость в этих составных типах. void/throwи nullпросто не чувствую себя хорошо с таким кодом. И дискриминационные союзы (DUs) хорошо отвечают всем требованиям. Таким образом, вы обнаружили, что хотите их, как и многие из нас.

Хорошая новость заключается в том, что существует множество библиотек, которые моделируют DU, например, в C # (посмотрите на мою собственную библиотеку Succinc <T>, например).

Дэвид Арно
источник
2

Типы суммы, как правило, будут менее полезны в основных языках ОО, поскольку они решают проблему, аналогичную типу ОО. Один из способов взглянуть на них состоит в том, что они оба обрабатывают подтипы, но OO - это openто, что можно добавить произвольные подтипы к родительскому типу, а типы сумм - closedто есть, кто заранее определяет, какие подтипы допустимы.

Теперь многие ОО-языки сочетают подтипы с другими понятиями, такими как унаследованные структуры, полиморфизм, типизация ссылок и т. Д., Чтобы сделать их в целом более полезными. Как следствие, они, как правило, требуют больше усилий для настройки (с классами и конструкторами и так далее), поэтому обычно их не используют для таких вещей, как Results и Options и так далее, пока универсальная типизация не станет обычной.

Я бы также сказал, что акцент на реальных отношениях, которые большинство людей изучило, когда они начали программирование ОО, например, Dog isa Animal, означал, что Integer isa Result или Error isa Result кажутся немного чуждыми. Хотя идеи довольно похожи.

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

Alex
источник
Кстати: у Scala закрытое наследство. sealedозначает «может быть расширен только в одном модуле компиляции», что позволяет закрывать набор подклассов во время разработки.
Йорг Миттаг,
-3

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

Поэтому предположение, что «дискриминационные союзы связаны с функциональным программированием» неверно.

gnasher729
источник