Что такое функтор в функциональном программировании?

224

Я встречал термин «Functor» несколько раз, читая различные статьи о функциональном программировании, но авторы обычно предполагают, что читатель уже понимает этот термин. Просмотр в Интернете предоставил либо чрезмерно технические описания (см. Статью в Википедии ), либо невероятно расплывчатые описания (см. Раздел «Функторы» на этом веб-сайте ocaml-учебника ).

Может кто-нибудь любезно дать определение термину, объяснить его использование и, возможно, привести пример того, как создаются и используются функторы?

Изменить : Хотя меня интересует теория, стоящая за этим термином, меня интересует не столько теория, сколько реализация и практическое использование концепции.

Редактировать 2 : Похоже, что происходит некоторая кросс-терминология: я специально имею в виду Функторы функционального программирования, а не функциональные объекты C ++.

Эрик Форбс
источник
4
Смотрите также: adit.io/posts/...
Влад Импала
Довольно хороший ответ тоже: stackoverflow.com/a/45149475/1498178
тораритт
Если вы более заинтересованы в практической реализации и использовании, чем в стратосферной терминологии и теории, лежащей в основе концепции, вам просто нужен один вкладыш: функтор предоставляет функцию «карты».
Ричард Гомес
@RichardGomes IMHO Я думаю, что это сводит роль функтора к простому Java-подобному интерфейсу, а это не так. Функтор преобразует вещи, он строит новые типы из существующих (в Haskell), что означает, что типы также отображаются. fmapсопоставляет функции. Существует два вида сопоставлений. Такой взгляд на вещи поможет понять теорию категорий (которая носит более общий характер). Я имею в виду, что интересно понять основную теорию категорий, чтобы помочь нам со всеми вещами теории категорий в Haskell (функтор, монады, ...).
Людовик Куты
@VladtheImpala Публикация в блоге фантастическая, но, даже если она очень помогает, я хотел бы иметь в виду, что функтор создает (сопоставляет) другой тип. Мне особенно нравится предложение «Функтор F берет каждый тип T и отображает его на новый тип FT» в монадах, как буррито . ИМХО, это не просто контекст (блок) вокруг значения, даже если это оказывается практичным, чтобы увидеть подобные вещи (Haskell PoV против теории категорий PoV?)
Людовик Куты

Ответы:

273

Слово «функтор» происходит от теории категорий, которая является очень общей, очень абстрактной областью математики. Он был заимствован дизайнерами функциональных языков как минимум двумя различными способами.

  • В семействе языков ML функтор - это модуль, который принимает один или несколько других модулей в качестве параметра. Это считается расширенной функцией, и большинство начинающих программистов испытывают трудности с этим.

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

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

    • Функция полного заказа на клавиши

    Как только вы это сделаете, вы можете использовать одну и ту же сбалансированную двоичную реализацию дерева навсегда. (Тип значения, хранящегося в дереве, обычно остается полиморфным - дереву не нужно смотреть на значения, кроме как копировать их, тогда как дерево определенно должно иметь возможность сравнивать ключи, и оно получает функцию сравнения из параметр функтора.)

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

    Для дикого примера функторов ML в действии вы можете увидеть статью ML Module Mania , в которой содержится публикуемый (то есть, страшный) пример функторов в действии . Чтобы получить блестящее, ясное и ясное объяснение системы модулей ML (со сравнениями с другими типами модулей), прочитайте первые несколько страниц блестящей POPL-публикации Ксавьера Лероя 1994 года « Типы манифестов, модули и отдельная компиляция» .

  • В 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

      В Haskell это свойство выражается путем указания типа fmap:

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

      где у нас теперь есть маленький t, что означает «любой тип в Functorклассе».

    Короче говоря, в Haskell функтор - это своего рода коллекция, для которой, если вам дана функция для элементов, fmapвам будет возвращена функция для коллекций . Как вы можете себе представить, это идея, которую можно широко использовать, поэтому она является частью стандартной библиотеки Haskell.

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

