Що стосується функціонального програмування, що таке функтор?


224

Я декілька разів стикався з терміном "Functor", читаючи різні статті про функціональне програмування, але автори зазвичай припускають, що читач уже розуміє цей термін. Оглянувшись в Інтернеті, ви отримали або надмірно технічні описи (див. Статтю у Вікіпедії ), або неймовірно розпливчасті описи (див. Розділ про функціонерів на цьому веб-сайті з навчального поводу ).

Чи може хтось люб’язно визначити термін, пояснити його вживання та, можливо, навести приклад того, як створюються та використовуються Функтори?

Редагувати : Хоча мене цікавить теорія, що стоїть за цим терміном, мене менш цікавить теорія, ніж мене стосується впровадження та практичного використання концепції.

Редагувати 2 : Схоже, відбувається певна перехресна термінологія: я конкретно маю на увазі Функторів функціонального програмування, а не об’єктів функцій C ++.


4
Дивіться також: adit.io/posts/…
Влад Імпала

Досить хороший відповідь теж: stackoverflow.com/a/45149475/1498178
toraritte

Якщо ви більше зацікавлені в практичній реалізації та використанні, ніж у стратосферній термінології та теорії, що стоїть за концепцією, вам просто потрібен один вкладиш: функтор відкриває функцію "карта".
Річард Гомес

@ RichardGomes IMHO Я думаю, що це зводить роль функтора до простого інтерфейсу, схожого на Java, якого немає. Функтор перетворює речі, він створює нові типи з існуючих (в Haskell), що означає, що типи також відображаються. fmapвідображає функції. Є два види відображення. Такий спосіб бачення речей допоможе зрозуміти теорію категорій (що більш загальне). Я маю на увазі, що цікаво зрозуміти базову теорію категорій, щоб допомогти нам з усіма матеріалами теорії категорій у Haskell (функтор, монади, ...).
Людович Куті

@VladtheImpala Повідомлення в блозі є фантастичним, але, навіть якщо це дуже допомагає, я хотів би мати на увазі, що функтор будує (картає) іншого типу. Мені особливо подобається речення "Функтор F приймає кожен тип T і відображає його на новий тип FT" у Monads - це як буріто . ІМХО - це не просто контекст (поле) навколо значення, навіть якщо це виявляється практичним бачити такі речі (Haskell PoV vs теорія категорій PoV?)
Людовик Куті

Відповіді:


273

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

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

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

    • Тип ключа, який буде використовуватися у двійковому дереві

    • Функція загального замовлення на клавішах

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

    Ще одне застосування функціонерів ML - це багатошарові мережеві протоколи . Посилання на справді приголомшливий документ групи CMU Fox; він показує, як використовувати функтори для побудови більш складних шарів протоколу (наприклад, TCP) на типі більш простих шарів (наприклад, IP або навіть безпосередньо через Ethernet). Кожен шар реалізований як функтор, який приймає за параметр шар під ним. Структура програмного забезпечення насправді відображає те, як люди думають про проблему, на відміну від шарів, що існують лише у свідомості програміста. У 1994 році, коли ця робота була опублікована, це було великою справою.

    Для дикого прикладу функціонерів ML в дії ви можете побачити паперовий модуль ML Mania , який містить оприлюднюваний (тобто страшний) приклад функціонерів на роботі. Для блискучого, прозорого, прозорого пояснення МЛ модулів системи (з порівняннями з іншими видами модулів), прочитати кілька перших сторінок блискучих 1994 POPL паперу Ксав'є Лерой маніфесту типів, модулів і роздільної компіляції .

  • У Haskell, і в деяких пов'язаних чисто функціональної мови, Functorце клас типу . Тип належить до класу типів (або, більш технічно, тип "є екземпляром" класу типу), коли тип забезпечує певні операції з певною очікуваною поведінкою. Тип Tможе належати до класу, Functorякщо він має певну колекційну поведінку:

    • Тип Tпараметризований над іншим типом, який слід розглядати як тип елемента колекції. Тип повної колекції то що - щось подібне T Int, T String, T Bool, якщо ви , що містять цілі числа, рядки або булеві відповідно. Якщо тип елемента невідомий, він записується як параметр типу a , як у T a.

      Приклади включають списки (нульовий або більше елементів типу a), Maybeтип (нульовий або один елемент типу a), набори елементів типу a, масиви елементів типу a, всі види дерев пошуку, що містять значення типу a, і безліч інших можна придумати.

    • Інша властивість, яку Tслід задовольнити, полягає в тому, що якщо у вас є функція типу a -> b(функція на елементах), ви повинні мати можливість приймати цю функцію та пов’язану з продуктом функцію у колекціях. Ви робите це з оператором fmap, яким поділяються всі типи в Functorкласі типу. Оператор насправді перевантажений, тому якщо у вас є функція evenз типом Int -> Bool, то

      fmap even

      це перевантажена функція, яка може зробити багато прекрасних речей:

      • Перетворити список цілих чисел у список булевих

      • Перетворіть дерево цілих чисел у дерево булевих

      • Перетворити Nothingна Nothingта Just 7вJust False

      У Хаскеллі ця властивість виражається даванням типу fmap:

      fmap :: (Functor t) => (a -> b) -> t a -> t b

      де у нас зараз невеликий t, що означає "будь-який тип у Functorкласі".

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

