Як суто функціональні мови обробляють модульність?


23

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

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

Нещодавно я переходжу до функціонального програмування, тому я почав задаватися питанням:
як чисто функціональні мови обробляють такі речі? Тобто мови без будь-якого поняття класів та об’єктів.


1
Чому ви вважаєте, що функціонал не означає класи? Деякі з FRIST класів прийшли з LISP - CLOS Подивіться на Clojure просторів імен і типів або модулів і Haskell .

Я посилався на функціональні мови, на яких НЕ мають занять, я дуже добре знаю мало хто з них
електрична кава

1
Наприклад, Caml, його сестринська мова OCaml додає об'єкти, але сам Caml їх не має.
Електрична кава

12
Термін "суто функціональний" відноситься до функціональних мов, які підтримують референтну прозорість і не пов'язані з тим, чи має мова якісь об'єктно-орієнтовані функції.
sepp2k

2
Торт - брехня, повторне використання коду в ОО набагато складніше, ніж у FP. Незважаючи на те, що OO протягом багатьох років вимагає повторного використання коду, я бачив, як це відбувається як мінімум разів. (не соромтесь сказати, що я повинен робити це неправильно. Мені подобається, наскільки я добре пишу код ОО, маючи роками розробляти та підтримувати системи ОО, я знаю якість власних результатів)
Джиммі Хоффа,

Відповіді:


19

