Функторы, когда я должен их использовать, каково их предполагаемое использование

Я просто не могу обернуть вокруг них голову.

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

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

Я читал, что существует 4 типа функторов:

Сравнитель
Замыкание
Предикат
Преобразователь

Вероятно, нам следует разобраться с каждым из них.

p.s. есть ли что-то подобное в vb?

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

  • Лямбда-выражения - это функторы?
  • Анонимные функции являются функторами?

Но я задал этот вопрос, потому что столкнулся с другим типом функторов, а именно с этими:

delegate void FunctorDelegate(int value);
class Addition {
    FunctorDelegate _delegate;

    public Addition AddDelegate(FunctorDelegate deleg) {
        _delegate += deleg;
        return this;
    }
    public int AddAllElements(IList< int> list) {
        int runningTotal = 0;
        foreach( int value in list) {
            runningTotal += value;
            _delegate(value);
        }
        return runningTotal;
    }
}

И затем вызывая это с этим:

 int runningTotal = new Addition()
     .AddDelegate(new FunctorDelegate(
                     delegate(int value) {
                         if ((value % 2) == 1) {
                             runningOddTotal += value;
                         }
                     }))
    .AddDelegate(new FunctorDelegate(
                     delegate(int value) {
                         if ((value % 2) == 0) {
                             runningEvenTotal += value;
                         }
                     }))
    .AddAllElements(list);

Так что никаких причудливых вещей в стиле лямбда.

Теперь у меня есть этот пример, но совсем не ясно, почему это «хорошее» решение.

Используются ли делегаты (функторы) как лямбда-выражения или анонимные методы «в большинстве случаев» просто как ярлык для программиста? Насколько я вижу, есть только несколько случаев, когда они на самом деле являются предпочтительным выбором для решения проблемы.


person albertjan    schedule 10.06.2009    source источник
comment
Я склонен сказать, что это не хорошее решение; возможно, они пытались показать потенциальное использование, а не типичное использование. Единственная приятная вещь (и я здесь великодушен) в этом коде заключается в том, что он перечисляет данные только один раз, но на это есть и другие ответы. Например, PushLINQ (MiscUtil) позволяет выполнять несколько агрегатов в однократном потоке данных с (IMO) гораздо большей элегантностью.   -  person Marc Gravell    schedule 10.06.2009
comment
хе-хе, этот код должен был показать его потенциальное использование, но даже в этом он плохо справился :). Я разместил его, чтобы объяснить другой тип функтора (анонимный метод), с которым я столкнулся. Но было/неясно, где в моей повседневной жизни это станет РЕШЕНИЕМ. Фредрик немного ответил.   -  person albertjan    schedule 10.06.2009


Ответы (5)


Я думаю, вы путаете термины из разных языков. Кажется, вы используете «Функтор» в смысле С++ или Java, например. см. страницу Википедии. В C++ это объект класса, который перегружает оператор вызова функции, поэтому его можно использовать как функцию, но с состоянием.

Логически это то же самое, что делегат, привязанный к методу экземпляра в C# (или любом другом языке .NET).

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

void MyMethod() { Console.WriteLine("Hi!"); }

void Foo()
{
    Action a = MyMethod;
    a();
}

Во-вторых, вы можете использовать синтаксис анонимных методов, представленный в C# 2.0:

void Foo()
{
    Action a = delegate { Console.WriteLine("Hi!"); }
    a();
}

В-третьих, вы можете использовать лямбда-синтаксис, представленный в C# 3.0:

void Foo()
{
    Action a = () => Console.WriteLine("Hi!");
    a();
}

Преимущество последних двух заключается в том, что тело метода может читать и записывать локальные переменные в содержащем методе.

Преимущество лямбда-синтаксиса перед анонимными методами заключается в том, что он более лаконичен и делает вывод о типах параметров.

Обновление: преимущество анон-методов (ключевое слово delegate) по сравнению с лямбда-выражениями заключается в том, что вы можете вообще опустить параметры, если они вам не нужны:

// correct way using lambda
button.Click += (sender, eventArgs) => MessageBox.Show("Clicked!");

// compile error - wrong number of arguments
button.Click += () => MessageBox.Show("Clicked!");

// anon method, omitting arguments, works fine
button.Click += delegate { MessageBox.Show("Clicked!"); };

Я знаю только одну ситуацию, когда это стоит знать, а именно при инициализации события, чтобы вам не нужно было проверять null перед его запуском:

event EventHandler Birthday = delegate { };

Избегает много ерунды в другом месте.

Наконец, вы упомянули, что существует четыре вида функторов. На самом деле существует бесконечное множество возможных типов делегатов, хотя у некоторых авторов могут быть свои любимые и, очевидно, будут некоторые общие шаблоны. Action или Command не принимает параметров и возвращает void, а предикат принимает экземпляр некоторого типа и возвращает true или false.