Як завжди, люди продовжують вигадувати нові, корисні абстракції, і ви, можливо, захочете розібратися у прикладних функторах, для яких найкращим посиланням може стати документ під назвою « Прикладне програмування з ефектами » Конора МакБрайда та Росса Патерсона.


7
Я розумію як ML-функторів, так і Haskell-functors, але не вистачає розуміння, щоб їх зв'язати разом. Який взаємозв'язок між цими двома, у категоріально-теоретичному сенсі?
Вей Ху

6
@Wei Hu: Теорія категорій для мене ніколи не мала сенсу. Найкраще, що я можу сказати, - це те, що всі три поняття передбачають картографування.
Норман Рамзі

16
Відповідно до цієї вікі Haskell: en.wikibooks.org/wiki/Haskell/Category_theory , це так: Категорія - це сукупність предметів та морфізмів (функцій), де морфізми перебувають від об'єктів категорії до інших об'єктів цієї категорії . Функтор - це функція, яка відображає об'єкти та морфізми з однієї категорії на об'єкти та морфізми в іншій. Найменше, як я це розумію. Що це означає саме для програмування, я ще не маю зрозуміти.
пав

5
@ Норман-Ремсі, ви подивилися на концептуальну математику Лоувера та Шхануеля? Я абсолютно початківець у цій області, але книга є чіткою для читання і - зважусь сказати - приємною. (Полюбив своє пояснення.)
Рам Раджамоні,

2
then you have to be able to take that function and product a related function on collectionsВи мали на увазі produceзамість product?
problemofficer

64

Інші відповіді тут повні, але я спробую ще одне пояснення використання FP функтора . Прийміть це як аналогію:

Функтор є контейнер типу А , що, при впливі на нього функцію , що карти з вб , дають контейнер типу б .

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


3
Контейнер типу b означає "такий же контейнер, як і вхідний контейнер, але тепер заповнений b". Отже, якщо у нас є список бананів, і ми відображаємо функцію, яка бере банан і виводить фруктовий салат, тепер у нас є список фруктових салатів. Так само, якби у нас було дерево бананів, і ми відображали ту саму функцію, у нас зараз було б дерево яблук. Etc. дерево і список є два функтора тут.
Qqwy

3
"Функтор - це контейнер типу a, який, коли його піддають функції" - це насправді навпаки - функція (морфізм) підлягає функтору, яка буде відображена в інший морфізм
Дмитро Зайцев

38

Є три різні значення, не дуже пов’язані!

  • У Ocaml це параметризований модуль. Дивіться посібник . Я думаю, що найкращий спосіб виправити їх на прикладі: (написано швидко, може бути помилка)

    module type Order = sig
        type t
        val compare: t -> t -> bool
    end;;
    
    
    module Integers = struct
        type t = int
        let compare x y = x > y
    end;;
    
    module ReverseOrder = functor (X: Order) -> struct
        type t = X.t
        let compare x y = X.compare y x
    end;;
    
    (* We can order reversely *)
    module K = ReverseOrder (Integers);;
    Integers.compare 3 4;;   (* this is false *)
    K.compare 3 4;;          (* this is true *)
    
    module LexicographicOrder = functor (X: Order) -> 
      functor (Y: Order) -> struct
        type t = X.t * Y.t
        let compare (a,b) (c,d) = if X.compare a c then true
                             else if X.compare c a then false
                             else Y.compare b d
    end;;
    
    (* compare lexicographically *)
    module X = LexicographicOrder (Integers) (Integers);;
    X.compare (2,3) (4,5);;
    
    module LinearSearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    module BinarySearch = functor (X: Order) -> struct
        type t = X.t array
        let find x k = 0 (* some boring code *)
    end;;
    
    (* linear search over arrays of integers *)
    module LS = LinearSearch (Integers);;
    LS.find [|1;2;3] 2;;
    (* binary search over arrays of pairs of integers, 
       sorted lexicographically *)
    module BS = BinarySearch (LexicographicOrder (Integers) (Integers));;
    BS.find [|(2,3);(4,5)|] (2,3);;