Норман Рэмси
источник
7
Я понимаю и ML-функторы, и Haskell-функторы, но мне не хватает понимания, чтобы связать их вместе. Каковы отношения между этими двумя в теоретико-категориальном смысле?
Вэй Ху
6
@ Wei Hu: Теория категорий никогда не имела для меня никакого смысла. Лучшее, что я могу сказать, это то, что все три понятия связаны с отображением.
Норман Рэмси
16
Согласно этой вики Haskell: en.wikibooks.org/wiki/Haskell/Category_theory , это выглядит так: Категория - это коллекция объектов и морфизмов (функций), где морфизмы - от объектов в категории до других объектов в этой категории. , Функтор - это функция, которая отображает объекты и морфизмы из одной категории в объекты и морфизмы в другой. Меньше вот как я это понимаю. Что это значит именно для программирования, я еще не понял.
Пол
5
@ norman-ramsey, ты смотрел на концептуальную математику от Lawvere и Schanuel? Я абсолютный новичок в этой области, но книга в высшей степени удобочитаема и - смею говорить - доставляет удовольствие. (
Мне понравилось
2
then you have to be able to take that function and product a related function on collectionsВы имели в виду produceвместо product?
проблематик
64

Другие ответы здесь полны, но я попробую другое объяснение использования функтора в FP . Возьмем это как аналогию:

Функтор - это контейнер типа a, который при воздействии на функцию, отображающую из ab , дает контейнер типа b .

В отличие от использования указателя на абстрагированную функцию в C ++, здесь функтор не является функцией; скорее это то, что ведет себя последовательно, когда подвергается функции .

SEH
источник
3
Контейнер типа b означает «контейнер того же типа, что и входной контейнер, но теперь заполнен буквами b». Итак, если у нас есть список бананов, и мы отображаем функцию, которая берет банан и выводит фруктовый салат, у нас теперь есть список фруктовых салатов. Аналогично, если бы у нас было дерево бананов, и мы отображали ту же функцию, у нас теперь было бы дерево яблок. И т.д. дерево и список - это два Функтора.
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!

  • В императивных языках это указатель на функцию.
sdcvvc
источник
Разве <q> map </ q> в последних 3 строках этого комментария не должно быть действительно <q> fmap </ q>?
imz - Иван Захарящев
1
Я всегда читал, что функторы являются контейнерами, но это всего лишь плохое упрощение. Ваш ответ наконец дал недостающую ссылку: функторы - это класс типов (ограничение типа) для параметризованных типов (конструкторы типов). Это так просто!
16

В OCaml это параметризованный модуль.

Если вы знаете C ++, подумайте о функторе OCaml как о шаблоне. В C ++ есть только шаблоны классов, а функторы работают в масштабе модуля.

Примером функтора является Map.Make; module StringMap = Map.Make (String);;создает модуль карты, который работает с картами со строковыми ключами.

Вы не могли бы достичь чего-то вроде StringMap только с полиморфизмом; вам нужно сделать некоторые предположения о ключах. Модуль String содержит операции (сравнение и т. Д.) Для полностью упорядоченного строкового типа, а функтор будет связывать операции, содержащиеся в модуле String. Вы можете сделать что-то подобное с объектно-ориентированным программированием, но у вас будут накладные расходы на метод.

Tobu
источник
Я получил это с веб-сайта ocaml - но я не понимаю, каким будет использование параметризованного модуля.
Эрик Форбс
4
@Kornel Да, я описал концепцию OCaml. Другая концепция - это просто «функциональная ценность», которая не имеет ничего общего с FP. @Erik Я немного расширил, но справочные документы загружаются медленно.
Тобу
13

Вы получили довольно много хороших ответов. Я передам:

Функтор в математическом смысле - это особый вид функции в алгебре. Это минимальная функция, которая отображает алгебру в другую алгебру. «Минимальность» выражается законами функтора.

Есть два способа посмотреть на это. Например, списки являются функторами некоторого типа. То есть, учитывая алгебру над типом «а», вы можете сгенерировать совместимую алгебру списков, содержащих вещи типа «а». (Например: карта, которая переводит элемент в одноэлементный список, содержащий его: 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 бесплатно здесь. Вот для чего нужно сопоставление с образцом.

Функторы хороши для «прикрепления» вещей к элементам алгебры алгебраически совместимым способом.

user276631
источник
8

Лучший ответ на этот вопрос найден в "Typeclassopedia" Brent Yorgey.

В этом выпуске Monad Reader содержится точное определение того, что такое функтор, а также множество определений других понятий и диаграммы. (Monoid, Applicative, Monad и другие концепции объясняются и рассматриваются по отношению к функтору).

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

отрывок из Typeclassopedia для Functor: «Простая интуиция заключается в том, что Functor представляет некоторый« контейнер »вместе со способностью равномерно применять функцию к каждому элементу в контейнере»

Но на самом деле вся тип-классопедия очень рекомендуется для чтения, что удивительно легко. В некотором смысле вы можете видеть представленный там класс типов как параллель к шаблону проектирования в объекте в том смысле, что они дают вам словарный запас для данного поведения или возможностей.

ура

JFT
источник
7

Есть довольно хороший пример в книге O'Reilly OCaml, которая находится на веб-сайте Инрии (которая на момент написания статьи, к сожалению, не работает). В этой книге я нашел очень похожий пример, используемый caltech: Введение в OCaml (pdf link) . Соответствующим разделом является глава о функторах (стр. 139 в книге, стр. 149 в PDF).

В книге у них есть функтор MakeSet, который создает структуру данных, состоящую из списка, и функции для добавления элемента, определения наличия элемента в списке и поиска элемента. Функция сравнения, которая используется для определения того, находится ли она в / не в наборе, была параметризована (что делает MakeSet функтором вместо модуля).

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

Используя функтор и модуль, который реализует сравнение, они могут создать новый модуль в одну строку:

module SSet = MakeSet(StringCaseEqual);;

это создает модуль для заданной структуры данных, которая использует сравнения без учета регистра. Если вы хотите создать набор, в котором используются сравнения с учетом регистра, вам просто нужно реализовать новый модуль сравнения вместо нового модуля структуры данных.

Тобу сравнил функторы с шаблонами в C ++, которые я считаю вполне подходящими.

Ники Йошиучи
источник
6

Учитывая другие ответы и то, что я собираюсь опубликовать сейчас, я бы сказал, что это довольно сильно перегруженное слово, но в любом случае ...

Чтобы получить подсказку о значении слова «функтор» в Haskell, спросите 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т.д.

В подразделе класса типов Functor и разделе « Функторы, аппликативные функторы и моноиды « вы учите хакел »за великое благо» приводятся примеры того, как эта конкретная концепция полезна. (Резюме: много мест! :-))

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

Михал Марчик
источник
5

Вот статья о функторах из POV программирования , а затем более конкретно, как они появляются в языках программирования .

Практическое использование функтора в монаде, и вы можете найти много уроков по монадам, если посмотрите на это.

Крейг Штунц
источник
1
«Практическое использование функтора в монаде» - не только. Все монады являются функторами, но есть множество применений для не-монадных функторов.
amindfv
1
Я бы сказал, что изучение монад с использованием функторов - это то же самое, что копить в Rolls, чтобы купить продукты.
Марко Фаустинелли
5

В комментарии к ответу , получившему наибольшее количество голосов , пользователь Wei Hu спрашивает:

Я понимаю и ML-функторы, и Haskell-функторы, но мне не хватает понимания, чтобы связать их вместе. Каковы отношения между этими двумя в теоретико-категориальном смысле?

Примечание : я не знаю ML, поэтому, пожалуйста, простите и исправьте все ошибки, связанные с ними.

Давайте сначала предположим, что мы все знакомы с определениями «категория» и «функтор».

Компактным ответом будет то, что «функторы Хаскеля» являются (эндо-) функторами, F : Hask -> Haskа «функторы ML» - функторами G : ML -> ML'.

Здесь Haskесть категория формируется типов Haskell и функций между ними, а так же MLи ML'такие категории , определяемые ML структурами.

Примечание : есть некоторые технические проблемы с созданием Haskкатегории, но есть способы их обойти.

С теоретической точки зрения категории это означает, что Hask-functor - это карта 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.

Ncat
источник
5

«Функтор - это отображение объектов и морфизмов, которое сохраняет композицию и идентичность категории».

Давайте определим, что такое категория?

Это куча предметов!

Нарисуйте несколько точек (на данный момент 2 точки, одна - «a», другая - «b») внутри круга и назовите этот круг A (Категория).

Что держит категория?

Композиция между объектами и функция идентичности для каждого объекта.

Итак, мы должны отобразить объекты и сохранить композицию после применения нашего Functor.

Давайте представим, что «A» - это наша категория, в которой есть объекты ['a', 'b'] и существует морфизм a -> b

Теперь нам нужно определить функтор, который может отображать эти объекты и морфизмы в другую категорию «B».

Допустим, функтор называется «Может быть»

data Maybe a = Nothing | Just a

Итак, категория «В» выглядит следующим образом.

Пожалуйста, нарисуйте еще один круг, но на этот раз с «Возможно a» и «Возможно b» вместо «a» и «b».

Все выглядит хорошо, и все объекты сопоставлены

«а» стало «может быть», а «б» стало «возможно, б».

Но проблема в том, что мы должны сопоставить морфизм от «а» до «б».

Это означает, что морфизм a -> b в «A» должен соответствовать морфизму «Maybe a» -> «Maybe b».

морфизм из a -> b называется f, затем морфизм из 'Maybe a' -> 'Maybe b' называется 'fmap f'

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

Определение функции 'f' в 'A':

f :: a -> b

е берет а возвращает б

определение функции 'f' в 'B':

f :: Maybe a -> Maybe b

f берет Может а и возвращает Может б

давайте посмотрим, как использовать 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». Специальное сопоставление с образцом 'Nothing' происходит из определения 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:

// 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типа тип bNumber и fфункцию, отображающую строку в ее длину:

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

Здесь a = Stringпредставлен набор всех строк и b = Numberнабор всех чисел. В этом смысле aи bпредставляют, и представляют объекты в категории множеств (которая тесно связана с категорией типов, причем здесь разница не существенна). В категории множеств морфизмы между двумя наборами - это точно все функции из первого множества во второе. Таким образом, наша функция длины f- это морфизм из набора строк в набор чисел.

Поскольку мы рассматриваем только заданную категорию, соответствующие Функторы из нее в себя являются картами, отправляющими объекты объектам, и морфизмы в морфизмы, которые удовлетворяют определенным алгебраическим законам.

Пример: Array

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

Важно не путать карту Функтора

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);
}

