API та функціональне програмування


15

З мого (правда, обмеженого) впливу функціональних мов програмування, таких як Clojure, здається, що інкапсуляція даних відіграє менш важливу роль. Зазвичай різні типові типи, такі як карти або набори, є бажаною валютою представлення даних над об'єктами. Крім того, ці дані, як правило, незмінні.

Наприклад, ось одна з найвідоміших цитат «Rich Hickey of Clojure», в інтерв’ю з цього приводу :

Фогус: Слідуючи цій ідеї, деякі люди дивуються тому, що Clojure не займається інкапсуляцією прихованих даних щодо її типів. Чому ви вирішили відмовитися від приховування даних?

Хікі: Давайте будемо зрозуміти, що Clojure сильно підкреслює програмування на абстракції. Але в якийсь момент комусь потрібно буде мати доступ до даних. І якщо у вас є поняття "приватне", вам потрібні відповідні поняття привілей та довіри. А це додає цілу тону складності та малої вартості, створює жорсткість у системі та часто змушує речі жити там, де вони не повинні. Це на додаток до іншої втрати, яка виникає, коли проста класифікація кладеться в класи. Наскільки дані непорушні, мало шкоди може надати доступ, окрім того, що хтось може залежати від чогось, що може змінитися. Ну добре, люди роблять це весь час у реальному житті, і коли все змінюється, вони пристосовуються. І якщо вони раціональні, вони знають, коли вони приймають рішення, ґрунтуючись на чомусь, що може змінитись, що, можливо, в майбутньому їм потрібно буде адаптуватися. Отже, це рішення щодо управління ризиками, я думаю, що програмісти повинні вільно приймати. Якщо люди не мають сенсу бажати запрограмувати абстракції та бути обережними, коли вони будуть брати подробиці щодо впровадження, вони ніколи не стануть хорошими програмістами.

Походить із світу ОО, це, здається, ускладнює деякі закріплені принципи, які я навчився роками. До них можна віднести приховування інформації, закон про деметер та єдиний принцип доступу. Загальний потік, який полягає в тому, що інкапсуляція дозволяє нам визначити API для того, щоб інші знали, що вони повинні і чого не повинні торкатися. По суті, створення договору, який дозволяє обслуговуючому персоналу коду вільно вносити зміни та переробляти, не переживаючи про те, як це може ввести помилки в код споживача (принцип відкритого / закритого). Він також забезпечує чистий кураторний інтерфейс для інших програмістів, щоб знати, які інструменти вони можуть використовувати для отримання або надбудови цих даних.