В C# 3.0 вы можете создать делегат с четырьмя параметрами любых типов:

Func<string, int, double> f;  // takes a string and an in, returns a double

Re: обновленный вопрос

Вы спрашиваете (я думаю), есть ли много вариантов использования лямбда-выражений. Их больше, чем можно перечислить!

Чаще всего вы видите их в середине более крупных выражений, которые работают с последовательностями (списками, вычисляемыми на лету). Предположим, у меня есть список людей, и мне нужен список людей, которым ровно сорок лет:

var exactlyForty = people.Where(person => person.Age == 40);

Метод Where — это метод расширения интерфейса IEnumerable<T>, где T в данном случае — это какой-то класс Person.

Это известно в .NET как «Linq to Objects», но в других местах известно как чистое функциональное программирование на последовательностях или потоках или «ленивых» списках (все разные названия одного и того же).

person Daniel Earwicker    schedule 10.06.2009
comment
Правильно. Теперь мы получаем где-то. Таким образом, функторы (как в названии) на самом деле не существуют в .net, они являются делегатами. Краткость лямбда - это, я думаю, личный вкус. Я не против напечатать больше, чтобы он был читабельным. Но вывод типа — это очень круто, и это определенно плюс для лямбда-флейвора. Добавлю еще немного к своему вопросу. - person albertjan; 10.06.2009
comment
Спасибо за согласие, я добавил бонусное обновление об исключении аргументов для делегирования. - person Daniel Earwicker; 11.06.2009
comment
Спасибо :) Я думаю, что мы проделали большую работу, прояснив это и приведя аргументы в пользу лямбда-выражений, делегатов и анонимных функций. Там, где я работаю, они о. боятся этого. Но эй, вы не можете остановить будущее правильно. :). - person albertjan; 11.06.2009

С точки зрения .NET, я думаю, что вы описываете, это Delegate, и он существует во всей .NET, а не только в C#.

Я не уверен, что «закрытие» будет «типом» того же, что и компаратор/предикат/преобразователь, поскольку в терминах C# замыкание — это просто деталь реализации, но может быть любым из те трое.

В .NET делегаты используются двумя основными способами:

  • как механизм событий
  • обеспечить программирование в функциональном стиле

Первое важно, но, похоже, вас больше интересует второе. На самом деле они работают так же, как интерфейсы с одним методом... рассмотрим:

List<int> vals = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<int> evenVals = vals.FindAll(i => i % 2 == 0); // predicate
List<string> valsAsStrings = vals.ConvertAll(i => i.ToString()); // transformer
// sort descending
vals.Sort((x, y) => y.CompareTo(x)); // comparer

Замыкание — это больше, когда мы привносим дополнительную область из вне делегата в делегата:

int max = int.Parse(Console.ReadLine()); // perhaps 6
List<int> limited = vals.FindAll(i => i <= max);

здесь max захватывается делегатом как замыкание.

Re "Подготовлены ли классы в рамках для этого?" - многие, и LINQ проделал долгий путь, чтобы сделать это еще шире. LINQ предоставляет методы расширения (например) для всех IEnumerable<T>, а это означает, что коллекции без доступа на основе делегата получают их бесплатно:

int[] data = { 1,2,3,4,5,6,7,8,9 };
var oddData = data.Where( i => i % 2 == 1 );
var descending = data.OrderBy(i => -i);
var asStrings = data.Select(i => i.ToString());

Здесь методы Where и OrderBy являются методами расширения LINQ, которые принимают делегатов.

person Marc Gravell    schedule 10.06.2009
comment
Привет, спасибо за ответ. Я знал, что они имеют отношение к делегатам. Я не думаю, что можно делать какое-либо серьезное программирование на С#, не сталкиваясь с делегатами. В основном я использовал их для асинхронных функций и событий. Но я задал этот вопрос, потому что столкнулся с другой формой функторов. Я не знал, что linq, подобные предикату, также были функторами. Я обновлю вопрос о стилях. - person albertjan; 10.06.2009
comment
Материал LINQ... сложен ;-p Методы расширения в IEnumerable‹T› используют делегаты, но методы расширения в IQueryable‹T› используют деревья выражений, которые выглядят идентично вызывающей стороне (при условии, что они используют либо операторы лямбда, либо синтаксис запроса), но которые полностью, на 100% отличаются ;-p - person Marc Gravell; 10.06.2009
comment
Таким образом, функторами являются только часть выражений linq lamba. хорошо. :) Мне нравится парень, который подумал, что это хорошая идея :P Это работает, и я использую этот материал ежедневно, но как и почему это работает, знают лишь немногие люди :) - person albertjan; 10.06.2009
comment
Нет... это не так; лямбда-делегат — это функтор, как и анонимный метод, как и обычный метод. Разница заключается в тонком различии между Func‹...› и Expression‹Func‹...››: marcgravell.blogspot.com/2009/03/explaining-expression.html - person Marc Gravell; 10.06.2009
comment
Это очень интересный пост, хотя, вероятно, требуется немного больше чтения, чтобы полностью понять его. Как вы сказали, вы многое покрыли в маленьком пространстве. Но теперь определенно яснее, что такое функторы и зачем они. - person albertjan; 10.06.2009