Тепер ви можете швидко додавати багато можливих замовлень, способів формування нових замовлень, легко виконувати двійковий або лінійний пошук над ними. Загальне програмування FTW.

  • У таких функціональних мовах програмування, як Haskell, вони мають на увазі конструктори деяких типів (параметризовані типи, такі як списки, набори), які можна "відобразити". Якщо бути точним, функторf обладнаний (a -> b) -> (f a -> f b). Це має витоки в теорії категорій. Стаття у Вікіпедії, до якої ви посилаєтесь, - це використання.

    class Functor f where
        fmap :: (a -> b) -> (f a -> f b)
    
    instance Functor [] where      -- lists are a functor
        fmap = map
    
    instance Functor Maybe where   -- Maybe is option in Haskell
        fmap f (Just x) = Just (f x)
        fmap f Nothing = Nothing
    
    fmap (+1) [2,3,4]   -- this is [3,4,5]
    fmap (+1) (Just 5)  -- this is Just 6
    fmap (+1) Nothing   -- this is Nothing

Отже, це особливий тип конструкторів типу, і це мало спільного з функторами в Ocaml!

  • В імперативних мовах - це вказівник на функціонування.

Чи не повинен <q> карта </q> в останніх 3 рядках цього коментаря бути дійсно <q> fmap </q>?
imz - Іван Захарящев

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

16

У OCaml - це параметризований модуль.

Якщо ви знаєте C ++, подумайте про функтор OCaml як шаблон. У C ++ є лише шаблони класів, а функтори працюють за шкалою модулів.

Приклад функтора - Map.Make; module StringMap = Map.Make (String);;будує модуль карт, який працює з картами String-keyed.

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


Я отримав це з веб-сайту ocaml - але я не розумію, яким би було використання параметризованого модуля.
Ерік Форбс

4
@Kornel Так, я описав концепцію OCaml. Інша концепція - це лише «функціональна цінність», яка не має нічого особливого в ПП @Erik Я трохи розширився, але довідкові документи повільно завантажуються.
Тобу

13

Ви отримали досить багато хороших відповідей. Я підкажу:

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

На це можна поглянути два способи. Наприклад, списки - це функтори для певного типу. Тобто, даючи алгебру над типом 'a', ви можете генерувати сумісну алгебру списків, що містять речі типу 'a'. (Наприклад: карта, яка включає елемент до списку синглтон, що містить його: f (a) = [a]) Знову ж таки, поняття сумісності виражається законами функтора.

З іншого боку, з урахуванням функтора f "над" типу a (тобто fa є результатом застосування функтора f до алгебри типу a), а функції з g: a -> b, ми можемо обчислити новий функтор F = (fmap g), який відображає fa до f b. Коротше кажучи, fmap - це частина F, яка відображає "частини функтора" на "частини функтора", а g - частина функції, яка відображає "частини алгебри" на "частини алгебри". Він займає функцію, функтор, і після завершення він також є функцією.

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

Функція Haskell НЕ є класом типу. Це тип даних із вільною змінною, яка задовольняє клас типу. Якщо ви готові копатися в кишках типу даних (без вільних змінних), ви можете повторно інтерпретувати тип даних як функтор над нижньою алгеброю. Наприклад:

дані F = F Int

є ізоморфним класу Інтів. Отже F як конструктор значення - це функція, яка відображає Int до F Int, еквівалентну алгебру. Це функтор. З іншого боку, тут ви не отримуєте fmap безкоштовно. Ось для чого відповідає узор.

