Які функціональні особливості варті невеликої плутанини OOP за користь, яку вони приносять?


13

Після вивчення функціонального програмування в Haskell і F #, парадигма OOP здається задньою назад класами, інтерфейсами, об'єктами. Які аспекти ПП я можу залучити до роботи, які можуть зрозуміти мої колеги? Чи варто будь-які стилі FP поговорити з моїм начальником про перепідготовку моєї команди, щоб ми могли їх використовувати?

Можливі аспекти ПП:

  • Незмінюваність
  • Часткове застосування та прокачування
  • Функції першого класу (вказівники функцій / функціональні об'єкти / шаблон стратегії)
  • Ледача оцінка (і монади)
  • Чисті функції (без побічних ефектів)
  • Вирази (проти тверджень - кожен рядок коду створює значення замість або на додаток до спричинення побічних ефектів)
  • Рекурсія
  • Узгодження шаблонів

Це безкоштовно для всіх, де ми можемо робити все, що підтримує мова програмування, до тієї міри, якою підтримує її мова? Або є краща настанова?


6
У мене був подібний досвід. Приблизно через 2 місяці болю я почав знаходити приємний баланс "речей, які відображаються на об'єкти" та "речей, які відображають функції". Це допомагає зробити серйозні злому мовою, яка підтримує обидва. Зрештою, і мої навички FP, і OOP значно покращуються
Daniel Gratzer

3
FWIW, Linq є функціональним і ледачим, і ви можете імітувати функціональне програмування в C #, використовуючи статичні методи і уникаючи стійкості стану.
Роберт Харві

1
У цей момент вам слід прочитати сіп . Це безкоштовно і добре написано. Він пропонує приємне порівняння між двома парадигмами.
Саймон Бергот

4
FP і OOP одночасно є певним ортогональним і в деякому сенсі подвійним. OOP - це про абстрагування даних, FP - про (відсутність) побічних ефектів. Наявність у вас побічних ефектів чи ні, є ортогональним для того, як ви абстрагуєте свої дані. Обчислення лямбда, як функціональне, так і об'єктно-орієнтоване, наприклад. Так, FP зазвичай використовує абстрактні типи даних, а не об'єкти, але ви також можете просто використовувати об'єкти, не будучи меншими FP. У OTOH також існує глибоке відношення: функція ізоморфна об'єкту лише одним методом (саме так вони «підроблені» в Java та реалізовані в Java8,…
Jörg W Mittag

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

Відповіді:


13

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

Всі ми навчені поводитися і нам комфортно з певним рівнем складності. Мені подобається називати цю граничну норму hrair людини (від Watership Down, наскільки високо ви можете порахувати). Це чудова річ, щоб розширити свою думку, вашу здатність розглянути більше варіантів і мати більше інструментів для підходу та вирішення проблем. Але це зміна, і воно виводить вас із зони комфорту.

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

Щасти!


3
Хочу додати, що можна краще зрозуміти деякі традиційні концепції OOP при роботі з функціональними мовами, такими як Haskell або Clojure. Особисто я зрозумів, наскільки поліморфізм - це дійсно важлива концепція (інтерфейси на Java або класи машинопису в Haskell), тоді як успадкування (те, що я вважав визначальним поняттям) - це якась дивна абстракція.
wirrbel

6

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

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

Моя улюблена комбінація може виглядати досить тривіально, але я вважаю, що вона має дуже високий вплив на продуктивність. Ця комбінація - це часткове застосування та функція currying та функцій першого класу, які я б повторно ніколи не писав for-loop знову : замість цього передавайте тіло циклу функції ітерації чи відображення. Мене нещодавно найняли на роботу на C ++, і я кумедно помітив, що я зовсім втратив звичку писати для петель!

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


2
Я повністю погоджуюся, але у мене були відштовхування від людей, які по всій галузі схильні до згоди: вони знають, що вони відвідують шаблон, вони бачили і використовували його багато разів, тому код у ньому - це те, що вони розуміють і знайомі, інший підхід, хоча смішно простіший і легший, є іноземним і, отже, складнішим для них. Це прикрий факт, що в галузі було 15+ років OOP, що потрапив у кожну програмістську голову, що 100+ рядків коду для них легше зрозуміти, ніж 10 просто тому, що вони запам'ятали ці 100 рядків після повторення їх протягом десятиліття
Джиммі Хоффа

1
-1 - Більш короткий код не означає, що ви пишете "менше" коду. Ви пишете один і той же код, використовуючи менше символів. Якщо що-небудь, ви робите більше помилок, оскільки код (часто) важче читати.
Теластин

8
@Telastyn: Terse - це не те, що читати. Також величезні маси подутих котлоагрегатів мають свій спосіб нечитабельності.
Майкл Шоу

1
@Telastyn Я думаю, що ви просто торкнулися справжньої суті тут, так лайф може бути поганим і нечитабельним, роздутий може бути поганим і нечитабельним, але ключ не змінної довжини та загадково написаного коду. Ключовим моментом є те, що ви згадуєте вище кількість операцій, я не погоджуюся з тим, що кількість операцій не співвідноситься з ремонтом, я думаю, що робити менше речей (з чітко написаним кодом) приносить користь читабельності та ремонтопридатності. Очевидно, що виконання однакової кількості речей з функцією однієї літери та імен змінних не допоможе, хорошому FP потрібно значно менше операцій, все одно чітко написано
Jimmy Hoffa

2
@ User949300: якщо ви хочете зовсім інший приклад, як про це Java 8 , наприклад?: list.forEach(System.out::println);З точки FP зору, printlnфункція приймає два аргументи, мета PrintStreamі значення , Objectа й Collection«s forEachметод очікує функції з одним аргументом тільки який можна застосувати до кожного елемента. Таким чином, перший аргумент пов'язаний з екземпляром, знайденим при System.outвступі до нової функції з одним аргументом. Це простіше, ніжBiConsumer<…> c=PrintStream::println; PrintStream a1=System.out; list.forEach(a2 -> c.accept(a1, a2));
Холгер

5

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

FP та OOP не є взаємовиключними. Подивіться на Скалу. Деякі вважають, що це найгірше, тому що це нечистий FP, але деякі вважають, що це найкраще саме з цієї причини.

Один за одним, ось деякі аспекти, які чудово працюють з OOP:

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

  • Незмінюваність: Рядок - це, мабуть, найпоширеніший об’єкт Java, і він непорушний. У своєму блозі я висвітлюю незмінні Java-об’єкти та незмінні колекції Java . Дещо з цього може бути застосовно до вас.

  • Функції першого класу (вказівники функцій / функціональні об'єкти / шаблон стратегії) - з версії 1.1 з Java версією 1.1 з більшості класів API (а їх є сотні), які реалізують інтерфейс слухача. Runnable - це, мабуть, найбільш часто використовуваний функціональний об'єкт. Функції першого класу - це більше роботи над кодуванням мовою, яка не підтримує їх на самому собі, але іноді варто докласти додаткових зусиль, коли вони спростять інші аспекти вашого коду.

  • Рекурсія корисна для обробки дерев. У магазині ООП це, мабуть, головне відповідне використання рекурсії. Використання рекурсії для розваги в OOP, ймовірно, слід придушувати, якщо з будь-якої іншої причини, крім більшості мов OOP, за замовчуванням немає місця для стеку, щоб зробити це гарною ідеєю.

  • Вирази (проти тверджень - кожен рядок коду створює значення замість або на додаток до спричинення побічних ефектів) - Єдиним оператором оцінювання в C, C ++ та Java є термінальний оператор . Я обговорюю відповідне використання у своєму блозі. Ви можете виявити, що ви пишете кілька простих функцій, які є багаторазовими і оціночними.

  • Ледача оцінка (і монади) - здебільшого обмежена лінивою ініціалізацією в ООП. Без мовних функцій, які б його підтримували, ви можете знайти деякі API, які є корисними, але написати самостійно складно. Максимально використовуйте потоки натомість - див. Приклади інтерфейсу Writer і Reader.

  • Часткове застосування та просівання - Не практично без функцій першого класу.

  • Відповідність шаблонів - як правило, не рекомендується використовувати OOP.

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


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

Що насправді турбує мене в C # зараз, це те, що я не можу використовувати делегати замість однометодних інтерфейсів з 2 простих причин: 1. ви не можете робити рекурсивні лямби без злому (призначаючи нуль спочатку, а потім - лямбда-секунду) або Y- комбінатор (що так само некрасиво, як пекло в C #). 2. Немає псевдонімів типів, які можна використовувати в рамках проекту, тому підписи ваших делегатів досить швидко стають неможливими. Тому просто з цих 2 дурних причин я більше не можу насолоджуватися C #, тому що єдине, що я можу змусити його працювати, - це використовувати однометодні інтерфейси, що є просто зайвою зайвою роботою.
Trident D'Gao

@bonomo Java 8 має загальний java.util.function.BiConsumer, який може бути корисним у C #: public interface BiConsumer<T, U> { public void accept(T t, U u); }Є інші корисні функціональні інтерфейси в java.util.function.
GlenPeterson

@bonomo Ей, я розумію, це біль Haskell. Кожен раз, коли ви читаєте, хтось каже, що "Навчання FP зробило мене кращим в OOP", це означає, що вони навчилися Рубі або щось таке, що не є чистим і декларативним, як Haskell. Haskell дає зрозуміти, що OOP - марно низький рівень. Найбільша біль, з якою ви стикаєтесь, полягає в тому, що умовивід типу на основі обмежень не визначається, коли ви не в системі типу HM, тому висновок на основі контрантаунта повністю не робиться: blogs.msdn.com/b/ericlippert/archive / 2012/03/09 /…
Джиммі Хоффа

1
Most OOP languages don't have the stack space for it Дійсно? Все, що вам знадобиться, це 30 рівнів рекурсії для управління мільярдами вузлів у збалансованому бінарному дереві. Я майже впевнений, що мій стек підходить для набагато більше рівнів, ніж цей.
Роберт Харві

3

Окрім функціонального програмування та об’єктно-орієнтованого програмування, є також декларативне програмування (SQL, XQuery). Вивчення кожного стилю допоможе вам отримати нові уявлення, і ви навчитесь правильно підбирати інструмент для роботи.

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


2

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

Коли я дізнався про додаткові та додаткові результати FP, я зрозумів, що мислять більше математично і отримав кращу перспективу щодо засобів, якими я пишу програмне забезпечення. Це мій особистий досвід.

По мірі отримання більшого досвіду, основні програми програмування втратити набагато складніше. Тому я пропоную вам полегшити програму FP, поки концепція OOP повністю не затвердіє у вашій свідомості. ПП - це певна зміна парадигми. Удачі!


4
Навчання OOP - це як навчитися повзати. Але як тільки ви станете ставати на ноги, ви будете вдаватися до повзання лише тоді, коли ви занадто п'яні. Звичайно, ви не можете забути, як це зробити, але зазвичай не хотіли б. І ходити з гусеницями буде болісно, ​​коли ви знаєте, що можете бігати.
SK-логіка

@ SK-логіка, мені подобається ваша метафора
Trident D'Gao

@ SK-Logic: Що таке навчання імперативного програмування? Тягнеш себе на животі?
Роберт Харві

@RobertHarvey намагається заритися під землю іржавою ложкою та колодою перфокарт.
Джиммі Хоффа

0

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

Якщо ви використовуєте C ++ 11, в мовній / стандартній бібліотеці існує багато таких функціональних функцій програмування, які добре синергують з OOP. Звичайно, я не впевнений, наскільки добре TMP буде прийнятий вашим начальником або колегами, але справа в тому, що ви можете отримати багато з цих функцій у тій чи іншій формі в нефункціональних мовах / OOP, як C ++.

Використання шаблонів з рекурсією часу компіляції покладається на ваші перші 3 бали,

  • Незмінюваність
  • Рекурсія
  • Узгодження шаблонів

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

Що стосується інших пунктів, то використання std::bindта надання std::functionчасткової функції застосуванню функцій та покажчиків функцій вбудовані в мову. Об'єкти, що викликаються, - це функціональні об'єкти (а також часткове застосування функції). Зауважте, що під об'єктами, що викликаються, я маю на увазі ті, що визначають їх operator ().

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

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

#include <iostream>
#include <functional>

//holds a compile-time index sequence
template<std::size_t ... >
struct index_seq
{};

//builds the index_seq<...> struct with the indices (boils down to compile-time indexing)
template<std::size_t N, std::size_t ... Seq>
struct gen_indices
  : gen_indices<N-1, N-1, Seq ... >
{};

template<std::size_t ... Seq>
struct gen_indices<0, Seq ... >
{
    typedef index_seq<Seq ... > type;
};


template <typename RType>
struct bind_to_fcn
{
    template <class Fcn, class ... Args>
    std::function<RType()> fcn_bind(Fcn fcn, std::tuple<Args...> params)
    {
        return bindFunc(typename gen_indices<sizeof...(Args)>::type(), fcn, params);
    }

    template<std::size_t ... Seq, class Fcn, class ... Args>
    std::function<RType()> bindFunc(index_seq<Seq...>, Fcn fcn, std::tuple<Args...> params)
    {
        return std::bind(fcn, std::get<Seq>(params) ...);
    }
};

//some arbitrary testing function to use
double foo(int x, float y, double z)
{
    return x + y + z;
}

int main(void)
{
    //some tuple of parameters to use in the function call
    std::tuple<int, float, double> t = std::make_tuple(1, 2.04, 0.1);                                                                                                                                                                                                      
    typedef double(*SumFcn)(int,float,double);

    bind_to_fcn<double> binder;
    auto other_fcn_obj = binder.fcn_bind<SumFcn>(foo, t);
    std::cout << other_fcn_obj() << std::endl;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.