Comments 37
спасибо, статья интересная.
но вот «частичная аппликация» — не лучше ли все же «частичное применение»?
так как «аппликация» четко ассоциируется с другим. а вот функцию как-раз таки «применяют».
или я не прав?
Не уверен… по крайней мере я слышал только 'частичное применение'.
Примеры в статье лично меня скорее запутали, чем что-то пояснили, но за ссылку на термин в Википедии спасибо. Всю жизнь говорил прозой, и не знал :-) Давно использую похожие трюки по мере надобности.

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

Вот не уверен, какое из пониманий верно:

1. Метод links_Core::get($key, ...) является каррированием для links_Core::get_csrf($csrf_token, $key, ...).

2. Метод links_Core::get($key, ...) являлся бы каррирование для links_Core::get_csrf($csrf_token, $link), где $link — результат функции links_Core::get($key, ...).

Хотя в общем-то не важно, какой из вариантов формально верен. Важно, что реализации идеи встречаются на практике.

class links_Core
{
private static $links = array();

public function get($key)
{
if(empty(self::$links))
self::$links = Kohana::config('url/my_project.links', false, true);

if(!isset(self::$links[$key]))
return false;

$args = func_get_args();
array_shift($args);
array_unshift($args, self::$links[$key]);

return url::base().call_user_func_array('sprintf', $args);
}

public function get_csrf($csrf_token, $key)
{
$args = func_get_args();
array_shift($args);
$link = call_user_func_array(array('self', 'get'), $args);

if(strpos($link, '?') !== false)
return $link.'&csrf='.$csrf_token;
else
return $link.'?csrf='.$csrf_token;
}
}


* This source code was highlighted with Source Code Highlighter.
Конкретно мне такие статьи позволяют лучше ориентироваться в функциональном стиле кодирования. Кроме того, если бы речь шла не о сложении, а о более сложном алгоритме, то каррированием, как я понимаю, можно упростить реализацию, тестирование и использование.

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

С другой стороны, вот все кто с LINQ работал должны знать такой метод:

msdn.microsoft.com/ru-ru/library/bb548658.aspx

Жесть, правда? А надо както с этим работать. Вот, умение преобразовывать функции тут как раз помогает. В том числе и понимание карринга.
можно посмотреть на F#

например, там можно делать композицию функций:

readFile «c:\test.txt» |> addToEachLine «1» |> writeFile «c:\test2.txt»

операция оператор |> берет две функции и применяет результат первой ко второй

благодаря каррированию writeFile превращается из функции с двумя аргументами в функцию с одним аргументом и ее можно подвать на вход |>

В F# большинство встроенных функций для работы со списками, например, сначала бедур дополнительные аргументы а потом сам список. В результате из них легко каррированием получаются функции обработки с одним аргуметом «список»
Или последовательность.

Например seq.map берет функцию и список и последовательность, применяет функию ко всем аргументам последовательности и возвращает последовательность результатов.

тогда Seq.map (fun x -> x + 1) вернет функцию, которя к заданной последовательности прибавит единицу.

И такие функции удобно выстраивать в цепочкит типа

[1; 2; 3] |> List.filter (fun x -> x % 2 = 0) |> List.map (fun x-> x + 1)
Предлагаю заспамить авторов F# требованием добавить возможность перегрузки по возвращаемому значению. Без этого каррированными функциями полноценно не попользоваться.
type Vector = { X: int; Y: int; }

static member Add x y = x + y
static member Add x y = { X = x.X + y.X; Y = x.Y + y.Y; }

Сейчас так не работает, т.к. здесь перегрузка по типу возвращаемого значения (каждое Add по сути — пара свойств, одно типа int -> int -> int, другое — Vector -> Vector -> Vector). В CLR так можно, но из всех языков разрешено вроде только в MSIL asm.
1) при чем тут карррирование
2) почему Add int -> int -> int должен быть членом Vector
3) как будет работать вывод типов
1) Add — curried function
2) Это просто пример того, что не работает.
3) Не вижу сложности вывести тип для обоих Add как обычно.
все равно не понимаю, при чем тут карринг? по-моему это совершенно ортогонально каррингу — не вижу ни какого частичного применения здесь.