Затем, если вы добавите класс коллекции, у которого есть функция map, вы можете сделать:

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

При этом используется встроенный подкласс IntMapFunction для создания Functor, который является ОО-эквивалентом функции из более раннего примера JavaScript.

Использование Функторов позволяет применять функциональные методы на языке ОО. Конечно, некоторые языки OO также поддерживают функции напрямую, поэтому это не требуется.

Ссылка: http://en.wikipedia.org/wiki/Function_object

Кевин Грир
источник
На самом деле «функциональный объект» не является правильным описанием функтора. Например, Arrayфунктор, но Array(value)дает только 1-элементные массивы.
Дмитрий Зайцев
0

KISS: функтор - это объект, у которого есть метод карты.

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

источник: https://www.youtube.com/watch?v=DisD9ftUyCk&feature=youtu.be&t=76

soundyogi
источник
1
Отметим, что «объект» следует понимать очень широко и просто означает «что-то». Например, для ООП-языков следует заменить объект на класс . Можно сказать, что «функтор - это класс, который реализует интерфейс Functor» (Конечно, этот интерфейс может отсутствовать физически, но вы могли бы поднять логику «map» для этого интерфейса и сделать так, чтобы все ваши сопоставляемые классы разделяли ее - до тех пор, пока ваша система типов позволяет набирать такие общие черты).
Qqwy
1
Я считаю, что классы очень запутанные, если честно, с одной стороны, они являются просто образцом для чего-то конкретного / но они также могут иметь методы (статические вещи) и могут вести себя как объекты. Класс реализует интерфейс или экземпляр, который он создает?
soundyogi
1
Да, они могут сбивать с толку. Но: классы реализуют интерфейсы (они «заполняют» пробелы, которые были даны в методах интерфейсов. Другими словами: они превращают абстрактные руководящие принципы интерфейса в конкретное руководящее указание, которое может быть мгновенно создано (простите за каламбур)). Что касается «классов, которые ведут себя как объекты»: в действительно ООП-языках, таких как Ruby, классы являются экземплярами класса «класс». Это черепахи все время вниз.
Qqwy
ArrayКонструкция типа определяет один функтор. Его экземпляры также называют «массивами», но они не являются функторами. Описание здесь должно быть более точным.
Дмитрий Зайцев
@DmitriZaitsev Не могли бы вы уточнить? Так что вы говорите, что экземпляры не являются функторами? Я не вижу смысла в этом, поскольку вы получаете новый функтор, сопоставляя его.
Soundyogi
-4

