Функтор (программирование)
Функциональный объект (англ. function object ), так же функтор, функционал и функционоид — распространённая в программировании конструкция, позволяющая использовать объект как функцию. Часто используется как callback, делегат, либо как замена лямбда-выражениям в нефункциональных языках программирования.
Функтором представления называется функтор (англ. functor ), описывающий отображение между математическим понятием (множество, функция) и его реализацией на языке программирования (соответственно, множество, функция) [1] .
C
В C для создания функционального объекта используются указатели на функцию:
/* callback-функция */ int compare_function(int A, int B) return A B; > /* объявление функции сортировки */ void sort_ints(int* begin_items, int num_items, int (*cmpfunc)(int, int)); int main(void) int items[] = 4, 3, 1, 2>; sort_ints(items, sizeof(items)/sizeof(int), compare_function); return 0; >
С++
В C++ функциональный объект создаётся с помощью класса, у которого перегружен operator():
class compare_class public: bool operator()(int A, int B) return (A B); > >; // объявление функции сортировки template class ComparisonFunctor> void sort_ints(int* begin_items, int num_items, ComparisonFunctor c); int main() int items[] = 4, 3, 1, 2>; compare_class functor; sort_ints(items, sizeof(items)/sizeof(int), functor); >
С# и VB.NET
В C# и Visual Basic .NET для программирования функторов используются делегаты.
Java
Поскольку в Java функции не являются объектами первого класса, функциональные объекты представляют собой интерфейс с единственным методом, как правило, реализованный в виде безымянного вложенного класса):
ListString> list = Arrays.asList("10", "1", "20", "11", "21", "12"); Collections.sort(list, new ComparatorString>() public int compare(String o1, String o2) return Integer.valueOf(o1).compareTo(Integer.valueOf(o2)); > >);
Haskell
В Haskell функтором называется класс типов (в общеизвестной терминологии — интерфейс), который декларирует единственный метод «fmap». Интуитивно, «fmap» применяет функцию a -> b к значению типа f a, чтобы получить значение типа f b. С другой стороны, можно рассматривать «fmap» как функцию высшего порядка, преобразующую «простую» функцию a -> b в «составную» функцию f a -> f b. Важно отметить, что структура значения типа f после применения «fmap» должна оставаться неизменной.
class Functor f where fmap :: (a -> b) -> f a -> f b
Тривиальные примеры использования:
plusOne x = x + 1 numberList = [1, 2, 3, 4, 5] newNumberList = fmap plusOne numberList -- newNumberList == [2, 3, 4, 5, 6] square :: Int -> Int square x = x * x -- | 'Set' data type requires "Data.Set" library. squareAllSetElements :: Set Int -> Set Int squareAllSetElements set = fmap square set
Функтор может быть определён практически для любого параметрически полиморфного типа.
Примечания
- ↑Толковый словарь по вычислительным системам, 1990
Ссылки
- Функторы в STL Programmer’s Guide (англ.)
- Thomas BeckerSTL & Generic Programming: STL Function Objects and Their Adaptors (англ.) // Dr. Dobb’s Journal. — 2002.
- Herb SutterGeneralized Function Pointers (англ.) // Dr. Dobb’s Journal. — 2003.
- Описание функторов в Portland Pattern Repository (англ.)
- Толковый словарь по вычислительным системам / Под ред. В. Иллингуорта и др.. — М .: Машиностроение, 1990. — 560 с. — ISBN 5-217-00617-X
- Проставив сноски, внести более точные указания на источники.
- Концепции языков программирования
Функциональные объекты. STL (часть 11)
В предыдущих обсуждениях уже неоднократно мелькал такой термин как функтор, но особую актуальность он приобретает применительно к алгоритмам. Теперь пришло время разобраться с этим понятием. Функтор — это сокращение от функциональный объект, представляющий собой конструкцию, позволяющую использовать объект класса как функцию. В C++ для определения функтора достаточно описать класс, в котором переопределена операция () .
То, как из объекта образуется функция, легко показать на таком простом примере:
using namespace std ;
class summator : private vector < int >< summator ( const vector < int >& ini ) < for ( auto x : ini ) this -> push_back ( x ) ;
int operator ( ) ( bool even ) < int sum = 0 ; auto i = begin ( ) ; if ( even ) i ++ ; while ( i < end ( ) ) < if ( i == end ( ) ) break ; return sum ; int main ( void ) < setlocale ( LC_ALL , "rus" ) ; summator sums ( vector < int >( < 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 >) ) ;
Уже из такого простого примера видно следующее: операция () в классе может быть переопределена (точнее определена, поскольку она не имеет реализации по умолчанию) с произвольным числом, типом параметров и типом возвращаемого значения (или даже вовсе без возвращаемого значения). В итоге:
Выгода функтора состоит в том, что а). его можно параметризовать при создании объекта (перед вызовом) используя конструктор объекта с параметрами и б). может создаваться временный объект исключительно на время выполнения функционального вызова. Это иллюстрируется примером такого упрощённого целочисленного калькулятора:
Функторы в языках программирования
Интересно, что термин «функтор» означает совершенно разные вещи в разных языках программирования. Возьмем, например, C++. Каждый, кто освоил мастерство C++, знает, что класс, который реализует operator() , называется функтором. Теперь возьмём Standard ML. В ML функторы отображают структуры на структуры. Теперь Haskell. В Haskell функторы — это просто гомоморфизм над категориями. А в Prolog функтор означает атом в начале структуры. Все они различаются. Давайте подробнее рассмотрим каждый из них.
Функторы в C++
Функторы в C++ являются сокращением от «функциональные объекты«. Функциональный объект является экземпляром класса С++, в котором определён operator() . Если вы определите operator() для C++ класса, то вы получите объект, который действует как функция, но может также хранить состояние. Например,
#include #include class SimpleFunctor < std::string name_; public: SimpleFunctor(const char *name) : name_(name) <>void operator()() < std::cout >; int main() < SimpleFunctor sf("catonmat"); sf(); // выводит "Oh, hello, catonmat" >
Обратите внимание, что мы можем вызывать sf() в функции main , хотя sf является объектом. Это потому, что в классе SimpleFunctor для него определён operator() .
Чаще всего функторы в С++ используются в качестве предикатов, псевдозамыканий или функций сравнения в алгоритмах STL. Вот вам ещё один пример. Предположим, у вас есть список целых чисел и вы хотите найти сумму всех четных и сумму всех нечетных. Идеальная задача для функтора и for_each .
#include #include #include class EvenOddFunctor < int even_; int odd_; public: EvenOddFunctor() : even_(0), odd_(0) <>void operator()(int x) < if (x%2 == 0) even_ += x; else odd_ += x; >int even_sum() const < return even_; >int odd_sum() const < return odd_; >>; int main() < EvenOddFunctor evenodd; int my_list[] = < 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 >; evenodd = std::for_each(my_list, my_list+sizeof(my_list)/sizeof(my_list[0]), evenodd); std::cout
Здесь экземпляр EvenOddFunctor передается в for_each . for_each итерируется по каждому элементу в my_list и вызывает функтор. После этого он возвращает копию функтора evenodd , который содержит сумму чётных и нечётных элементов.
Функторы в Standart ML
Сложно сформулировать в терминах ООП: функторы в ML являются общими реализациями интерфейсов. В терминах ML функторы являются частью системы модулей ML и они позволяют компоновать структуры.
Например, предположим, что вы хотите написать систему плагинов, и вы хотите, чтобы все плагины реализовывали необходимый интерфейс, который, для простоты, включает в себя только функцию perform . В ML необходимо сначала задать сигнатуру для плагинов,
signature Plugin =
sig
val perform : unit -> unit
end ;
Теперь, когда мы определили интерфейс (сигнатуру) для плагинов, мы можем реализовать два плагина, скажем, LoudPlugin и SilentPlugin . Реализация осуществляется через структуры,
structure LoudPlugin :> Plugin =
struct
fun perform ( ) = print «ВЫПОЛНЯЕМ ЗАДАНИЕ ГРОМКО!\n»
end ;
structure SilentPlugin :> Plugin =
struct
fun perform ( ) = print «выполняем задание тихо\n»
end ;
Теперь мы приблизились к функторам. Функторы в ML принимают структуры в качестве аргументов, поэтому мы можем написать, что в качестве аргумента требуется Plugin ,
functor Performer ( P : Plugin ) =
struct
fun job ( ) = P . perform ( )
end ;
Этот функтор принимает Plugin в качестве аргумента P и использует его для функции job , которая вызывает функцию perform у плагина P .
Теперь давайте попробуем использовать функтор Performer . Помните, что функтор возвращает структуру,
structure LoudPerformer = Performer ( LoudPlugin ) ;
structure SilentPerformer = Performer ( SilentPlugin ) ;
LoudPerformer . job ( ) ;
SilentPerformer . job ( ) ;
ВЫПОЛНЯЕМ ЗАДАНИЕ ГРОМКО! выполняем задание тихо
Это, скорее всего, самый простейший пример функторов Standard ML.
Функторы в Haskell
Функторы в Haskell является тем, чем и должны быть настоящие функторы. Функторы Haskell очень напоминают математические функторы из теории категорий. В теории категорий функтор F — это отображение между категориями, такое, что структура категории сохраняется или, другими словами, это гомоморфизм между двумя категориями.
В Haskell это определение реализовано в виде простого класса типа,
class Functor f where
fmap :: ( a -> b ) -> f a -> f b
Оглядываясь, например, на ML, класс типа в Haskell подобен сигнатуре, за исключением того, что он определен для типа. Он определяет, какие операции должен реализовать тип, чтобы быть экземпляром данного класса. В данном случае, однако, Functor определен не над типами, а над конструктором типа f . Это значит, Functor — это то, что реализует функцию fmap , которая принимает функцию (принимающую тип a и возращающую тип b ) и значение типа f a (тип построеный из конструктора типа f , применяемый к типу a ) и возвращает значение типа f b .
Чтобы понять, что он делает, думайте о fmap как о функции, которая применяет операцию к каждому элементу в каком-то контейнере.
Простейший пример функторов — это обычные списки и функция map , которая применяет функцию к каждому элементу в списке.
Prelude> map (+1) [1,2,3,4,5]
[2,3,4,5,6]
В этом простом примере, функцией Functor ‘а fmap является просто map и конструктором типа f является [] — конструктор типа списка. Поэтому Functor , например, для списков определяется как
instance Functor [ ] where
fmap = map
Давайте посмотрим, что это действительно верно, используя fmap вместо map ,
Prelude> fmap (+1) [1,2,3,4,5]
[2,3,4,5,6]
Но заметьте, что определение Functor ничего не говорит о сохранении структуры! Поэтому любой нормальный функтор должен неявно удовлетворять законам функторов, которые являются частью определения математических функторов. Есть два правила fmap :
fmap > fmap (g. h) = fmap g. fmap h
Первое правило гласит, что отображение тождественной функции на каждый элемент в контейнере не имеет никакого эффекта. Второе правило гласит, что композиция двух функций над каждым элементом в контейнере то же самое, что отображение первой функции, а затем отображение второй.
Другой пример функторов, который иллюстрирует их наиболее ярко, — это операции над деревьями. Подумайте о дереве, как контейнере, а затем примените функцию fmap к значениям дерева, сохраняя структуру дерева.
Давайте, для начала, определим дерево,
data Tree a = Node ( Tree a ) ( Tree a )
| Leaf a
deriving Show
В этом определении сказано, что тип Tree (дерево) является либо Node (ветвью) из двух Tree (левой и правой ветви) или Leaf (листом). Выражение deriving Show позволяет нам осматривать дерево через функцию show.
Теперь мы можем определить Functor над деревьями Tree ,
instance Functor Tree where
fmap g ( Leaf v ) = Leaf ( g v )
fmap g ( Node l r ) = Node ( fmap g l ) ( fmap g r )
В этом определении сказано, что fmap от функции g над Leaf со значением v — это просто Leaf из g , применяемого к v . А fmap от g над Node с левой ветвью l и правой r — это просто Node из fmap , применяемого к значениям левой и правой ветви.
Теперь давайте проиллюстрируем, как fmap работает с деревьями. Построим дерево со строковыми ( String ) листьями и применим функцию length над ними, чтобы узнать длину каждого листа.
Prelude> let tree = (Node (Node (Leaf «hello») (Leaf «foo»)) (Leaf «baar»))
Prelude> fmap length tree
Node (Node (Leaf 5) (Leaf 3)) (Leaf 4)
Здесь у нас построено следующее дерево,
* / \ / \ * "baar" / \ / \ / \ / \ "hello" "foo"
И отображение length над ним даёт нам,
* / \ / \ * 4 / \ / \ / \ / \ 5 3
Еще один способ сказать, что fmap делает — отображает (в оригинале — поднимает) функцию из «нормального мира» в » f мир».
На самом деле функтором является фундаментальным классом типа в Haskell так как Монады, аппликативные функторы и стрелки — это всё функторы. Я бы сказал, что Haskell начинается там, где начинаются функторы.
Если вы хотите узнать больше о классах типа в Haskell, начните с превосходной статьи Typeclassopedia (начинается со стр. 17).
Функторы в Prolog
И, наконец, функторы в Prolog. Функторы в Prolog самые простые из всех. Можно рассмотреть два примера. Первый — это атом в начале структуры. Например, возьмём выражение,
?- likes ( mary , pizza )
функтором является первый атом — likes .
Второй — это встроенный предикат под названием functor . Он возвращает арность и функтор структуры. Например,
?- functor ( likes ( mary , pizza ) , Functor , Arity ) .
Functor = likes
Arity = 2
Вот вам и функторы в Prolog.
Заключение
В данной статье показано, как такой простой термин, как «функтор» может относиться к совершенно к разным вещам в разных языках программирования. Поэтому, когда вы слышите термин «функтор», важно знать контекст, в котором оно используется.
И, тут такое дело… мне тут сказали, что, оказывается, человек, чью статью я перевёл, есть на хабре =)
А ещё говорят, что по часть по Prolog в статье ошибочна.
Функторы в Python
Функциональные объекты, или функторы в программировании — объекты, которые можно вызывать, подобно функциям. В этой статье мы разберемся, как создавать функторы и какие возможности они открывают.
Как создать функтор?
Как известно, объекты в Python являются экземплярами класса. Значит, для создания функторов нам тоже нужен класс. Однако обычно объекты нельзя вызвать.
a = 1 # TypeError: 'int' object is not callable
Для того чтобы расширить функциональность объекта, нам понадобится переопределить магический метод __call__ . Он отвечает за поведение объекта при вызове.
class Functor: def __call__(self, *args): print('Вызов функтора с аргументами:', *args) functor = Functor() functor(1, 'q') # Вызов функтора с аргументами: 1 q
При вызове наш функтор выполняет ровно то, что мы записали в метод __call__ . Но что толку от функтора, если он заменяется простой функцией? Ведь нам доступны все возможности, которые можно реализовать с помощью класса!
Функтор, как замена замыкания и частичного применения функции
В статье о замыканиях мы упоминали, что их можно заменить на функторы. Давайте возьмем оттуда функцию для подбора имени ребенка, перепишем в виде функтора и улучшим.
class SetNames: def __init__(self, names): self.names = [] self.results = dict() self.add(names) def __call__(self, surname): result = self.results.get(surname) if self.results == dict(): self.results.update(surname: []>) for name in self.names: self.results[surname].append( f'name> surname>' ) result = self.results[surname] return result def add(self, lst: list): for i in lst: if not isinstance(i, str): raise TypeError( 'Имена должны быть строками' ) self.results.clear() self.names.extend(lst) girl_names = SetNames(('Julia', 'Lisa')) girl_names.add(['Inna', 'Anna']) print(girl_names('Koval')) # ['Julia Koval', 'Lisa Koval', 'Inna Koval', 'Anna Koval']
Наш функтор лишен проблем с поздним связыванием, характерных для замыканий. Теперь мы можем на ходу добавить в него парочку новых имен. Кроме того, результаты сохраняются, и если мы запросим список для той же фамилии снова, то сразу получим готовый результат.
Классы-декораторы
Поскольку функторы можно вызывать, само собой напрашивается использование их классов в роли декораторов.
from datetime import datetime class Validation: def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): res = self.function(*args, **kwargs) if not 0 res 100: raise ValueError( 'Validation failed' ) return f'datetime.now()>: res>' @Validation def square(a): return a**2 print(square(8)) # 2022-10-29 09:24:29.503697: 64 print(square(12)) # ValueError: Validation failed
Конструктор нашего класса-декоратора принимает только один аргумент — функцию. В методе __call__ мы проверяем, находится ли возвращаемое значение в диапазоне от 0 до 100 . Если условие не выполняется, мы вызываем ошибку, а иначе просто возвращаем строку с текущим временем и полученным числом. Но можно пойти дальше, и сделать декоратором объект класса.
class Validation: def __init__(self, lower, upper): self._lower = lower self._upper = upper def __call__(self, arg): def wrapper(a): res = arg(a) if not self._lower res self._upper: raise ValueError( 'Validation failed' ) return f'datetime.now()>: res>' return wrapper @Validation(0, 10) def square(a): return a**2 print(square(2)) # 2022-10-29 09:41:59.377104: 4 print(square(4)) # ValueError: Validation failed
Теперь мы можем передавать границы интервала напрямую в декоратор.
Классами-декораторами можно декорировать как функции, так и другие классы. Они часто встречаются в модулях, и легко отличаются от обычных тем, что их названия записаны в стиле CamelCase.
Заключение
Функторы бывают очень полезны. Они значительно более гибкие, чем функции, поскольку могут содержать дополнительные атрибуты и методы, наследоваться от других классов и иметь наследников. Особо важно то, что функторы могут выступать в роли декораторов, дополняя функциональность функций и классов.
Практический Python для начинающих
Станьте junior Python программистом за 7 месяцев