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