Я знаю, что концепция инвариантов существует в нескольких парадигмах программирования. Например, инварианты цикла актуальны в ОО, функциональном и процедурном программировании.
Однако, один очень полезный вид, найденный в ООП, является инвариантом данных определенного типа. Это то, что я называю «инвариантами на основе типов» в заголовке. Например, Fraction
тип может иметь numerator
и denominator
с инвариантом, что их gcd всегда равен 1 (т.е. дробь находится в сокращенной форме). Я могу гарантировать это только наличием некоторой инкапсуляции типа, не позволяющей свободно устанавливать его данные. В свою очередь, мне никогда не нужно проверять, уменьшено ли оно, поэтому я могу упростить алгоритмы, такие как проверки на равенство.
С другой стороны, если я просто объявлю Fraction
тип без предоставления этой гарантии посредством инкапсуляции, я не смогу безопасно написать какие-либо функции для этого типа, которые предполагают, что дробь уменьшена, потому что в будущем кто-то еще может прийти и добавить способ получить неуменьшенную фракцию.
Как правило, отсутствие этого вида инварианта может привести к:
- Более сложные алгоритмы как предварительные условия должны быть проверены / обеспечены в нескольких местах
- СУХОЕ нарушение, поскольку эти повторяющиеся предварительные условия представляют одно и то же базовое знание (что инвариант должен быть верным)
- Необходимость принудительного выполнения предварительных условий из-за сбоев во время выполнения, а не гарантий во время компиляции
Так что мой вопрос в том, что ответ функционального программирования на этот вид инварианта. Существует ли функционально-идиоматический способ достижения более или менее одного и того же? Или есть какой-то аспект функционального программирования, который делает преимущества менее значимыми?
источник
PrimeNumber
класс. Было бы слишком дорого выполнять несколько избыточных проверок на простоту для каждой операции, но это не тот тип теста, который можно выполнить во время компиляции. (Многие операции, которые вы хотели бы выполнить с простыми числами, скажем, умножение, не образуют замыкание , т.е. результаты, вероятно, не гарантируются простыми числами . (Публикация в виде комментариев, поскольку я сам не знаю функционального программирования.)Ответы:
Некоторые функциональные языки, такие как OCaml, имеют встроенные механизмы для реализации абстрактных типов данных, что обеспечивает применение некоторых инвариантов . Языки, которые не имеют таких механизмов, полагаются на пользователя, «не заглядывающего под ковер» для обеспечения соблюдения инвариантов.
Абстрактные типы данных в OCaml
В OCaml модули используются для структурирования программы. Модуль имеет реализацию и сигнатуру , последний является своего рода сводкой значений и типов, определенных в модуле, тогда как первый предоставляет фактические определения. Это можно сравнить с диптихом,
.c/.h
знакомым программистам на Си.В качестве примера, мы можем реализовать
Fraction
модуль следующим образом:Это определение теперь можно использовать так:
Любой может получить значения типовой доли напрямую, минуя встроенную систему безопасности
Fraction.make
:Чтобы предотвратить это, можно скрыть конкретное определение типа
Fraction.t
:Единственный способ создать это
AbstractFraction.t
- использоватьAbstractFraction.make
функцию.Абстрактные типы данных в схеме
Язык Scheme не имеет такого же механизма абстрактных типов данных, как OCaml. Он полагается на пользователя, «не заглядывающего под ковер» для достижения инкапсуляции.
В Схеме обычно определяют предикаты, такие как
fraction?
распознавание значений, дающих возможность проверить входные данные. По моему опыту, доминирующее использование - позволить пользователю проверять свой ввод, если он подделывает значение, а не проверять ввод в каждом вызове библиотеки.Однако существует несколько стратегий для принудительного абстрагирования возвращаемых значений, например, возвращая замыкание, которое возвращает значение при применении, или возвращая ссылку на значение в пуле, управляемом библиотекой, - но я никогда не видел ни одного из них на практике.
источник
Инкапсуляция не является функцией, которая пришла с ООП. Любой язык, который поддерживает правильную модуляризацию, имеет это.
Вот примерно как вы это делаете на Хаскеле:
Теперь, чтобы создать Rational, вы используете функцию отношений, которая применяет инвариант. Поскольку данные неизменны, вы не сможете позже нарушить инвариант.
Однако это вам чего-то стоит: пользователь больше не может использовать ту же деконструирующую декларацию, что и знаменатель и числитель.
источник
Вы делаете это так же: создаете конструктор, который обеспечивает ограничение, и соглашаетесь использовать этот конструктор всякий раз, когда вы создаете новое значение.
Но, Карл, в ООП вам не нужно соглашаться на использование конструктора. Да неужели?
На самом деле, возможности для такого рода злоупотреблений в FP меньше. Вы должны поставить конструктор последним из-за неизменности. Я бы хотел, чтобы люди перестали думать о инкапсуляции как о некой защите от некомпетентных коллег или об устранении необходимости сообщать об ограничениях. Это не делает этого. Это просто ограничивает места, которые вы должны проверить. Хорошие программисты FP также используют инкапсуляцию. Это просто происходит в форме сообщения нескольких предпочтительных функций для внесения определенных изменений.
источник