Функціональне програмування порівняно з OOP з класами


32

Мене останнім часом цікавлять деякі концепції функціонального програмування. Я вже деякий час користуюсь OOP. Я бачу, як я створив би досить складний додаток в OOP. Кожен об’єкт знав би, як робити те, що робить об’єкт. Або все, що робить і клас батьків. Тому я можу просто сказати, Person().speak()щоб змусити людину розмовляти.

Але як я можу робити подібні речі у функціональному програмуванні? Я бачу, як функції є предметами першого класу. Але ця функція виконує лише одну конкретну річ. Чи просто у мене є say()метод, що плаває навколо, і називатиму його еквівалентним Person()аргументом, щоб я знав, що таке щось говорить?

Тож я бачу прості речі, як я можу зробити порівнянне з OOP та об'єктами у функціональному програмуванні, щоб я міг модулювати та організовувати свою кодову базу?

Для довідки, мій основний досвід роботи з OOP - це Python, PHP та деякі C #. Мови, на які я дивлюся, які мають функціональні особливості, - це Scala та Haskell. Хоча я схиляюся до Скали.

Основний приклад (Python):

Animal(object):
    def say(self, what):
        print(what)

Dog(Animal):
    def say(self, what):
        super().say('dog barks: {0}'.format(what))

Cat(Animal):
    def say(self, what):
        super().say('cat meows: {0}'.format(what))

dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')

Scala розроблений як OOP + FP, тому вам не доведеться вибирати
Karthik T

1
Так, я знаю, але я також хочу це знати з інтелектуальних причин. Я не можу знайти нічого еквівалентного об'єкта у функціональних мовах. Що стосується шкали, я все одно хотів би знати, коли / де / як я повинен використовувати функціонал над oop, але це IMHO - інше питання.
лижі

2
"Особливо перекреслено, ІМО - це поняття, що ми не підтримуємо державу.": Це неправильне поняття. Неправда, що FP не використовує стан, скоріше FP обробляє стан по-іншому (наприклад, монади в Haskell або унікальні типи в Clean).
Джорджіо


Відповіді:


21

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

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

Функціональні мови загалом пропонують різні варіанти досягнення поліморфізму:

  • Щось на зразок мультиметодів, які викликають іншу функцію на основі вивчення наданих аргументів. Це можна зробити за типом першого аргументу (який фактично дорівнює поведінці більшості мов OOP), але може бути зроблено і для інших атрибутів аргументів.
  • Прототипи / об'єктоподібні структури даних, які містять першокласні функції в якості членів . Таким чином, ви можете вбудувати функцію "сказати" всередині ваших структур даних про собаку та кота. Ви ефективно зробили кодову частину даних.
  • Зіставлення шаблонів - де логіка відповідності шаблонів вбудована у визначення функції та забезпечує різну поведінку для різних параметрів. Поширений у Хаскеллі.
  • Розгалуження / умови - еквівалентно умовам if / else в OOP. Це може бути не розширюваним, але все-таки може бути доречним у багатьох випадках, коли у вас обмежений набір можливих значень (наприклад, функція передала число чи рядок чи нуль?)

Як приклад, ось реалізація вашої проблеми Clojure за допомогою мультиметодів:

;; define a multimethod, that dispatched on the ":type" keyword
(defmulti say :type)  

;; define specific methods for each possible value of :type. You can add more later
(defmethod say :cat [animal what] (println (str "Car purrs: " what)))
(defmethod say :dog [animal what] (println (str "Dog barks: " what)))
(defmethod say :default [animal what] (println (str "Unknown noise: " what)))

(say {:type :dog} "ruff")
=> Dog barks: ruff

(say {:type :ape} "ook")
=> Unknown noise: ook

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


Не на 100% ясно, але достатньо, щоб побачити, куди йдеш. Я міг бачити це як код "тварини" у заданому файлі. Також хороша частина гілок / умов. Я не розглядав це як альтернативу if / else.
лижі

11

Це не пряма відповідь, і не обов'язково на 100% точна, оскільки я не є експертом з функціональних мов. Але в будь-якому випадку я поділюся з вами своїм досвідом ...

Близько року тому я був у подібному човні, як і ти. Я робив C ++ і C #, і всі мої дизайни завжди були дуже важкими для OOP. Я чув про мови FP, читав деяку інформацію в Інтернеті, перегортав книгу F #, але все ще не міг зрозуміти, як мова FP може замінити OOP або бути корисною загалом, оскільки більшість прикладів, які я бачив, були занадто простими.

