Основне правило роботи полягає в тому, що в програмуванні функції FP виконують таку ж роботу, як і об'єкти в OO-програмуванні. Ви можете зателефонувати на їхні методи (ну, так само "метод" виклику), і вони відповідатимуть згідно з деякими інкапсульованими внутрішніми правилами. Зокрема, кожна пристойна мова FP дозволяє вам мати "змінні екземпляри" у своїй функції із закриттям / лексичним визначенням.
var make_OO_style_counter = function(){
return {
counter: 0
increment: function(){
this.counter += 1
return this.counter;
}
}
};
var make_FP_style_counter = function(){
var counter = 0;
return fucntion(){
counter += 1
return counter;
}
};
Тепер наступне питання - що ви розумієте під інтерфейсом? Один із підходів - це використання номінальних інтерфейсів (він відповідає інтерфейсу, якщо він говорить, що це робить) - цей зазвичай багато в чому залежить від того, якою мовою ви користуєтеся, тому дозволяє залишити його для останнього. Інший спосіб визначення інтерфейсу - це структурний спосіб, бачачи, які параметри річ отримує та повертає. Це такий інтерфейс, який ви зазвичай бачите в динамічних мовах, набраних качками, і він дуже добре вписується у всі FP: інтерфейс - це лише типи вхідних параметрів для наших функцій та типи, які вони повертають, тому всі функції відповідають правильні типи підходять до інтерфейсу!
Тому найпростіший спосіб представлення об'єкта, що відповідає інтерфейсу, - це просто мати групу функцій. Зазвичай ви долаєте некрасивість передачі функцій окремо, упаковуючи їх у якийсь запис:
var my_blarfable = {
get_name: function(){ ... },
set_name: function(){ ... },
get_id: function(){ ... }
}
do_something(my_blarfable)
Використання голих функцій або записів функцій пройде довгий шлях у вирішенні більшості ваших поширених проблем "знежиреним" способом без тон котла. Якщо вам потрібно щось досконаліше, інколи мови надають додаткові можливості. Одним із прикладів, про які згадували люди, є класи типу Haskell. Класи типу по суті асоціюють тип з однією з цих записів функцій і дозволяють писати речі, щоб словники неявні і автоматично переходили до внутрішніх функцій.
-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
blarg_name :: String,
blarg_id :: Integer
}
do_something :: BlargDict -> IO()
do_something blarg_dict = do
print (blarg_name blarg_dict)
print (blarg_id blarg_dict)
-- Typeclass version
class Blargable a where
blag_name :: a -> String
blag_id :: a -> String
do_something :: Blargable a => a -> IO
do_something blarg = do
print (blarg_name blarg)
print (blarg_id blarg)
Однак, важливо, що слід зазначити про типи класів, це те, що словники асоціюються з типами, а не зі значеннями (як, наприклад, те, що відбувається у словнику та версіях OO). Це означає, що система типів не дозволяє змішувати "типи" [1]. Якщо ви хочете, щоб список "blargables" або бінарних функцій, що приймають до blargables, тоді typeclasses обмежуватимуть все однотипними, тоді як підхід до словника дозволить вам мати мікстури різного походження (яка версія краще багато залежить від того, що ви є робити)
[1] Є вдосконалені способи зробити "екзистенційні типи", але це, як правило, не варте клопоту.