Что такое функтор в функциональном программировании? - PullRequest
213 голосов
/ 09 января 2010

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

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

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

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

Ответы [ 17 ]

4 голосов
/ 16 марта 2017

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

Позволяет определить, что такое категория?

Это куча объектов!

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

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

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

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

Давайте представим, что '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 берет 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».

То Функтор!

enter image description here

2 голосов
/ 28 декабря 2016

Грубый обзор

В функциональном программировании функтор , по сути, представляет собой конструкцию, в которой обычные унарные функции (т. Е. С одним аргументом) функции между переменными новых типов. Гораздо проще писать и поддерживать простые функции между простыми объектами и использовать функторы для их подъема, а затем вручную писать функции между сложными объектами-контейнерами. Еще одним преимуществом является написание простых функций только один раз, а затем их повторное использование через различные функторы.

Примеры функторов включают массивы, функторы «возможно» и «либо», фьючерсы (см., Например, 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()

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

A Функтор в Теория категорий представляет собой карту между двумя категориями, соблюдающими композицию их морфизмов. В Компьютерный язык основной интересующей категорией является та, чьи объекты имеют типы (определенные наборы значений) и чьи морфизмы являются функциями 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 может означать много вещей, но только одна вещь - это Functor - конструкция типа, отображающая тип a в тип [a] всех массивов типа a. Например, функтор Array отображает тип String на тип [String] (набор всех массивов строк произвольной длины) и устанавливает тип Number на соответствующий тип [Number] (набор все массивы чисел).

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

Array :: a => [a]

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

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

, который отправляет значение в массив из 1 элемента с этим значением в виде одной записи. Эта функция не является частью Array Функтора! С точки зрения этого функтора, 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 голосов
/ 29 ноября 2014

Не противоречит предыдущим теоретическим или математическим ответам, но Функтор также является Объектом (на объектно-ориентированном языке программирования), который имеет только один метод и эффективно используется как функция.

Примером является интерфейс 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, который является ОО-эквивалентом функции из более раннего примера JavaScript.

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

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

0 голосов
/ 08 марта 2016

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

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

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

0 голосов
/ 10 сентября 2014

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

0 голосов
/ 09 января 2010

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

0 голосов
/ 09 января 2010

Проще говоря, функтор или объект функции - это объект класса, который можно вызывать так же, как функцию.

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

...