Из моего (по общему признанию ограниченного) знакомства с функциональными языками программирования, такими как Clojure, кажется, что инкапсуляция данных играет менее важную роль. Обычно различные нативные типы, такие как карты или наборы, являются предпочтительной валютой представления данных поверх объектов. Кроме того, эти данные обычно неизменны.
Например, вот одна из самых известных цитат из Rich Hickey из Clojure, в интервью по этому вопросу :
Fogus: Следуя этой идее, некоторые люди удивляются тому факту, что Clojure не занимается скрытием данных в своих типах. Почему вы решили отказаться от сокрытия данных?
Хикки: Давайте проясним, что Clojure сильно подчеркивает программирование на абстракции. Однако в какой-то момент кому-то понадобится доступ к данным. И если у вас есть понятие «частный», вам нужны соответствующие понятия о привилегиях и доверии. И это добавляет целую тонну сложности и небольшой ценности, создает жесткость в системе и часто заставляет вещи жить там, где им не следует. Это в дополнение к другой потере, которая происходит, когда простая информация помещается в классы. В той степени, в которой данные являются неизменяемыми, от предоставления доступа может быть мало вреда, кроме того, что кто-то может зависеть от того, что может измениться. Ну, ладно, люди делают это все время в реальной жизни, и когда все меняется, они адаптируются. И если они рациональны, они знают, когда принимают решение, основанное на чем-то, что может измениться, и что им может понадобиться адаптироваться в будущем. Итак, это решение по управлению рисками, которое, я думаю, программисты должны иметь право принимать. Если у людей нет чувства желания программировать на абстракции и опасаться сочетать детали реализации, то они никогда не станут хорошими программистами.
Исходя из мира ОО, это, кажется, усложняет некоторые из закрепленных принципов, которым я научился за эти годы. Среди них: сокрытие информации, закон Деметры и принцип единообразного доступа. Общий поток, заключающийся в том, что инкапсуляция позволяет нам определять API, чтобы другие знали, что им следует и чего не следует касаться. По сути, создание контракта, который позволяет сопровождающему некоторого кода свободно вносить изменения и рефакторинги, не беспокоясь о том, как он может вносить ошибки в код потребителя (принцип Open / Closed). Он также предоставляет чистый, кураторский интерфейс для других программистов, чтобы узнать, какие инструменты они могут использовать для получения или создания этих данных.
Когда к данным разрешен прямой доступ, этот контракт API нарушается, и все эти преимущества инкапсуляции, похоже, исчезают. Кроме того, строго неизменные данные, по-видимому, делают передачу вокруг доменных структур (объектов, структур, записей) гораздо менее полезной в смысле представления состояния и набора действий, которые могут быть выполнены в этом состоянии.
Как функциональные кодовые базы решают эти проблемы, которые, кажется, возникают, когда размер кодовой базы становится огромным, так что необходимо определить API-интерфейсы, а многие разработчики вовлечены в работу с конкретными частями системы? Существуют ли примеры такой ситуации, демонстрирующие, как это обрабатывается в кодовых базах такого типа?
Also, strictly immutable data seems to make passing around domain-specific structures (objects, structs, records) much less useful in the sense of representing a state and the set of actions that can be performed on that state.
На самом деле, нет. Единственное, что меняется, это то, что изменения заканчиваются на новом объекте. Это огромная победа, когда дело доходит до рассуждений о коде; Передача изменяемых объектов означает необходимость отслеживать, кто может их видоизменить, и эта проблема возрастает в зависимости от размера кода.Ответы:
Прежде всего, я перейду ко вторым комментариям Себастьяна о том, что функционально правильно, что такое динамическая типизация. В более общем смысле, Clojure - это разновидность функционального языка и сообщества, и вам не следует обобщать слишком много, основываясь на нем. Я сделаю несколько замечаний с точки зрения ML / Haskell.
Как упоминает Базиль, концепция управления доступом существует в ML / Haskell и часто используется. «Факторинг» немного отличается от традиционных языков ООП; в ООП понятие класса играет одновременно роль типа и модуля , тогда как функциональные (и традиционные процедурные) языки рассматривают их ортогонально.
Другой момент заключается в том, что ML / Haskell очень тяготеют к генерикам со стиранием типов, и что это может быть использовано для обеспечения другого вида «скрытия информации», чем инкапсуляция ООП. Когда компонент знает только тип элемента данных в качестве параметра типа, этому компоненту можно безопасно передавать значения этого типа, и тем не менее он будет лишен возможности делать с ними много, потому что он не знает и не может знать их конкретный тип (
instanceof
на этих языках нет универсального или динамического приведения). Эта запись в блоге является одним из моих любимых вводных примеров этих методов.Далее: в мире FP очень часто используются прозрачные структуры данных в качестве интерфейсов для непрозрачных / инкапсулированных компонентов. Например, шаблоны интерпретатора очень распространены в FP, где структуры данных используются в качестве синтаксических деревьев, которые описывают логику, и передаются в код, который их «выполняет». Правильно сказано, что состояние существует эфемерно, когда работает интерпретатор, который использует структуры данных. Также реализация интерпретатора может изменяться, пока он все еще связывается с клиентами в терминах одних и тех же типов данных.
Последнее и самое длинное: инкапсуляция / сокрытие информации - это техника , а не цель. Давайте немного подумаем о том, что он предоставляет. Инкапсуляция - это метод согласования контракта и реализации программного блока. Типичная ситуация такова: реализация системы допускает значения или состояния, которые, согласно ее контракту, не должны существовать.
Если вы посмотрите на это таким образом, мы можем указать, что FP предоставляет, помимо инкапсуляции, ряд дополнительных инструментов, которые можно использовать с той же целью:
Эта серия F # "Проектирование с типами" делает довольно приличное чтение по некоторым из этих тем, в частности, № 2. (Вот откуда взялась ссылка «сделать недопустимые недопустимые состояния состояний» выше.) Если вы посмотрите внимательно, вы заметите, что во второй части они демонстрируют, как использовать инкапсуляцию, чтобы скрыть конструкторы и не дать клиентам создавать недопустимые экземпляры. Как я уже говорил выше, это является частью инструментария!
источник
Я действительно не могу переоценить степень, в которой изменчивость вызывает проблемы в программном обеспечении. Многие практики, которые вбиваются в наши головы, компенсируют проблемы, которые вызывает изменчивость. Когда вы убираете изменчивость, вам не нужны такие практики.
Когда у вас есть неизменяемость, вы знаете, что ваша структура данных не изменится из-за вас неожиданно во время выполнения, поэтому вы можете создавать свои собственные производные структуры данных для своего собственного использования по мере добавления функций в вашу программу. Исходная структура данных не должна ничего знать об этих производных структурах данных.
Это означает, что ваши базовые структуры данных имеют тенденцию быть чрезвычайно стабильными. Новые структуры данных как бы извлекаются из этого по мере необходимости. Это действительно трудно объяснить, пока вы не сделали значительную функциональную программу. Вы просто все меньше и меньше заботитесь о конфиденциальности и все больше задумываетесь о создании надежных общих структур общих данных.
источник
Тенденция Clojure просто использовать хеши и примитивы, на мой взгляд, не является частью его функционального наследия, но является частью его динамического наследия. Я видел похожие тенденции в Python и Ruby (как объектно-ориентированные, так и императивные и динамические, хотя обе имеют довольно хорошую поддержку функций высшего порядка), но не в, скажем, Haskell (который статически типизирован, но чисто функциональный , со специальными конструкциями, необходимыми для избежания неизменности).
Поэтому вопрос, который вам нужно задать, заключается не в том, как функциональные языки обрабатывают большие API, а в том, как это делают динамические языки. Ответ: хорошая документация и много-много юнит-тестов. К счастью, современные динамические языки обычно имеют очень хорошую поддержку для обоих; например, и Python, и Clojure имеют способ встраивания документации в сам код, а не только в комментарии.
источник
Некоторые функциональные языки дают возможность инкапсулировать или скрывать детали реализации в абстрактных типах данных и модулях .
Например, в OCaml есть модули, определяемые набором именованных абстрактных типов и значений (особенно функций, работающих с этими абстрактными типами). Таким образом, в определенном смысле модули Ocaml являются API-интерфейсами. В Ocaml также есть функторы, которые превращают некоторые модули в другие, обеспечивая общее программирование. Так что модули являются композиционными.
источник