C # лямбда - карри - PullRequest
       17

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

13 голосов
/ 06 февраля 2009

Я прочитал Эта статья , и мне она показалась интересной.

Подводя итог для тех, кто не хочет читать весь пост. Автор реализует функцию более высокого порядка с именем 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 ответов я понимаю, что выигрыш будет заключаться в том, что в некоторых случаях, когда мы создаем новую функцию из карри, некоторые параметры не оцениваются повторно. Я провел небольшой тест на C # (имейте в виду, что меня интересует только реализация C #, а не теория карри в целом):

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

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

Ответы [ 6 ]

18 голосов
/ 06 февраля 2009

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

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

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

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

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

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

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

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

12 голосов
/ 06 февраля 2009

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

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

Edit:

Есть две причины, по которым карри.


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

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

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

Постановочный расчет

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

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


Заключение

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

0 голосов
/ 31 августа 2012

У меня есть этот глупый пример: Uncurry версия:

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

Функция карри:

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

Веселись!

0 голосов
/ 06 февраля 2009

вот еще один пример того, как вы можете использовать функцию 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;
    }
0 голосов
/ 06 февраля 2009

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

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

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

0 голосов
/ 06 февраля 2009

В некотором смысле, карринг - это техника включить автоматическое частичное применение.

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

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

из потока в кодирующих форумах

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...