Функціонери добре "прикріплювати" речі до елементів алгебри алгебраїчно сумісним чином.


8

Найкращу відповідь на це запитання знайдемо у "Типкласопедії" Брента Йоргея.

Цей випуск Monad Reader містить точне визначення того, що таке функтор, а також багато визначення інших понять, а також діаграму. (Monoid, Applicative, Monad та інші поняття пояснюються і бачаться стосовно функтора).

http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf

уривок з Typeclassopedia for Functor: "Проста інтуїція полягає в тому, що Functor являє собою якийсь" контейнер ", а також можливість рівномірно застосовувати функцію до кожного елемента в контейнері"

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

Ура


7

В книзі O'Reilly OCaml є досить хороший приклад, який розміщений на веб-сайті Inria (який, на жаль, не вдається). Я знайшов дуже подібний приклад у цій книзі, яку використовує caltech: Вступ до OCaml (pdf посилання) . Відповідний розділ - розділ про функтори (Сторінка 139 у книзі, стор. 149 у PDF).

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

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

Використовуючи функтор і модуль, який реалізує порівняння, вони можуть створити новий модуль в одному рядку:

module SSet = MakeSet(StringCaseEqual);;

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

Tobu порівнював функторів із шаблонами на C ++, які, на мою думку, є цілком придатними.


6

З огляду на інші відповіді та те, що я зараз публікую, я б сказав, що це досить сильне перевантажене слово, але все одно ...

Підказку щодо значення слова "функтор" у Хаскеллі запитайте у GHCi:

Prelude> :info Functor
class Functor f where
  fmap :: forall a b. (a -> b) -> f a -> f b
  (GHC.Base.<$) :: forall a b. a -> f b -> f a
        -- Defined in GHC.Base
instance Functor Maybe -- Defined in Data.Maybe
instance Functor [] -- Defined in GHC.Base
instance Functor IO -- Defined in GHC.Base

Отже, в основному функтор в Haskell - це те, що можна відобразити на карті. Ще один спосіб сказати, що функтор - це те, що може розглядатися як контейнер, який може попросити використати задану функцію для перетворення значення, яке він містить; Таким чином, для списків, fmapзбігається з map, для Maybe, fmap f (Just x) = Just (f x),fmap f Nothing = Nothing і т.д.

Функтор класу типів підрозділ і розділ функторів аплікативного функторів і моноїд в ЖЖЕ вас на Haskell для Добра наводять кілька прикладів, де ця конкретна концепція корисна. (Підсумок: багато місць! :-))

Зауважте, що будь-яку монаду можна розглядати як функтор, і насправді, як зазначає Крейг Стунц, найчастіше використовувані функтори мають тенденцію бути монадами ... ОТО, часом зручно робити тип екземпляром типу класу Functor не йдучи на те, щоб зробити його монадою. (Наприклад, у випадку ZipListз Control.Applicative, згаданим на одній із вищезгаданих сторінок .)


5

Ось стаття про функтори з програми програмування POV , далі докладніше, як вони опиняються в мовах програмування .

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


1
"Практичне використання функтора в монаді" - не тільки. Всі монади є функторами, але існує безліч застосувань для немонадних функторів.
amindfv

1
Я б сказав, що вивчати монади на використання функторів - це як заощадити, щоб Rolls ходив купувати продукти.
Марко Фаустінеллі

5

У коментарі до голосової відповіді користувач Вей Ху просить:

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

Примітка . Я не знаю ML, тому пробачте і виправте будь-які пов'язані помилки.

Спочатку припустимо, що ми всі знайомі з визначеннями "категорія" та "функтор".

Компактною відповіддю було б те, що "Haskell-functors" є (ендо-) функціоналами, F : Hask -> Haskтоді як "ML-functors" - функторами G : ML -> ML'.

Тут Haskзнаходиться категорія, утворена типами Haskell і функціями між ними, і аналогічно, MLі ML'це категорії, визначені структурами ML.

Примітка . Існують деякі технічні проблеми із створенням Haskкатегорії, але існують способи їх вирішення.

З теоретичної точки зору це означає, що функціонал Hask- це карта Fтипів Haskell:

data F a = ...

разом із картою fmapфункцій Haskell:

instance Functor F where
    fmap f = ...

ML - майже все те ж саме, хоча fmapя не знаю жодної канонічної абстракції, тому давайте визначимося з одним:

signature FUNCTOR = sig
  type 'a f
  val fmap: 'a -> 'b -> 'a f -> 'b f
end

Тобто fкарти ML-типи та fmapкарти ML-функції, так

functor StructB (StructA : SigA) :> FUNCTOR =
struct
  fmap g = ...
  ...
end

є функтором F: StructA -> StructB.


5

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

Давайте визначимо, що таке категорія?

Це купа предметів!

Намалюйте декілька крапок (зараз 2 крапки, одна - «a», інша - «b») всередині кола та назвіть це коло A (категорія).

Що містить категорія?

Композиція між об'єктами та функцією Identity для кожного об'єкта.

Отже, ми повинні зіставити об’єкти та зберегти композицію після застосування нашого Functor.

Давайте уявимо, що "A" - це наша категорія, яка має об'єкти ['a', 'b'] і існує морфізм a -> b

Тепер ми повинні визначити функтор, який може відобразити ці об'єкти та морфізми в іншу категорію "B".

Скажімо, функтор називається "Можливо"

data Maybe a = Nothing | Just a

Отже, категорія "B" виглядає приблизно так.

Будь ласка, намалюйте ще одне коло, але цього разу замість 'a' та 'b', можливо, 'та a, можливо, b'.

Все здається добре, і всі об’єкти відображені на карті

'a' став 'Можливо a', а 'b' став 'Можливо b'.

Але проблема полягає в тому, що ми також повинні скласти морфізм від 'a' до 'b'.

Це означає, що морфізм a -> b у "A" повинен відповідати морфізму "Може бути" -> "Можливо, b"

морфізм від a -> b називається f, тоді морфізм від 'Можливо a' -> 'Можливо, b' називається 'fmap f'

Тепер давайте подивимось, яку функцію 'f' виконував у «A», і подивимось, чи можемо ми повторити її у «B»

визначення функції 'f' в 'A':

f :: a -> b

f приймає a і повертає b

визначення функції 'f' у 'B':

f :: Maybe a -> Maybe b

f приймає Можливо a і повертається Може b

давайте подивимося, як використовувати fmap для відображення функції 'f' від 'A' до функції 'fmap f' у 'B'

визначення fmap

fmap :: (a -> b) -> (Maybe a -> Maybe b)
fmap f Nothing = Nothing
fmap f (Just x) = Just(f x)

Отже, що ми тут робимо?

Ми застосовуємо функцію 'f' до 'x', яка має тип 'a'. Спеціальна відповідність шаблону "нічого" походить від визначення " Functor Maybe.

Отже, ми відобразили наші об’єкти [a, b] та морфізми [f] від категорії 'A' до категорії 'B'.

То функціонер!

введіть тут опис зображення


Цікава відповідь. Я хотів би доповнити його монадами - це як буріто (смішна відповідь на абстракцію, інтуїцію та "помилку підручника монади" ), а його речення "Функтор F бере кожен тип T і відображає його на новий тип FT", наприклад конструктор типу . Функціональне програмування та теорія категорій - Категорії та функціонери теж були корисні.
Людович Куті

3

Приблизний огляд

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

Приклади функторів включають масиви, "можливо" та "або" функтори, ф'ючерси (див., Наприклад, https://github.com/Avaq/Fluture ) та багато інших.

Ілюстрація

Розглянемо функцію, що побудує повне ім’я людини від імені та прізвища. Ми могли б визначити це як fullName(firstName, lastName)функцію двох аргументів, що, однак, не було б придатним для функціонерів, які мають справу лише з функціями одного аргументу. Для виправлення ми збираємо всі аргументи в один об’єкт name, який тепер стає єдиним аргументом функції:

// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName

Що робити, якщо у нас у масиві багато людей? Замість того, щоб вручну перейти через список, ми можемо просто повторно використовувати нашу функцію fullNameза допомогою mapметоду, передбаченого для масивів із коротким єдиним рядком коду:

fullNameList = nameList => nameList.map(fullName)

і використовувати його як

nameList = [
    {firstName: 'Steve', lastName: 'Jobs'},
    {firstName: 'Bill', lastName: 'Gates'}
]

fullNames = fullNameList(nameList) 
// => ['Steve Jobs', 'Bill Gates']