На практике под функтором понимается объект, который реализует оператор вызова в C ++. В ocaml я думаю, что functor относится к чему-то, что принимает модуль в качестве входного и выходного другого модуля.

фэн
источник
-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 ()и какой-то метод для проверки этой переменной, и все аккуратно, как вам угодно.

Matt
источник
12
Нет, эти функторы не являются понятием теории типов, используемым языками FP.
Тобу
1
Я могу видеть, как можно доказать, что это FunctorClassсоответствует первому Закону Функтора, но не могли бы вы набросать доказательство для второго Закона? Я не совсем это вижу.
Йорг Миттаг
3
Бах, вы правы. Я попытался решить «сеть предоставила исключительно технические описания» и перебил ее, пытаясь избежать: «В семействе языков ML функтор - это модуль, который принимает один или несколько других модулей в качестве параметра». Этот ответ, однако, плохо. Слишком упрощенный и заниженный. Я испытываю желание разозлить это, но я оставлю это для будущих поколений, чтобы качать их головами в :)
Мэтт
Я рад, что вы оставили ответ и комментарии, потому что это помогает сформулировать проблему. Спасибо! У меня проблемы с тем, что большинство ответов написаны на языке Haskell или OCaml, и для меня это немного похоже на объяснение аллигаторов с точки зрения крокодилов.
Роб
-10

Функтор конкретно не связан с функциональным программированием. Это просто «указатель» на функцию или какой-то объект, который может быть вызван как функция.

alemjerus
источник
8
Существует определенная концепция FP для функтора (из теории категорий), но вы правы, что это же слово используется и для других вещей в языках без FP.
Крейг Штунц
Вы уверены, что указатели на функции являются функторами? Я не понимаю, как указатели на функции выполняют два закона функторов, особенно закон второго функтора (сохранение композиции морфизма). У вас есть доказательства для этого? (Просто грубый набросок.)
Йорг Миттаг