С# лямбда - карри

Я читал Эта статья, и мне было интересно.

Подвести итоги тем, кто не хочет читать весь пост. Автор реализует функцию более высокого порядка, названную Curry (это отредактировано мной без его внутреннего класса):

 public static Func<T1, Func<T2, TResult>> 
             Curry<T1, T2, TResult>(this Func<T1, T2, TResult> fn)
 {
     Func<Func<T1, T2, TResult>, Func<T1, Func<T2, TResult>>> curry = 
     f => x => y => f(x, y);
     return curry(fn);
 }

Это дает нам возможность взять выражение, подобное F (x, y) например.

Func<int, int, int> add = (x, y) => x + y;

и назовем его способом F.Curry() (x) (y);

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

Спасибо заранее.

Отредактировано:  После первых 3 ответов я понимаю, что коэффициент усиления будет заключаться в том, что в некоторых случаях, когда мы создаем новую функцию из curries, некоторые параметры не оцениваются. Я сделал этот небольшой тест на С# (имейте в виду, что меня интересует только реализация С#, а не теория карри вообще):

public static void Main(string[] args)
{
    Func<Int, Int, string> concat = (a, b) => a.ToString() + b.ToString();
    Func<Int, Func<Int, string>> concatCurry = concat.Curry();
    Func<Int, string> curryConcatWith100 = (a) => concatCurry(100)(a);

    Console.WriteLine(curryConcatWith100(509));
    Console.WriteLine(curryConcatWith100(609));
}

    public struct Int
    {
        public int Value {get; set;}

        public override string ToString()
        {
             return Value.ToString();
        }

        public static implicit operator Int(int value)
        {
            return new Int { Value = value };
        }
    }

При двух последовательных вызовах curryConcatWith100 оценка ToString() для значения 100 вызывается дважды (один раз для каждого вызова), поэтому я не вижу никакого усиления в оценке здесь. Я что-то пропустил?

Ответы

Ответ 1

Легче сначала рассмотреть fn (x, y, z). Это можно сделать карри, используя fn (x, y), предоставляя вам функцию, которая принимает только один параметр, z. Все, что нужно сделать с помощью x и y, может быть выполнено и сохранено закрытием, на которое возвращается возвращаемая функция.

Теперь вы вызываете возвращенную функцию несколько раз с различными значениями для z, не перекомпилируя часть требуемых x и y.

Edit:

Существует две причины для карри.


Уменьшение параметров

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

С Lambdas, присутствующим на С#, это имеет ограниченное значение, поскольку они могут обеспечить этот эффект в любом случае. Хотя это вы используете С# 2, функция Curry в вашем вопросе имеет гораздо большую ценность.

Расчет этапа

Другая причина карри - это, как я сказал ранее. Чтобы позволить сложным/дорогим операциям быть поставленным и повторно использоваться несколько раз, когда конечный параметр (параметры) подаются к функции curried.

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


Заключение

Уменьшение параметров с помощью курри, которое вы упомянули, полезно в С# 2, но значительно уменьшено в С# 3 из-за Lambdas.

Ответ 2

Currying используется для преобразования функции с параметрами x в функцию с параметрами y, поэтому ее можно передать другой функции, которая нуждается в функции с параметрами y.

Например, Enumerable.Select(this IEnumerable<T> source, Func<TSource, bool> selector) принимает функцию с 1 параметром. Math.Round(double, int) - функция, имеющая 2 параметра.

Вы можете использовать currying для "сохранения" функции Round в качестве данных, а затем передать эту функцию с курсивом в Select так, чтобы

Func<double, int, double> roundFunc = (n, p) => Math.Round(n, p);
Func<double, double> roundToTwoPlaces = roundFunc.Curry()(2);
var roundedResults = numberList.Select(roundToTwoPlaces);

Проблема здесь в том, что есть также анонимные делегаты, которые делают излишнее currying. Фактически анонимные делегаты - это форма каррирования.

Func<double, double> roundToTwoPlaces = n => Math.Round(n, 2);
var roundedResults = numberList.Select(roundToTwoPlaces);

Или даже просто

var roundedResults = numberList.Select(n => Math.Round(n, 2));

Currying был способом решения конкретной проблемы с учетом синтаксиса определенных функциональных языков. С анонимными делегатами и лямбда-оператором синтаксис в .NET намного проще.

Ответ 3

В некотором смысле, curring - это метод для включить автоматическое частичное приложение.

Более формально, каррирование - это метод превратить функцию в функцию который принимает один и только один аргумент.

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

из потока в codingforums

Мне особенно нравятся объяснения и длина, на которых это объясняется на этой странице .

Ответ 4

Один пример: у вас есть функция compare(criteria1, criteria2, option1, option2, left, right). Но когда вы хотите предоставить функцию compare для некоторого метода с сортировкой списка, тогда compare() должен принимать только два аргумента, compare(left, right). С карри вы связываете аргументы критериев так, как вам нужно, для сортировки этого списка, а затем, наконец, эта высоконастраиваемая функция представляет алгоритм сортировки как любой другой простой compare(left,right).

Подробно:.NET-делегаты используют неявное каррирование. Каждая нестатическая функция-член класса имеет неявную ссылку this, но при написании делегатов вам не нужно вручную использовать некоторую currying для привязки this к функции. Вместо этого С# заботится о синтаксическом сахаре, автоматически связывает это и возвращает функцию, которая требует только оставшихся аргументов.

В С++ boost:: bind et al. используются для того же самого. И, как всегда, на С++ все немного более явным (например, если вы хотите передать функцию-член-экземпляр в качестве обратного вызова, вам нужно явно привязать this).

Ответ 5

У меня есть этот глупый пример: Версия для несанкционированного доступа:

void print(string name, int age, DateTime dob)
{
    Console.Out.WriteLine(name);
    Console.Out.WriteLine(age);
    Console.Out.WriteLine(dob.ToShortDateString());
    Console.Out.WriteLine();
}

Функция Curry:

public Func<string, Func<int, Action<DateTime>>> curry(Action<string, int, DateTime> f)
{
    return (name) => (age) => (dob) => f(name, age, dob);
}

Использование:

var curriedPrint = curry(print);
curriedPrint("Jaider")(29)(new DateTime(1983, 05, 10)); // Console Displays the values

Удачи!

Ответ 6

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

    void ArchiveAndUpdate(string[] files)
    {
        Func<string, bool> archiveCurry1 = (file) =>
            Archive1(file, "archiveDir", 30, 20000000, new[] { ".tmp", ".log" });

        Func<string, bool> archiveCurry2 = (file) =>
            Archive2("netoworkServer", "admin", "nimda", new FileInfo(file));

        Func<string, bool> archvieCurry3 = (file) => true;

        // backup locally before updating
        UpdateFiles(files, archiveCurry1);

        // OR backup to network before updating
        UpdateFiles(files, archiveCurry2);

        // OR do nothing before updating
        UpdateFiles(files, archvieCurry3);
    }

    void UpdateFiles(string[] files, Func<string, bool> archiveCurry)
    {
        foreach (var file in files)
        {
            if (archiveCurry(file))
            {
                // update file //
            }
        }
    }

    bool Archive1(string fileName, string archiveDir, 
        int maxAgeInDays, long maxSize, string[] excludedTypes)
    {
        // backup to local disk
        return true;
    }

    bool Archive2(string sereverName, string username, 
        string password, FileInfo fileToArchvie)
    {
        // backup to network
        return true;
    }