Интересно с терминологией; моя спонтанная интерпретация термина «функтор» заключалась в том, что он относится к анонимным методам. Так что это будет мой взгляд на это.

Вот некоторые из моих типичных применений:

Сравнения (обычно для сортировки списка):

List<int> ints = new List<int>();
ints.AddRange(new int[] { 9, 5, 7, 4, 3, 5, 3 });
ints.Sort(new Comparison<int>(delegate(int x, int y)
    {
        return x.CompareTo(y);
    }));
// yes I am aware the ints.Sort() would yield the same result, but hey, it's just
// a conceptual code sample ;o)

// and the shorter .NET 3.5 version:
ints.Sort((x, y) =>
{
    return x.CompareTo(y);
});

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

Еще одно из моих довольно распространенных применений — это модульное тестирование, когда тест зависит от какого-либо события. Я обнаружил, что это необходимо при модульном тестировании рабочих процессов в Workflow Foundation:

WorkflowRuntime runtime = WorkflowHost.Runtime;  
WorkflowInstance instance = runtime.CreateWorkflow(typeof(CreateFile)); 
EventHandler<WorkflowEventArgs> WorkflowIdledHandler = delegate(object sender, WorkflowEventArgs e)
{
    // get the ICreateFileService instance from the runtime  
    ISomeWorkflowService service = WorkflowHost.Runtime.GetService<ISomeWorkflowService>();

    // set the desired file content  
    service.DoSomeWork(instance.InstanceId, inputData);
};  
// attach event handler
runtime.WorkflowIdled += WorkflowIdledHandler;  

instance.Start();  
// perform the test, and then detach the event handler
runtime.WorkflowIdled -= WorkflowIdledHandler; 

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

Есть и другие случаи, когда я нахожу это в своем коде, но обычно у них есть одна или две общие вещи:

  • Я не заинтересован в том, чтобы ссылаться на этот фрагмент кода где-либо еще, кроме этого конкретного места.
  • Метод нуждается в доступе к данным, которые недоступны для обычного метода
person Fredrik Mörk    schedule 10.06.2009
comment
Это больше похоже на то, что я изначально считал функторами. Я думаю, что последние два предложения вашего поста действительно затронули меня, это объясняет, почему я буду использовать функторы/анонимные методы/лямбда-выражение. - person albertjan; 10.06.2009

Реальный ответ заключается в том, что функтор — это тип математического объекта, который по-разному «овеществляется» разными языками. Например, предположим, что у вас есть объект-контейнер, в котором хранится множество других объектов того же типа. (Например, набор или массив). Затем, если бы в вашем языке был метод, который позволял бы вам «сопоставлять» контейнер, чтобы вы могли вызывать метод для каждого объекта в контейнере, тогда контейнер был бы функтор.

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

У каждого языка есть свой способ сделать это, и иногда они объединяют использование. Например, C++ использует указатели на функции для представления «передачи» метода и называет указатель на функцию «функтором». Делегаты - это просто дескриптор методов, которые вы можете передать. Вы используете терминологию "неправильно", как это делает С++.

Haskell понимает это правильно. Вы объявляете, что тип реализует интерфейс функтора, а затем получаете метод сопоставления.

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

person nomen    schedule 24.11.2015

Я уверен, что вы имеете в виду лямбда-выражения. Это небольшие функции, которые вы можете написать очень быстро, и они имеют характерный оператор "=>". Это новая функция C# 3.0.

Этот пример будет классическим трансформером; чтобы использовать его, нам нужен делегат для определения подписи для лямбда-функции.

delegate int Transformer(int i);

Теперь объявите Lambda с этим делегатом:

Transformer sqr = x => x * x;

Мы можем использовать его как обычную функцию:

Console.WriteLine(sqr(3)); //9

Они часто используются в запросах LINQ, например, для сортировки (сравнитель), для поиска (предикат).

Книга "Карманный справочник по С#" (помимо того, что, на мой взгляд, является лучшей, содержит очень хорошую часть по лямбда-выражениям. (ISBN 978-0-596-51922-3)

person wsd    schedule 10.06.2009
comment
В стороне - с .NET 3.5 было бы более распространено использовать Func‹int,int›, чем определять новый делегат Transformer, чтобы делать то же самое... - person Marc Gravell; 10.06.2009
comment
Это не только лямбда-выражения. Как мы установили (я думаю), только часть или лямбда-выражения на самом деле являются функторами. - person albertjan; 10.06.2009