Коли дозволено отримати доступ до даних, цей контракт API розірваний, і всі ці переваги щодо капсуляції, схоже, втрачаються. Крім того, здається, що суворо незмінні дані роблять проходження навколо доменних структур (об'єктів, структур, записів) набагато менш корисним у сенсі представлення стану та набору дій, які можна виконати в цьому стані.

Як функціональні кодові бази вирішують ці проблеми, які, здається, виникають, коли розмір кодової бази зростає величезним чином, що API потрібно визначити і багато розробників залучені до роботи з конкретними частинами системи? Чи є приклади такої ситуації, які демонструють, як це обробляється в таких типах кодових баз?


2
Ви можете визначити формальний інтерфейс без поняття об’єктів. Просто створіть функцію інтерфейсу, який їх документує. Не надайте документацію щодо детальної інформації про впровадження. Ви щойно створили інтерфейс.
Scara95

@ Scara95 Чи це не означає, що мені потрібно виконати роботу над тим, щоб реалізувати код для інтерфейсу та написати достатню документацію про нього, щоб попередити споживача, що робити, а що не робити? Що робити, якщо код зміниться, а документація застаріла? Зазвичай я віддаю перевагу коду самодокументування з цієї причини.
jameslk

Ви все одно повинні документувати інтерфейс.
Scara95

3
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.Не зовсім. Єдине, що змінюється - це те, що зміни закінчуються на новому об’єкті. Це величезна виграш, коли мова йде про міркування про код; передача змінних об'єктів навколо означає, що потрібно відслідковувати, хто може їх мутувати, проблема, яка збільшується з розміром коду.
Довал

Відповіді:


10

Перш за все, я перейду до коментарів Себастьяна щодо того, що є функціональним, що є динамічним набором тексту. Більш загально, Clojure - це один аромат із функціональних мов та спільноти, і ви не повинні занадто сильно узагальнювати його. Я зроблю кілька зауважень з точки зору ML / Haskell.

Як згадує Базіле, концепція контролю доступу існує в ML / Haskell і часто використовується. "Факторинг" трохи відрізняється від звичайних мов OOP; в OOP поняття класу одночасно грає роль типу та модуля , тоді як функціональні (і традиційні процедурні) мови трактують ці ортогонально.

Іншим моментом є те, що ML / Haskell дуже важкі для дженериків із стиранням типу, і що це може бути використано для надання іншого аромату "приховування інформації", ніж OOP капсулювання. Якщо компонент знає лише тип елемента даних як параметр типу, цей компонент може надійно передавати значення цього типу, і все ж йому буде заважати робити багато з ними, оскільки він не знає і не може знати їх конкретний тип ( instanceofна цих мовах не існує універсального чи періодичного мовлення). Цей запис у блозі - один із моїх улюблених вступних прикладів цих методів.

Далі: у світі FP дуже часто використовувати прозорі структури даних як інтерфейси до непрозорих / інкапсульованих компонентів. Наприклад, шаблони інтерпретатора дуже поширені на FP, де структури даних використовуються як синтаксичні дерева, що описують логіку, і подаються до коду, який "виконує" їх. Держава, правильно сказане, тоді існує ефемерно, коли інтерпретатор працює, що споживає структури даних. Також реалізація перекладача може змінюватися до тих пір, поки він все ще спілкується з клієнтами з точки зору однакових типів даних.

Останнє і найдовше: приховування / приховування інформації - це техніка , а не мета. Давайте подумаємо трохи про те, що це забезпечує. Інкапсуляція - це техніка узгодження контракту та реалізації програмного блоку. Типова ситуація така: впровадження системи визнає цінності або заявляє, що згідно з її договором не повинно існувати.

Ознайомившись із цим таким чином, ми можемо зазначити, що ПП надає, крім інкапсуляції, ще ряд додаткових інструментів, які можуть бути використані з цією ж метою:

  1. Незмінність як поширений дефолт. Ви можете передати прозорі значення даних коду сторонніх розробників. Вони не можуть змінювати їх і вводити їх у недійсні стани. (Відповідь Карла наголошує на цьому.)
  2. Системи складного типу з алгебраїчними типами даних, які дозволяють тонко контролювати структуру типів, не записуючи багато коду. Розумно використовуючи ці засоби, ви часто можете проектувати типи, де "погані стани" просто неможливі. (Слоган: "Зробити незаконні держави непредставними". ) Замість використання інкапсуляції для опосередкованого контролю набору допустимих станів класу, я скоріше просто скажу компілятору, що це, і це гарантуватиме їх для мене!
  3. Інтерпретаційний зразок, як уже згадувалося. Одним із ключових моментів для створення хорошого абстрактного типу синтаксичного дерева є:
    • Спробуйте спроектувати тип даних абстрактного синтаксичного дерева, щоб усі значення були "дійсними".
    • Якщо цього не зробити, змусіть інтерпретатора явно виявити недійсні комбінації та чисто відхилити їх.

Ця серія F # "Проектування з типами" робить досить пристойним читання з деяких із цих тем, зокрема №2. (Звідси походить посилання "зробити незаконні держави непредставними". Якщо ви уважно подивіться, ви зауважте, що у другій частині вони демонструють, як використовувати інкапсуляцію, щоб приховати конструктори та не допустити, щоб клієнти створювали недійсні екземпляри. Як я вже говорив вище, це є частиною інструментарію!


9

Я дійсно не можу завищувати ступінь, в якій незмінність викликає проблеми в програмному забезпеченні. Багато практик, які заграли нам в голову, компенсують проблеми, які викликають незмінність. Коли ви знімаєте незмінність, вам не потрібні такі практики.

Коли у вас є незмінність, ви знаєте, що ваша структура даних не буде зненацька змінюватися з-під вас під час виконання, тому ви можете створювати власні похідні структури даних для власного використання, додаючи функції до своєї програми. Початкова структура даних не повинна нічого знати про ці похідні структури даних.

Це означає, що ваші базові структури даних є надзвичайно стійкими. Нові структури даних начебто отримують від неї по краях по мірі необхідності. Це справді важко пояснити, поки ви не зробили важливу функціональну програму. Ви просто все менше піклуєтеся про конфіденційність і все більше і більше думаєте про створення міцних загальних структур публічних даних.


Хочеться додати одне, що незмінна змінна змушує програмістів дотримуватися розподіленої та розсіяної структури даних, якщо така структура взагалі є. Усі дані побудовані так, щоб створити логічну групу для зручного виявлення та подорожі, а не для транспортування. Це логічний прогрес, який ви досягнете, як тільки зробите достатньо функціонального програмування.
Xephon

8

Схильність Clojure до простого використання хешей та примітивів - це, на мою думку, не частина його функціональної спадщини, а частина її динамічної спадщини. Я бачив подібні тенденції в Python та Ruby (як об'єктно-орієнтовані, імперативні, так і динамічні, хоча обидві мають досить гарну підтримку функцій вищого порядку), але не в скажімо, Haskell (який має статичний тип, але чисто функціональний , зі спеціальними конструкціями, необхідними для уникнення незмінності).

Тож питання, яке вам потрібно задати, полягає не в тому, як функціональні мови обробляють великі API, а як динамічні мови. Відповідь: хороша документація та багато-багато одиничних тестів. На щастя, сучасні динамічні мови зазвичай мають дуже гарну підтримку для обох; наприклад, і Python, і Clojure мають можливість вбудовувати документацію в сам код, а не лише коментарі.


Щодо статично типових (чисто функціональних) мов, немає (простого) способу перенесення функції з типом даних, як в програмуванні OO. Тож документація має значення. Справа в тому, що вам не потрібна підтримка мови для визначення інтерфейсу.
Scara95

5
@ Scara95 Чи можете ви зрозуміти, що ви маєте на увазі під "виконувати функцію з типом даних"?
Себастьян Редл

6

Деякі функціональні мови дають можливість інкапсулювати або приховувати деталі реалізації в абстрактних типах даних та модулях .

Наприклад, OCaml має модулі, визначені колекцією названих абстрактних типів і значень (зокрема, функціонуючими на цих абстрактних типах). Тож у певному сенсі модулі Ocaml переробляють API. Ocaml також має функтори, які перетворюють деякі модулі в інші, забезпечуючи таким чином загальне програмування. Отже модулі є композиційними.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.