Для мене «прорив» настав, коли я вирішив вивчити пітон. Я завантажив python, потім перейшов на головну сторінку проекту euler і просто почав робити одну проблему за іншою. Python не обов'язково є мовою FP, і ви, звичайно, можете створювати в ньому класи, але порівняно з C ++ / Java / C #, у нього є набагато більше конструкцій FP, тому коли я почав грати з ним, я прийняв свідоме рішення не робити визначити клас, якщо мені абсолютно не довелося.

Що мені було цікаво про Python, це те, як легко та природно було приймати функції та «зшивати» їх, щоб створити складніші функції, і врешті-решт ваша проблема все-таки була вирішена викликом однієї функції.

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

Однак ключовим фактором FP ​​є те, що у вас немає побічних ефектів. Поки ви ставитеся до програми як до простої трансформації даних із визначеним набором входів та набором виходів, ви можете написати FP-код, який би виконував те, що вам потрібно. Очевидно, не кожна програма прекрасно впишеться в цю форму, але, як тільки ви почнете робити це, ви здивуєтеся, скільки додатків підходить. І саме тут я думаю, що блищать Python, F # або Scala, тому що вони надають вам конструкції FP, але коли вам потрібно пам’ятати про свій стан і «вводити побічні ефекти», ви завжди можете повернутися до справжніх і випробуваних методів OOP.

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

Минулого тижня я почав кодувати в Java, і з того часу майже щодня мені нагадують, що коли в OOP мені доводиться реалізовувати інтерфейси, оголошуючи класи методами, які перекривають функції, в деяких випадках я можу досягти того ж в Python, використовуючи простий лямбда-вираз, наприклад, 20-30 рядків коду, який я написав для сканування каталогу, склав би 1-2 рядки в Python і ніяких класів.

FP самі є мовами вищого рівня. У Python (вибачте, мій єдиний досвід FP) я міг би зібрати розуміння списку всередині іншого розуміння списку з лямбдами та іншими речами, кинутими в них, і все це було б лише 3-4 рядки коду. У мові C ++ я міг би абсолютно виконати те саме, але оскільки C ++ є нижчим рівнем, мені доведеться написати набагато більше коду, ніж 3-4 рядки, і оскільки кількість рядків збільшується, моє навчання SRP розпочнеться, і я би почав думаючи про те, як розділити код на більш дрібні частини (тобто більше функцій). Але в інтересах ремонтопридатності та приховування деталей реалізації я б перемістив усі ці функції в один клас і зробив їх приватними. І ось у вас це ... Я щойно створив клас, тоді як у python я б написав "return (.... lambda x: .. ....)"


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

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

І Рубі ... Одна з його філософій дизайну зосереджена на методах, що приймають блок коду як аргумент, як правило, необов'язковий. А зважаючи на чисте синтаксичне мислення та кодування таким чином легко. Важко подумати і скласти подібне у C #. C # write функціонально є багатослівним і дезорієнтуючим, він відчуває себе вбраним мовою. Мені подобається, що Рубі допомогла легше мислити, бачити потенціал у моїй думці C #. У підсумковому аналізі я бачу функціональний та ОО як доповнюючий; Я б сказав, що Рубі, безумовно, так вважає.
radarbob

8

У Haskell найближче у вас є "клас". Цей клас, хоч і не такий, як клас у Java та C ++ , буде працювати для того, що ви хочете в цьому випадку.

У вашому випадку так виглядатиме ваш код.

клас Тварина а де 
сказати :: Рядок -> звук 

Тоді ви можете мати окремі типи даних, адаптуючи ці методи.

наприклад, Animal Dog де
скажіть s = "кора" ++ s 

EDIT: - Перш ніж ви зможете спеціалізуватися сказати для Dog, вам потрібно сказати системі, що собака - тварина.

дані собака = \ - щось тут - \ (похідна тварина)

EDIT: - Для Wilq.
Тепер, якщо ви хочете використовувати функцію say у функції say foo, вам доведеться сказати haskell, що foo може працювати лише з Animal.

foo :: (Animal a) => a -> String -> String
foo a str = сказати str 

Тепер, якщо ви подзвоните foo з собакою, він буде гавкати, якщо ви подзвоните з котом, він буде мявок.

головний = робити 
нехай d = dog (\ - параметри cstr - \)
    c = кіт  
в шоу $ foo d "Hello World"

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


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

nitpick Тварина має бути використана з великої літери
Daniel Gratzer

1
Як функція say знає, що ви називаєте її на Собаці, якщо вона займає лише String? І не "похідне" лише для деяких вбудованих класів?
WilQu

6

Функціональні мови використовують 2 конструкції для досягнення поліморфізму:

  • Функції першого порядку
  • Джерела

