C # Linq против карри - PullRequest
       5

C # Linq против карри

15 голосов
/ 12 января 2012

Я немного играю с функциональным программированием и различными его концепциями.Все это очень интересно.Несколько раз я читал о карри и о том, какое это имеет преимущество.

Но я не понимаю с этим.Следующий источник демонстрирует использование концепции карри и решения с помощью linq.На самом деле, я не вижу никаких признаков использования концепции карри.

Итак, в чем преимущество карри?

static bool IsPrime(int value)
{
    int max = (value / 2) + 1;
    for (int i = 2; i < max; i++)
    {
        if ((value % i) == 0)
        {
            return false;
        }
    }
    return true;
}

static readonly Func<IEnumerable<int>, IEnumerable<int>> GetPrimes = 
        HigherOrder.GetFilter<int>().Curry()(IsPrime);

static void Main(string[] args)
{
    int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

    Console.Write("Primes:");
    //Curry
    foreach (int n in GetPrimes(numbers))
    {
        Console.Write(" {0}", n);
    }
    Console.WriteLine();

    //Linq
    foreach (int n in numbers.Where(p => IsPrime(p)))
    {
        Console.Write(" {0}", n);
    }

    Console.ReadLine();
}

Вот метод фильтра HigherOrder:

public static Func<Func<TSource, bool>, IEnumerable<TSource>, IEnumerable<TSource>> GetFilter<TSource>()
{
    return Filter<TSource>;
}

Ответы [ 3 ]

39 голосов
/ 12 января 2012

В чем преимущество карри?

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

Итак, если у вас есть метод:

static int Add(int x, int y) { return x + y; }

Вы можете назвать это так:

int result = Add(2, 3); // 5

Вы можете использовать метод Add:

static Func<int, int> MakeAdder(int x) { return y => Add(x, y); }

и сейчас:

Func<int, int> addTwo = MakeAdder(2);
int result = addTwo(3); // 5

Частичное приложение иногда также называют «карри», когда речь идет неформально, потому что оно явно связано:

Func<int, int> addTwo = y=>Add(2,y);
int result = addTwo(3);

Вы можете сделать машину, которая сделает этот процесс за вас:

static Func<B, R> PartiallyApply<A, B, R>(Func<A, B, R> f, A a)
{
    return (B b)=>f(a, b);
}
...
Func<int, int> addTwo = PartiallyApply<int, int, int>(Add, 2);
int result = addTwo(3); // 5

Итак, теперь мы подходим к вашему вопросу:

В чем преимущество использования карри?

Преимущество любой техники в том, что она дает вам больше гибкости в работе с методами.

Например, предположим, что вы пишете реализацию алгоритма поиска пути. Возможно, у вас уже есть вспомогательный метод, который дает приблизительное расстояние между двумя точками:

static double ApproximateDistance(Point p1, Point p2) { ... }

Но когда вы на самом деле строите алгоритм, вам часто нужно знать, каково расстояние между текущим местоположением и фиксированной конечной точкой . В чем нуждается алгоритм равен Func<Point, double> - каково расстояние от местоположения до фиксированной конечной точки? Что у вас есть Func<Point, Point, double>. Как вы собираетесь превратить то, что у вас есть, в то, что вам нужно? С частичным применением; Вы частично применяете фиксированную конечную точку в качестве первого аргумента к методу приблизительного расстояния, и вы получаете функцию, которая соответствует потребностям вашего алгоритма поиска пути:

Func<Point, double> distanceFinder = PartiallyApply<Point, Point, double>(ApproximateDistance, givenEndPoint);

Если метод ApproximateDistance был карри в первую очередь:

static Func<Point, double> MakeApproximateDistanceFinder(Point p1) { ... }

Тогда вам не нужно будет подавать частичное заявление самостоятельно; Вы просто позвоните MakeApproximateDistanceFinder с фиксированной конечной точкой, и все будет готово.

Func<Point, double> distanceFinder = MakeApproximateDistanceFinder(givenEndPoint);
5 голосов
/ 12 января 2012

Комментарий @Eric Lippert к В чем преимущество карри в C #? (достижение частичной функции) указывает на это сообщение в блоге:

Применение карри и частичной функции

Где я нашел это лучшее объяснение, которое работает для меня:

С теоретической точки зрения это интересно, поскольку оно (карри) упрощает лямбда-исчисление включает в себя только те функции, которые имеют не более один аргумент. С практической точки зрения, это позволяет программисту генерировать семейства функций из базовой функции, фиксируя первые k аргументов. Это сродни закреплению чего-то на стене это требует двух контактов. Перед закреплением объект свободен двигаться в любом месте на поверхности; однако, когда первый пин-код вставлен в тогда движение ограничено. Наконец, когда второй пин ставится тогда больше нет свободы передвижения. Точно так же, когда программист каррирует функцию двух аргументов и применяет ее к Сначала аргумент, а затем функциональность ограничена одним измерением. Наконец, когда он применяет новую функцию ко второму аргументу, тогда определенное значение вычисляется.

Продолжая это, я вижу, что функциональное программирование по существу вводит «программирование потока данных, а не потока управления», это похоже на использование, скажем, SQL вместо C #. С этим определением я вижу , почему LINQ равен и почему у него много приложений вне чистых объектов Linq2Objects - таких как события в Rx.

1 голос
/ 12 января 2012

Преимущество использования карри в основном можно найти в функциональных языках, которые созданы для того, чтобы извлечь выгоду из карри, и имеют удобный синтаксис для этой концепции. C # не является таким языком, и реализации карри в C # обычно трудно понять, как и выражение HigherOrder.GetFilter<int>().Curry()(IsPrime).

...