Яка функціонально-програмова альтернатива інтерфейсу?


15

Якщо я хочу програмувати у "функціональному" стилі, чим би я замінив інтерфейс?

interface IFace
{
   string Name { get; set; }
   int Id { get; }
}
class Foo : IFace { ... }

Може бути Tuple<>?

Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;

Єдина причина, що я в першу чергу використовую інтерфейс - це те, що я хочу завжди мати певні властивості / методи.


Редагувати: ще детальніше про те, що я думаю / намагаюся.

Скажімо, у мене є метод, який виконує три функції:

static class Blarf
{
   public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}

З екземпляром Barя можу використовувати цей метод:

class Bar
{
   public string GetName();
   public void SetName(string value);

   public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);

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

Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!

У C # - interfaceце один із способів впоратися з цим; але це здається дуже об'єктно-орієнтованим підходом. Мені цікаво, чи є більш функціональне рішення: 1) передайте групу функцій разом, і 2) переконайтесь, що функції належним чином пов'язані між собою.


Ви б не зробили. Інтерфейси для типів даних є ідеальними (хоча ви віддасте перевагу незмінним об'єктам).
Теластин

1
Глава 2 SICP з цього приводу в значній мірі.
user16764

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

Відповідь була б залежною від мови. У Clojure ви можете використовувати clojure.org/protocols , де єдиною м'якою областю є типи параметрів, над якими повинні працювати функції - вони є об'єктом - це все, що ви знаєте.
Робота

1
Зробити це просто: структура, що містить ці покажчики методу, плюс функція ініціалізації її з об'єкта об'єкта. Чому Haskell? ;)
mlvljr

Відповіді:


6

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

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


3
"Ви повинні думати російською".
Ðаn

Справедливо; моя реальна проблема не особливо цікава. У мене вже все добре працює на C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDay, якщо ви дійсно хочете подивитися на код), але було б весело робити в більш функціональному стилі, поки все ще використовується C #.
Ðаn

1
@ Ðаn Це справді цитата Firefox (фільм) (бо це дивовижно, якщо це є)? Або він використовується десь ще?
icc97

2
Де, як я згоден, це повний перехід парадигми від невмілого до функціонального програмування, існує багато порядків більше рядків коду, написаних невмілим способом. Так що набагато більше кутових справ, написаних для величезних систем, буде знайдено при імпертивному програмуванні. Існує маса передового досвіду щодо невмілого програмування, і знання того, чи можна перекладати ці навички або якщо вони не є проблемою на FP - це гідне питання. Цілком можливо написати жахливий код на FP, тому такі питання повинні висвітлити і хороші частини FP.
icc97

11

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


5
+1 для типів класів - головна відмінність класу типу Haskell від інтерфейсу Java полягає в тому, що тип класу асоціюється з типом після того, як обидва оголошуються окремо. Ви можете використовувати старий тип через новий "інтерфейс" так само легко, як і старий "інтерфейс" для доступу до нового типу. Для приховування даних ви ховаєте реалізацію типу в модулі. Принаймні, згідно Бертрана Мейєра з Ейфелевої слави , клас OOP - це свого роду модуль.
Steve314

5

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

Перший і найпоширеніший: Параметричний поліморфізм.

Це дає можливість функції функціонувати на довільні типи:

--Haskell Example
id :: a -> a --Here 'a' is just some arbitrary type
id myRandomThing = myRandomThing

head :: [a] -> a
head (listItem:list) = listItem

Що приємно, але не дає динамічної розсилки інтерфейсів ОО. Для цього Haskell має типові класи, Scala має наслідки тощо

class Addable a where
   (<+>) :: a -> a -> a
instance Addable Int where
   a <+> b = a + b
instance Addable [a] where
   a <+> b = a ++ b

--Now we can get that do something similar to OO (kinda...)
addStuff :: (Addable a) => [a] -> a
-- Notice how we limit 'a' here to be something Addable
addStuff (x:[]) = x
addStuff (x:xs) = x <+> addStuff xs
-- In better Haskell form
addStuff' = foldl1 <+>

Між цими двома механізмами ви можете висловити всілякі складні та цікаві форми поведінки на типі.


1
Можна додати підказки підкреслення sintax, коли мова у відповіді не відповідає мові у питанні. Наприклад, дивіться запропоновану нами редакцію.
хугомг

1

Основне правило роботи полягає в тому, що в програмуванні функції 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] Є вдосконалені способи зробити "екзистенційні типи", але це, як правило, не варте клопоту.


0

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

;; returns a function of the type #'(lambda (x y z &optional a b c)

(defun get-higher-level-method-impl (some-type-of-qualifier) 
    (cond ((eq 'foo) #'the-foo-version)
          ((eq 'bar) #'the-bar-version)))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.