Створення поліморфного коду з ними зовсім інше, ніж те, як OOP використовує успадкування та віртуальні методи. Хоча обидва з них можуть бути доступними на вашій улюбленій мові OOP (наприклад, C #), більшість функціональних мов (як Haskell) розбивають її до одинадцяти. Він рідко функціонує як негенеричний, і більшість функцій мають функції параметрів.

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


2
OOP - все про поліморфізм. Якщо ви думаєте, що OOP пов'язаний з функціями, пов'язаними з вашими даними, ви нічого не знаєте про OOP.
Ейфорія

4
поліморфізм - це лише один аспект ООП, і я думаю, що не той, про який насправді задають ОП.
Док Браун

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

1
@ErikReppen Якщо "застосувати інтерфейс" не потрібно часто, то ви не робите OOP. Також у Haskell є модулі.
Ейфорія

1
Вам не завжди потрібен інтерфейс. Але вони дуже корисні, коли вам це потрібно. ІМО - ще одна важлива частина ООП. Що стосується модулів, що надходять у Haskell, я думаю, що це, мабуть, найближче до OOP за функціональними мовами, що стосується організації коду. Принаймні з того, що я читав досі. Я знаю, вони все ще дуже різні.
лижі

0

це дійсно залежить від того, що ви хочете досягти.

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

def bark(what):
    print "barks: {0}".format(what) 

def meow(what):
    print "meows: {0}".format(what)

def climb(how):
    print "climbs: {0}".format(how)

if __name__ == "__main__":
    animals = {'dog': {'say': bark},
               'cat': {'say': meow,
                       'climb': climb}}
    animals['dog']['say']("ruff")
    animals['cat']['say']("purr")
    animals['cat']['climb']("well")

зауважте, що (а) " собачих випадків" немає чи кота, і (b) вам доведеться самостійно відстежувати "тип" своїх об'єктів.

як, наприклад: pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]. тоді ви могли б зробити таке розуміння списку[animals[pet[1]]['say'](pet[2]) for pet in pets]


0

Мови OO можуть використовуватися замість мов низького рівня, іноді для взаємодії безпосередньо з машиною. C ++ Напевно, але навіть для C # є адаптери і такі. Хоча письмовий код для керування механічними деталями та щохвилинного контролю над пам’яттю найкраще зберігати якомога ближче до низького рівня. Але якщо це питання пов'язане з поточним об'єктно-орієнтованим програмним забезпеченням, таким як Line Of Business, веб-додатки, IOT, веб-сервіси та більшість масово використовуваних додатків, то ...

Відповідь, якщо це застосовується

Читачі можуть спробувати працювати з сервісно-орієнтованою архітектурою (SOA). Тобто DDD, N-шаруваті, N-багаторівневі, шестикутні, як би там не було. Я не бачив, щоб програма для великого бізнесу ефективно використовувала "Традиційні" OO (Active-Record або Rich-Models), як це було описано в 70-х і 80-х дуже в останнє десятиліття +. (Див. Примітку 1)

Вина не в ОП, але є кілька проблем із питанням.

  1. Наведений вами приклад - просто продемонструвати поліморфізм, це не виробничий код. Іноді приклади, точно такі, що приймаються буквально.

  2. У FP та SOA дані відокремлюються від Business Logic. Тобто дані та логіка не йдуть разом. Логіка переходить у службу, а дані (доменні моделі) не мають поліморфної поведінки (див. Примітку 2).

  3. Послуги та функції можуть бути поліморфними. На FP ви часто передаєте функції як параметри іншим функціям замість значень. Ви можете зробити те ж саме в OO Languages ​​з такими типами, як Callable або Func, але це не розповсюджується (Див. Примітку 3). У FP та SOA ваші моделі не є поліморфними, а лише вашими послугами / функціями. (Див. Примітку 4)

  4. У цьому прикладі є поганий випадок жорсткого кодування. Я говорю не лише про червону кольорову рядок «собачий гавкіт». Я також говорю про самих CatModel та DogModel. Що станеться, коли ви хочете додати Овець? Вам потрібно зайти в свій код і створити новий код? Чому? У виробничому коді я б швидше бачив просто AnimalModel з його властивостями. У гіршому випадку, AmphibianModel і FowlModel, якщо їх властивості та поводження настільки різні.

Це я б очікував побачити в поточній мові "ОО":

public class Animal
{
    public int AnimalID { get; set; }
    public int LegCount { get; set; }
    public string Name { get; set; }
    public string WhatISay { get; set; }
}