В stl есть хелперы bind1st и bind2nd, которые выполняют частичное применение функции 2х аргументов, позволяя вернуть функтор-замыкание уже с одним аргументом. Используется достаточно часто
Я правильно понял, что основная область применения карри-функций — это функции с переменным числом параметров?
Каждый что-то понял, но никто не уверен, то понял ли он именно то, что имелось кем-то в виду. Вопрос в том, имел ли кто-то что-то в виду или что-то тут действительно не так :-p

PS: Откаррируйте-ка мой комментарий, а? :-)
Не совсем «с переменным».

Допустим есть некая универсальная функция с кучей параметров, которая делает сложное действие А (в примерах — сложение двух чисел).

int Sum(int a, int b){ return a+b; }

А вам нужна вот в данный конкретный момент функция-частный случай первой (в примере — «увеличение на 1» — это частный случай сложения).

Нужна она вам, что передать её в качестве параметра в другую функцию:

void SomeOtherFunction(Func<int, int>);

Эта функция требует, чтобы ей передали ф-ю, принимающую один параметр int.

Вы можете объявить её вручную:

int Inc (int a) { return Sum(a, 1); }

а потом передавать Inc:

SomeOtherFunction(var1, var2, Inc);

Но вместо этого вы лучше просто скаррируете Sum: «Sum.Partial(1)», тем самым избавляясь от ненужного описания функции Inc, и получите вот такой вызов:

SomeOtherFunction(var1, var2, Sum.Partial(1));
то есть, переменное кол-во параметров не в смысле, что одна и та же функция имеет переменное количество параметров, а в том, что вы создаёте новые функции, отличные от изначальной количеством параметров
Спасибо за статью. Я так понимаю это ваша реализация, а то я замучился искать упоминание в MSDN? :)
да, каррирование в C# — это уже как-то немного неожиданно, хотя в принципе после введения лямбд ничего странного вроде и нет.
хм, а написать Y-комбинатор-то получается тоже возможно, или я путаю…
так что дело за монадами ;))
лямбды тут не причем. это все можно было писать еще и в C# 2.0 с анонимными методами
монады аналогично. как только появились генерики можно было писать. только вот использовать не совсем удобно было потому как не было сахара

p.s. наслаждайтесь:

delegate Func<T,R> RecFunc<T,R>(RecFunc<T,R> f);

Func<T, R> Y<T, R>(Func<Func<T, R>, Func<T, R>> f)
{
RecFunc<T, R> recFunc = r => t => f(r®)(t);
return recFunc(recFunc);
}
да, все верно. и все-таки после прочтения остается ощущение что пусть каждый язык занимается тем, для чего предназначен. богу богову, кесарю кесарево.
Интересно, почему на русском это назвали каррирование… ведь на англ. яз. вроде это произносится как «кёрриинг». :)
Сахар это конечно хорошо, но нужно не забывать, что при применении выражений типа a => b => f(a,b), передача значения 'a' в место вызова f(a, b) происходит через поле автоматически сгенерированного класса. Т.е. выполнение этого выражения влечет за собой создание временного объекта в куче. И хотя затраты на выделение памяти в CLR сведены к минимуму, количество объектов все же может отрицательно повлиять на производительность. Другими словами, злоупотреблять сахаром не стоит — можно нажить диабет :)
Вот этому как-то мало уделяют внимания, когда говорят о новых фишечках языка. Может память действительно более не ограниченный ресурс, но все таки…
Да, каждая медаль имеет две стороны. Это как шаблоны C++ в свое время — все очень удобно и круто, но экзешник распухает капитально (а под досом это имело значение...)
Only those users with full accounts are able to leave comments. Log in, please.