Багато функціональних мов мають модульну систему. (До речі, багато об'єктно-орієнтованих мов теж.) Але навіть за відсутності такої, ви можете використовувати функції як модулі.

JavaScript - хороший приклад. У JavaScript функції використовуються як для реалізації модулів, так і для об'єктно-орієнтованого капсулювання. У схемі, яка була головним натхненником JavaScript, є лише функції. Функції використовуються для реалізації майже всього: об'єктів, модулів (які називаються одиницями в Racket), навіть структур даних.

OTOH, Haskell та сімейство ML мають явну модульну систему.

Об'єктна орієнтація стосується абстрагування даних. Це воно. Модульність, успадкування, поліморфізм, навіть стан, що змінюється, є ортогональними питаннями.


8
Чи можете ви пояснити, як ці речі працюють трохи детальніше стосовно oop? Замість того, щоб просто заявити, що поняття існують ...
Електрична кава

Sidenote - Модулі та одиниці - це дві різні конструкції в Racket - модулі можна порівняти з просторами імен, а одиниці - на півдорозі між просторами імен та інтерфейсами OO. Документи розглядають детальніше про відмінності
Джек

@Jack: Я не знав, що Ракетка також має концепцію під назвою module. Я думаю, що прикро, що у Racket є концепція, яка називається moduleне модулем, а концепцією, яка є модулем, але не називається module. У всякому разі, ви писали: "одиниці є на півдорозі між просторами імен та інтерфейсами ОО". Ну хіба це не таке визначення, що таке модуль?
Йорг W Міттаг

Модулі та одиниці - це обидва групи імен, прив'язаних до значень. Модулі можуть мати залежності від інших конкретних наборів прив’язок, тоді як одиниці можуть мати залежність від деякого загального набору прив’язок , який має надати будь-який інший код, який використовує пристрій. Одиниці параметризуються за прив’язки, модулі - ні. Модуль, що залежить від прив'язки, mapі одиниця, що залежить від прив'язки map, відрізняються тим, що модуль повинен посилатися на певне специфічне mapзв'язування, наприклад, на одне з racket/base, тоді як різні користувачі блоку можуть давати різні визначення mapдля одиниці.
Джек

4

Схоже, ви задаєте два питання: "Як можна досягти модульності у функціональних мовах?" що розглядалося в інших відповідях і "як можна створити абстракції на функціональних мовах?" на що я відповім.

У мовах ОО ви схильні зосереджуватися на іменнику "тварина", "поштовий сервер", "його садова виделка" тощо. Функціональні мови, навпаки, підкреслюють дієслово, "ходити", "забирати пошту" , "продавати" тощо.

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

Ще один конкретний приклад цього - проект, над яким я працював недавно. Я писав базу даних в Haskell. У мене була одна «вбудована мова» для визначення операцій на найнижчому рівні; наприклад, це дозволило мені писати та читати речі із носія інформації. У мене була ще одна окрема "вбудована мова" для визначення операцій на найвищому рівні. Тоді я мав, що по суті є перекладачем, для перетворення операцій з вищого рівня на нижчий.

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


4

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

Статично типізовані мови OOP традиційно використовують номінальне підтипування, що означає, що код, призначений для класу / модуля / інтерфейсу A, може мати справу з класом / модулем / інтерфейсом B лише тоді, коли B явно згадує А. Мови в сім'ї функціонального програмування в основному використовують структурну підтипізацію, що означає цей код, призначений для A, може обробляти B, коли B має всі методи та / або поля A. B могла бути створена іншою командою до того, як виникла потреба у більш загальному класі / інтерфейсі A. Наприклад, в OCaml, структурне підтипування стосується модульної системи, об'єктоподібної системи об'єктів та її цілком унікальних типів поліморфних варіантів.

Найбільш помітна різниця між OOP і FP wrt. модульність полягає в тому, що за замовчуванням "одиниця" в пакетах OOP разом як об'єкт проводить різні операції над одним і тим же випадком значень, тоді як "одиниця" за замовчуванням у FP-зв'язках поєднується як функція тієї ж операції для різних випадків значень. У FP все ще дуже просто поєднати операції разом, наприклад, як модулі. (BTW, ні Haskell, ні F # не мають повноцінної системи модулів сімейства ML.) Проблема вираження- це завдання поступового додавання як нових операцій, що працюють над усіма значеннями (наприклад, приєднання нового методу до існуючих об'єктів), так і нових випадків значень, які повинні підтримувати всі операції (наприклад, додавання нового класу з тим же інтерфейсом). Як було обговорено в першій лекції Ральфа Лаеммеля (яка має широкі приклади на C #), додавання нових операцій є проблематичним для мов OOP.

Поєднання OOP та FP у Scala може зробити його однією з найпотужніших мов Wrt. модульність. Але OCaml все ще є моєю улюбленою мовою, і в моїй особистій, суб'єктивній думці вона не підходить до Scala. Дві лекції Ральфа Леммеля нижче обговорюють рішення проблеми експресії в Haskell. Я вважаю, що це рішення, хоча і ідеально працює, ускладнює використання отриманих даних з параметричним поліморфізмом. Вирішення проблеми експресії з поліморфними варіантами в OCaml, пояснене в статті Jaques Garrigue, зв'язаній нижче, не має цього недоліку. Я також посилаюся на розділи підручника, які порівнюють використання модулів модулів OOP та OOP в OCaml.

Нижче наведені специфічні для Haskell та OCaml посилання на проблему вираження :


2
Ви б не хотіли пояснити докладніше, чим займаються ці ресурси, і чому ви рекомендуєте їх відповідати на поставлене запитання? "Відповіді лише на посилання" не дуже вітаються на Stack Exchange
gnat

2
Я тільки що подав фактичну відповідь, а не просто посилання, як редагування.
lukstafi

0

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

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

Інший вид повторного використання коду - це створення нових структур даних для роботи над існуючими функціями. Це обробляється на функціональних мовах за допомогою функцій, таких як дженерики та класи типів. Наприклад, клас типу Orke Haskell дозволяє використовувати sortфункцію будь-якого типу з Ordекземпляром. Екземпляри легко створити, якщо вони ще не існують.

Візьміть свій Animalприклад і розгляньте можливість використання функції годування. Безпосередня реалізація OOP полягає у підтримці колекції Animalоб'єктів та проходженні їх через усі, викликаючи feedметод на кожному з них.

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

У FP немає схильності поля Animalзавжди залишатися згрупованими разом, що має деякі цікаві наслідки для повторного використання. Скажімо , у вас є список Animalзаписів, з такими областями , як name, species, location, food type, food amountі т.д. У вас також є список FoodStoreзаписів з полями , як location, food typeі food amount.

Першим кроком у годівлі може бути зіставлення кожного з цих списків записів до списків (food amount, food type)пар із негативними цифрами для кількості тварин. Потім ви можете створити функції, щоб робити всілякі речі з цими парами, як, наприклад, підсумовувати кількість продуктів кожного типу. Ці функції не належать до абсолютно або Animalабо в FoodStoreмодулі, але вельми багаторазові обома.

У вас з'явилася купа функцій, які роблять корисні речі [(Num A, Eq B)]для багаторазового використання та модульної роботи, але у вас виникають труднощі з розумінням, куди їх розмістити або як назвати їх як групу. Ефект полягає в тому, що модулі FP важче класифікувати, але класифікація набагато менш важлива.


-1

Одне з популярних рішень - розбити код на модулі, ось як це робиться в JavaScript:

    media.podcast = (function(name) {
    var fileExtension = 'mp3';        

     function determineFileExtension() {
         console.log('File extension is of type ' + fileExtension);
     }

     return {
         download: function(episode) {
            console.log('Downloading ' + episode + ' of ' + name);
            determineFileExtension();
        }
    }    
}('Astronomy podcast'));

Стаття повністю пояснити цю модель в JavaScript , крім цього є безліч інших способів визначити модуль, наприклад RequireJS , CommonJS , Google Closure. Інший приклад - Erlang, де у вас є і модулі, і поведінка, які застосовують API і шаблон, граючи схожу роль як інтерфейси в OOP.

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