public class AnimalService : IManageAnimals
{
    private IPersistAnimals _animalRepo;
    public AnimalService(IPersistAnimals animalRepo) { _animalRepo = animalRepo; }

    public List<Animal> GetAnimals() => _animalRepo.GetAnimals();

    public string WhatDoISay(Animal animal)
    {
        if (!string.IsNullOrWhiteSpace(animal.WhatISay))
            return animal.WhatISay;

        return _animalRepo.GetAnimalNoise(animal.AnimalID);
    }
}

Основний потік

Як перейти від класів в ОО до функціонального програмування? Як казали інші; Можна, але насправді це не так. Суть вищезазначеного полягає в тому, щоб продемонструвати, що ви навіть не повинні використовувати Класи (у традиційному розумінні світу) під час виконання Java та C #. Як тільки ви перейдете до написання коду в сервісно-орієнтованій архітектурі (DDD, багатошаровий, багаторівневий, шестикутний, що завгодно), ви будете на крок ближче до функціоналу, оскільки ви відокремлюєте свої дані (доменні моделі) від своїх логічних функцій (послуг).

Мова ОО на крок ближче до ПП

Ви можете взяти це трохи далі і розділити служби SOA на два типи.

Необов’язковий тип 1 класу : загальні послуги, що реалізують інтерфейс для вхідних точок. Це будуть "нечисті" точки вступу, які можуть викликати "чистий" або "нечистий" інший функціонал. Це можуть бути ваші вхідні точки з API RESTful.

Необов'язковий тип 2 : Чисті бізнес-логічні послуги. Це статичні класи, які мають "чистий" функціонал. У ФП «Чистий» означає, що немає побічних ефектів. Він прямо не встановлює Держави чи Персистенції ніде. (Див. Примітку 5)

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

Примітки

Примітка 1 : Оригінальний об'єктно-орієнтований дизайн "Rich" або "Active-Record" все ще існує. З'явилося багато такого спадкового коду, як колись, коли люди «робили це правильно» десять років і більше тому. Востаннє я бачив, що такий код (зроблено правильно) був із відеоігри Codebase в C ++, де вони точно контролювали пам’ять і мали дуже обмежений простір. Не кажучи про архітектури, орієнтовані на FP та сервісні послуги, це звіри і не повинні враховувати обладнання. Але вони надають можливість постійно змінюватись, підтримуватися, мати змінний розмір даних та інші аспекти як пріоритетні. У відеоіграх та AI машин ви дуже точно керуєте сигналами та даними.

Примітка 2 : Моделі доменів не мають поліморфної поведінки, а також не мають зовнішніх залежностей. Вони "Ізольовані". Це не означає, що вони повинні бути на 100% анемічними. Вони можуть мати багато логіки, пов'язаної з їх конструкцією та зміною властивостей, що змінюються, якщо такі стосуються. Дивіться DDD "Ціннісні об'єкти" та об'єкти Еріка Еванса та Марка Семана.

Примітка 3 : Лінки та Ламбда дуже поширені. Але коли користувач створює нову функцію, він рідко використовує функцію або функцію Callable, тоді як на FP було б дивно бачити додаток без функцій, що слідують за цим шаблоном.

Примітка 4 : Не плутати поліморфізм із спадщиною. CatModel може успадкувати AnimalBase, щоб визначити, які властивості тварина зазвичай має. Але як я показую, подібні моделі - це кодовий запах . Якщо ви бачите цю схему, можете подумати про її розбиття та перетворення в дані.

Примітка 5 : Чисті функції можуть (і дійсно) приймати функції як параметри. Вхідна функція може бути нечистою, але може бути чистою. Для тестування це завжди було б чисто. Але у виробництві, хоча це трапляється як чисте, воно може містити побічні ефекти. Це не змінює той факт, що чиста функція є чистою. Хоча функція параметра може бути нечистою. Не плутати! : D


-2

Ви можете зробити щось подібне .. php

    function say($whostosay)
    {
        if($whostosay == 'cat')
        {
             return 'purr';
        }elseif($whostosay == 'dog'){
             return 'bark';
        }else{
             //do something with errors....
        }
     }

     function speak($whostosay)
     {
          return $whostosay .'\'s '.say($whostosay);
     }
     echo speak('cat');
     >>>cat's purr
     echo speak('dog');
     >>>dogs's bark

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

1
Але передане поняття близьке до відповідності шаблонів, що використовується у функціональному програмуванні, тобто $whostosayстає типом об'єкта, який визначає, що виконується. Наведене вище може бути змінено, щоб додатково прийняти інший параметр, $whattosayщоб тип, який його підтримує (наприклад 'human'), міг використовувати його.
syockit
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.