Це буде спрацьовувати, коли кожен запис у нашому nameListоб’єкті надає firstNameі lastNameвластивості, і властивості. Але що робити, якщо деяких об’єктів немає (або навіть взагалі не є об'єктами)? Щоб уникнути помилок і зробити код більш безпечним, ми можемо перетворити наші об'єкти у Maybeтип (наприклад, https://sanctuary.js.org/#maybe-type ):

// function to test name for validity
isValidName = name => 
    (typeof name === 'object') 
    && (typeof name.firstName === 'string')
    && (typeof name.lastName === 'string')

// wrap into the Maybe type
maybeName = name => 
    isValidName(name) ? Just(name) : Nothing()

де Just(name)контейнер, що містить лише дійсні імена, і Nothing()це особливе значення, яке використовується для всього іншого. Тепер замість того, щоб перебивати (або забувати), щоб перевірити обґрунтованість наших аргументів, ми можемо просто повторно використати (підняти) свою первісну fullNameфункцію ще одним єдиним рядком коду, заснованим знову на mapметоді, цього разу передбаченому для типу "Можливо":

// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)

і використовувати його як

justSteve = maybeName(
    {firstName: 'Steve', lastName: 'Jobs'}
) // => Just({firstName: 'Steve', lastName: 'Jobs'})

notSteve = maybeName(
    {lastName: 'SomeJobs'}
) // => Nothing()

steveFN = maybeFullName(justSteve)
// => Just('Steve Jobs')

notSteveFN = maybeFullName(notSteve)
// => Nothing()

Теорія категорій

Functor в теорії категорій є відображення між двома категоріями поважають склад їх морфізм. В комп'ютерній мові основною категорією, що цікавить, є той, об'єктами якого є типи (певні набори значень), морфізми яких є функціями f:a->bвід одного типу aдо іншого b.

Наприклад, візьмемо aза Stringтип, тип bЧисло, і fце функція, що відображає рядок у його довжину:

// f :: String -> Number
f = str => str.length

Тут a = Stringпредставлений набір усіх рядків і b = Numberбезліч усіх чисел. У цьому сенсі і те, і іншеa і bпредставляють об'єкти в категорії Set (що тісно пов'язане з категорією типів, різниця тут несуттєва). У категорії «Набір» морфізми між двома множинами - це точно всі функції від першого набору до другого. Отже, наша функція довжини fтут - це морфізм від безлічі рядків у множину чисел.

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

Приклад: Array

Arrayможе означати багато речей, але лише одне - це Функтор - конструкція типу, що відображає тип aу тип [a]усіх масивів типу a. Наприклад, Arrayфунктор відображає типString у тип [String](набір усіх масивів рядків довільної довжини), а тип встановлює Numberу відповідний тип [Number](набір усіх масивів чисел).

Важливо не плутати карту Functor

Array :: a => [a]

з морфізмом a -> [a]. Функтор просто відображає (пов'язує) тип aу тип[a] як одну річ до іншої. Те, що кожен тип насправді є сукупністю елементів, тут не має жодного значення. Навпаки, морфізм є фактичною функцією між цими множинами. Наприклад, існує природний морфізм (функція)

pure :: a -> [a]
pure = x => [x]

який надсилає значення в 1-елементний масив із цим значенням як єдиний запис. Ця функція не є частиною ArrayFunctor! З точки зору цього функтора, pureце просто функція, як і будь-яка інша, нічого особливого.

З іншого боку, у ArrayФунктора є друга його частина - частина морфізму. Що відображає морфізм f :: a -> bу морфізм [f] :: [a] -> [b]:

// a -> [a]
Array.map(f) = arr => arr.map(f)

Тут arrє будь-який масив довільної довжини зі значеннями типу a, і arr.map(f)це масив однакової довжини зі значеннями типу b, записи якого є результатами застосування fдо записів arr. Щоб зробити його функціонером, повинні дотримуватися математичні закони відображення тотожності до ідентичності та композицій до композицій, які легко перевірити в цьому Arrayприкладі.


2

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

Прикладом є інтерфейс Runnable на Java, який має лише один метод: run.

Розглянемо цей приклад спочатку в Javascript, який має першокласні функції:

[1, 2, 5, 10].map(function(x) { return x*x; });

Вихід: [1, 4, 25, 100]

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

Щоб зробити те ж саме, це Java, використовуючи Functor, спочатку потрібно визначити інтерфейс, скажімо:

public interface IntMapFunction {
  public int f(int x);
}

Потім, якщо ви додасте клас колекції, який мав функцію карти, ви можете зробити:

myCollection.map(new IntMapFunction() { public int f(int x) { return x * x; } });

Для цього використовується підклас підрозділу IntMapFunction для створення Functor, який є еквівалентом OO функції з попереднього прикладу JavaScript.

Використання функцій дозволяє застосовувати функціональні прийоми мовою ОО. Звичайно, деякі мови OO також мають підтримку функцій безпосередньо, тому це не потрібно.

Довідка: http://en.wikipedia.org/wiki/Function_object


Насправді "об’єкт функції" не є правильним описом функтора. Наприклад Array, це функтор, але Array(value)дає лише 1-елементні масиви.
Дмитро Зайцев

0

KISS: Функтор - це об’єкт, який має метод карти.

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

src: https://www.youtube.com/watch?v=DisD9ftUyCk&feature=youtu.be&t=76


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

1
Я вважаю Класи супер заплутаними, якщо бути чесними, з одного боку вони є лише основою чогось конкретного / але вони також можуть мати методи (статичні речі) і можуть вести себе як предмети. Чи реалізує клас інтерфейс чи створений ним екземпляр?
саундйогі

1
Так, вони можуть заплутати. Але: Класи реалізують інтерфейси (вони «заповнюють» пробіли, які були задані методами інтерфейсів. Іншими словами: вони перетворюють абстрактні вказівки інтерфейсу в конкретні вказівки, які можна миттєво (пробачити каламбур) миттєво). Щодо 'класи ведуть себе як об'єкти': У справді OOP-мовах, таких як Ruby, Класи є екземплярами класу 'Class'. Це черепахи аж донизу.
Qqwy

Arrayтип конструкції визначає єдиний функтор. Його екземпляри також називаються "масивами", але вони не є функціонерами. Опис тут слід зробити більш точним.
Дмитро Зайцев

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

-4

На практиці функтор означає об'єкт, який реалізує оператор виклику в C ++. У ocaml я думаю, що функтор посилається на те, що приймає модуль як вхід і вихід іншого модуля.


-6

Простіше кажучи, функтор або об’єкт функції - це об'єкт класу, який можна назвати так само, як функцію.

В C ++:

Так ви пишете функцію

void foo()
{
    cout << "Hello, world! I'm a function!";
}

Так ви пишете функтор

class FunctorClass
{
    public:
    void operator ()
    {
        cout << "Hello, world! I'm a functor!";
    }
};

Тепер ви можете зробити це:

foo(); //result: Hello, World! I'm a function!

FunctorClass bar;
bar(); //result: Hello, World! I'm a functor!

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


12
Ні, ці функціонери не є поняттям теорії типів, яке використовується мовами FP.
Тобу

1
Я можу FunctorClassщось бачити, як можна було довести, що він виконує перший закон про функціонера, але ви могли б накреслити доказ другого закону? Я не зовсім цього бачу.
Йорг W Міттаг

3
Ба, ви праві, хлопці. Я вирішив вирішити "веб надав надзвичайно технічні описи" і прострочив, намагаючись уникнути: "У сімействі мов ML функція функтора - це модуль, який приймає один або кілька інших модулів як параметр". Однак ця відповідь є поганою. Надмірно спрощене і недоозначене. Мені спокушається, щоб я його вибрав, але я залишу це майбутнім поколінням, щоб похитати головою :)
Метт

Я радий, що ви залишили відповідь та коментарі, оскільки це допомагає вирішити проблему. Дякую! У мене виникають проблеми з тим, що більшість відповідей написані в термінах Haskell або OCaml, і мені це трохи схоже на пояснення алігаторів з точки зору крокодилів.
Роб

-10

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


8
Існує конкретна концепція FP функтора (з теорії категорій), але ви праві, що те саме слово використовується і для інших речей у мовах, що не належать до FP.
Крейг Штунц

Ви впевнені, що покажчики функцій - це Функтори? Я не бачу, як покажчики функцій виконують два Закони Функтора, особливо Закон другого Функтора (збереження складу морфізму). Чи є у вас докази на це? (Просто груба замальовка.)
Jörg W Mittag
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.