Re[7]: [Scala] В чем пракитическая польза карринга?
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.14 10:12
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Кстати, а можно написать ints.map(3.toString) ?


Все числа заменить на "3"? В таком синтаксисе — нет, требуемые аргументы должны фигурировать — явно или как _.

scala> List(1,2,3).map(_ => 3.toString)
res0: List[String] = List(3, 3, 3)

scala> List(1,2,3).map(3.toString)
<console>:8: error: type mismatch;
 found   : () => String
 required: Int => ?
              List(1,2,3).map(3.toString)
Re[8]: [Scala] В чем пракитическая польза карринга?
От: AlexRK  
Дата: 26.06.14 10:29
Оценка:
Здравствуйте, dimgel, Вы писали:

D>Здравствуйте, AlexRK, Вы писали:


ARK>>Кстати, а можно написать ints.map(3.toString) ?


D>Все числа заменить на "3"? В таком синтаксисе — нет, требуемые аргументы должны фигурировать — явно или как _.


Я вот и думаю, что с явными аргументами оно как-то нагляднее.

Кстати, а переменную с именем "_" в скале можно объявить? В C# точно можно.
Re[9]: [Scala] В чем пракитическая польза карринга?
От: dimgel Россия https://github.com/dimgel
Дата: 26.06.14 10:36
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Кстати, а переменную с именем "_" в скале можно объявить? В C# точно можно.


Не-а. Как раз на днях пытался.

scala> val `_` = 1
<console>:1: error: wildcard invalid as backquoted identifier
       val `_` = 1
           ^


Однако в жаве тоже нельзя объявить переменную с именем *
Re[10]: [Scala] В чем пракитическая польза карринга?
От: AlexRK  
Дата: 26.06.14 10:41
Оценка:
Здравствуйте, dimgel, Вы писали:

D>Однако в жаве тоже нельзя объявить переменную с именем *


В жабе звезда в допустимый алфавит идентификаторов и не входит.

Тут скорее ближе аналогия с Ada, там, насколько я помню, нельзя в идентификаторе писать подряд два символа "_", начинаться с "_" и еще какие-то ограничения.
Re[3]: [Scala] В чем пракитическая польза карринга?
От: _NN_ www.nemerleweb.com
Дата: 29.06.14 06:12
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Я верно понимаю, что при наличии лямбд частичное применение не особенно нужно?


ARK>
ARK>  filter(array, x => less(x, 10));
ARK>  filter(array, x => between(50, x, 100));
ARK>


Могу сказать, что одно другому не помеха.
Это разные способы выразить одно и тоже.
В Nemerle есть аж несколько способов выразить лямбду и каждый из них нужно использовать по назначению.

1. Анонимный блок
def a = { def f(x, y) { x + y }; f };
System.Console.WriteLine(a(1, 2));


2. Анонимная функция: разворачивается в блок с возвратом функции (1)
def a = fun(x, y) { x + y};
System.Console.WriteLine(a(1, 2));


3. Лямбда в стиле C#, разворачивается в 'fun(){..}':
def a = (x, y) => x + y;
System.Console.WriteLine(a(1, 2));


4. Частичное применение:
def a = _ + _;
System.Console.WriteLine(a(1, 2));


P.S.
Можно объявить переменную с именем '_'
def @_ = 1; // '@' как и в C# означает, что нужно трактовать идентификатор как простое имя.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: [Scala] В чем пракитическая польза карринга?
От: Аноним  
Дата: 07.07.14 10:34
Оценка:
Мой личный взгляд таков, что это просто менее удачная версия частичного применения, а по сему вещь лишняя при наличии последнего.
Re: [Scala] В чем пракитическая польза карринга?
От: maxkar  
Дата: 12.07.14 09:06
Оценка: +1
Здравствуйте, _Oswald_, Вы писали:

_O_>Поясните, пожалуйста, в чем заключается практическая ценность каррирования функций?


Про частичное применение написали уже много. Но вот ключевую особенность карринга по сравнению с частичным применением не упомянули. Связана она с "полиморфизмом по числу аргументов". Т.е. каррирование дает возможность использовать функцию от N аргументов "в том же контексте", что и функцию от одного аргумента.

Вот простой пример (ocaml):

# let f1 = (+);;
val f1 : int -> int -> int = <fun>
# let f2 x y z = z * y + z;;
val f2 : 'a -> int -> int -> int = <fun>
# let a = [1;2;3;4;5];;
val a : int list = [1; 2; 3; 4; 5]
# let a1 = List.map f1 a;;
val a1 : (int -> int) list = [<fun>; <fun>; <fun>; <fun>; <fun>]
# let a2 = List.map f2 a;;
val a2 : (int -> int -> int) list = [<fun>; <fun>; <fun>; <fun>; <fun>]


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

Если рассматривать более практичные сценарии, будет еще интереснее. Без каррирования практически невозможно работать с монадоподобными сущностями. Это как минимум UI (реактивное программирование), асинхронные комбинаторы (то же реактивное программирование) и монадические парсер-комбинаторы. Покажу пример последнего (скала):

// Конструктор для узла синтаксического дерева.
def defun(name : String, arglist : Seq[ArgDef], rettype : Type, body : Seq[Statement]) : FunDef = ???

// Используемые парсеры, сигнатуры
val idP : Parser[String] = ???
val arglistP : Parser[Seq[Argdef]] = sepBy(argDef, lit(","))
val typeP : Parser[Type] = ...
val statemetsP : Parser[Statement] = ???

// Пример комбинации и парсинга.
val defunP =
  defun.curried <$> lit("function") <*> idP <$>
    lit("(") <*> arglistP <$> lit(")") <$> lit(":") <*>
    typeP <$> lit("{") <*> statementsP <$> lit("}")

Значение операторов — <$> парсит оба выражения и возвращает результат левого. <*> — Парсит оба выражения и применяет _результат_ левого выражения к правому. Более формально оно было бы определено как (определения только для парсера, для "чистой функции" в качестве первого аргумента — аналогично):

implicit class ParserOps[T](val valude : Parser[T]) extends AnyVal {
  def <$>[I](parser : Parser[I]) : Parser[T] = ???
}

implicit class ParserApplyOp[T => R](val value : Parser[T => R]) extends AnyVal {
  def <*>(parser : Parser[T]) : Parser[R] = ???
}


В примере вызовы lit для понятности, на практике я бы их сделал имплиситами и писал просто строки.

Примеры с async и реактивным программированием будут очень похожи. Везде берем функции с каким-то (не фиксированным) числом аргументов, каррируем и применяем их к монадоподобным объектам. А вот только на частичном применении все это очень печально писать. Либо куча лямбд, либо все комбинации, подписки слушателей и прочее приходится вручную.
Re: [Scala] В чем пракитическая польза карринга?
От: dimgel Россия https://github.com/dimgel
Дата: 18.07.14 21:16
Оценка:
Здравствуйте, _Oswald_,

_O_>Поясните, пожалуйста, в чем заключается практическая ценность каррирования функций?

_O_>Я только начинаю учить scala

Чёт только дошло, вроде тут ещё не писали. В случае scala, каррирование используется для поддержки path-dependent types. Типы в последующих списках аргументов могут ссылаться на параметры, объявленные в предыдущих списках аргументов:

object MacroUtil {
    def assertx(expr: Boolean) = macro assertxMacro

    // Тут тип c.Expr ссылается на c.
    def assertxMacro(c: Context)(expr: c.Expr[Boolean]) = {
        import c.universe._
        val msg = show(expr.tree)
        c.Expr[Unit](q"assert($expr, $msg)")
    }
}


Это скорее из области заморочек конкретного языка, а не про каррирование вообще, но тем не менее.
Re[2]: [Scala] В чем пракитическая польза карринга?
От: AlexRK  
Дата: 27.07.14 07:43
Оценка:
Здравствуйте, maxkar, Вы писали:


M>
M>val defunP =
M>  defun.curried <$> lit("function") <*> idP <$>
M>    lit("(") <*> arglistP <$> lit(")") <$> lit(":") <*>
M>    typeP <$> lit("{") <*> statementsP <$> lit("}")
M>



Нда, помню кто-то назвал ФЯ "сумасшедшей поделкой безумных ученых" и, пожалуй, в чем-то был прав.

M>Примеры с async и реактивным программированием будут очень похожи. Везде берем функции с каким-то (не фиксированным) числом аргументов, каррируем и применяем их к монадоподобным объектам.


А не можете пояснить для тупых на примере попроще?

Вот async, нужно ли здесь каррирование и где? Или это актуально исключительно в функциональных языках?

async void ProcessDataAsync()
{
    // Start the HandleFile method.
    Task<int> task = HandleFileAsync("C:\\enable1.txt");

    // Wait for the HandleFile task to complete.
    int x = await task;
    Console.WriteLine("Count: " + x);
}

async Task<int> HandleFileAsync(string file)
{
    int count = 0;

    // Read in the specified file.
    using (StreamReader reader = new StreamReader(file))
    {
        string v = await reader.ReadToEndAsync();

        // ... Process the file data somehow.
        count += v.Length;
    }

    return count;
}
Re[3]: [Scala] В чем пракитическая польза карринга?
От: trop Россия  
Дата: 27.07.14 17:29
Оценка:
Здравствуйте, AlexRK, Вы писали:
ARK>А не можете пояснить для тупых на примере попроще?

если на туплах:

в скале ф-ии берут аргументы в виде нескольких туплов
т.е. классическое написание — int f (a, b, c) — в скале это функция одного аргумента,
int f (a, b) (c, d) — функция от двух аргументов ,и можно её расценивать как
функцию одного аргумента (f (a, b)) (c, d) и передавать как параметр типа f' (c, d), где f' = f (a,b)

одерски пишет на хаскель и тащит всё в скалу оттуда

в хаскеле все функции такие:
f a b c = ((f a) b) c, т.е. подставив _a_ получим функцию g от b c,
но в хаскеле пишется не совсем так (f :: a — читать f (a) ):
f :: a -> b -> c -> d , где d это тип результата, это упрощение такой записи:
f :: a -> (b -> (c -> d)), видно что f a :: b -> (c -> d)

ф-ии можно композировать (алгебраическая подстановка с учетом типов аргументов),
в случае если у коммутируемых функций по одному аргументу, тогда можно их
закоммутировать в цепочку и выполняться она будет справа налево,
если же у функций разное количество аргументов, то нужно садиться и ручками
на листке подставлять, чтобы типы не перепутались в голове

все что выше это частичное применение, или подстановка аргумента (бета-редукция)
или каррирование

а в хаскеле curry это такое преобразование:
curry: f (a,b) -> f' a b
есть обратное преобразование:
uncurry: f a b -> f' (a,b)

например, есть функция f от пары (a, b), которую написал незнакомый нам тип,
а у нас в этом месте только один аргумент b, то тогда можно написать
(flip . curry) f ,где flip переворачивает аргументы в функции двух аргументов,
и получится f b a , или после частичного применения — (f' b) a, функция от аргумента a


SPJ в видео об истории хаскеля рассказывал, что когда они придумывали название языка
они набросали на доске все варианты, а потом каждый стал вычеркивать тот который ему не нравился
больше всего, в итоге остался вариант curry, который неприлично рифмовался, поэтому
решили назвать именем. Пол Худак поехал к вдове Карри ,она очень любезно разрешила
назвать язык именем мужа, но когда Пол уже уходил она вдруг добавила "а вы знаете,
Хаскелю никогда не нравилось его имя"
-
Re[4]: [Scala] В чем пракитическая польза карринга?
От: AlexRK  
Дата: 27.07.14 17:35
Оценка:
Здравствуйте, trop, Вы писали:

T>ф-ии можно композировать (алгебраическая подстановка с учетом типов аргументов),

T>в случае если у коммутируемых функций по одному аргументу, тогда можно их
T>закоммутировать в цепочку и выполняться она будет справа налево,
T>если же у функций разное количество аргументов, то нужно садиться и ручками
T>на листке подставлять, чтобы типы не перепутались в голове

Главный вопрос — не что это такое, а ЗАЧЕМ это нужно.
Причем интересны более-менее реальные сценарии, а не ряд Фибоначчи.
Да, важное уточнение, из реальных сценариев парсеры лично мне не очень интересны.
Интересно что-то приближенное к жизни — файлик прочитать, XML создать, данные отсортировать, асинхронно метод вызвать...
Re[5]: [Scala] В чем пракитическая польза карринга?
От: trop Россия  
Дата: 27.07.14 18:21
Оценка: +1
Здравствуйте, AlexRK, Вы писали:
ARK>Главный вопрос — не что это такое, а ЗАЧЕМ это нужно.

если обобщить, то это простая арифметика над функциями и над типами,
простые правила позволяют комбинировать функции и комбинировать типы
в более сложные конструкции.
как конструктор лего, есть блоки(функции) и если разъёмы(типы) совпадают то можно их сцеплять,
причем сцепка уже выглядит проще, т.к. разъемы задействованы, осталось меньше свободных разъемов.
ещё например есть блок от другого конструктора, можно согнуть его разъёмы и прицепить как надо,
к своему блоку, лишь бы разъёмы были ,а прицепить несложно, в потом во всю эту конструкцию
как в водопровод или электросхему залить аргумент и наблюдать во что все выльется,
и если ты все правильно соединил, то это с высокой вероятностью заработает сразу
-
Re[5]: [Scala] В чем пракитическая польза карринга?
От: trop Россия  
Дата: 27.07.14 18:29
Оценка:
Здравствуйте, AlexRK, Вы писали:

а то что называют константой напр в хаскеле это функция без аргумента,
как хвостик болтается с разъёмом, типа bnc-терминатора rg-58 на коаксильной сети,
и можно его вкрутить в подходящий разъем блока (передать аргумент)
-
Re[6]: [Scala] В чем пракитическая польза карринга?
От: dimgel Россия https://github.com/dimgel
Дата: 27.07.14 18:52
Оценка: +1 :)
Здравствуйте, trop, Вы писали:

T>а то что называют константой напр в хаскеле это функция без аргумента,

T>как хвостик болтается с разъёмом, типа bnc-терминатора rg-58 на коаксильной сети,
T>и можно его вкрутить в подходящий разъем блока (передать аргумент)

Брал я как-то в магазине селёдку, молоко и туалетную бумагу. В кои-то веки на этот набор продавщица среагировала (протестировано тыщу раз в разных магазинах разных городов — всем пофиг), но в ответ на её хихи я имел заявить, что всё это фигня, рыбу вообще в молоке... как её... — Замачивают. — Во! — (она в курсе, что я бывший повар) А я ещё знаете как готовлю: ... — (перебиваю, подавшись вперёд) А я тупо чищу И ЖРУ!!! (оба ржём).

Я к тому, что "передать аргумент" — это на самом деле очень простое действо: вот тупо берёшь и передаёшь: f(x). А все эти ваши объяснения — они опять же не про ЗАЧЕМ, а очередное КАК. Ну вот типа да, вот такой хаскел извращенский, что даже вокруг передачи аргумента в функцию наворотили теорий. Ассемблер и то, помнится, проще был.
Re[7]: [Scala] В чем пракитическая польза карринга?
От: trop Россия  
Дата: 28.07.14 04:12
Оценка:
Здравствуйте, dimgel, Вы писали:
D>"передать аргумент" — это на самом деле очень простое действо

просто воображение разыгралось, не обращай внимания

вообще частичное применение и лямбда функции в хаскеле используются регулярно,
как и монады, реализующие императивность (порядок выполнения).
нет вот какого-то конкретного применения,
ну например если надо регулировать поведение какого то поддерева вычислений,
то наружу выносится какой нибудь предикат|функция с доп параметрами в начале,
и подается с разными наборами этих параметров в верхнюю функцию блока,
тем самым варьируя поведение и не плодя boilerplate
-
Re[8]: [Scala] В чем пракитическая польза карринга?
От: dimgel Россия https://github.com/dimgel
Дата: 28.07.14 04:18
Оценка:
Здравствуйте, trop, Вы писали:

T>просто воображение разыгралось, не обращай внимания


А мне просто повыпендриваться захотелось.

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


Да это понятно, просто стиль такой, естественным образом следующий из идеологии языка. Дальше туториалов я по хаскелю в своё время не продвинулся, но то, что там все либы этот карринг юзают в хвост и в гриву, помню.
Re[3]: [Scala] В чем пракитическая польза карринга?
От: maxkar  
Дата: 28.07.14 21:12
Оценка: 2 (1)
Здравствуйте, AlexRK, Вы писали:

ARK>А не можете пояснить для тупых на примере попроще?

Угу. Ниже будут.

ARK>Вот async, нужно ли здесь каррирование и где?

В этом примере с асинками — не нужен. Но это не совсем честный пример.

1. На функциях с одним аргументом каррирование особого смысла не имеет (и я не уверен, что оно вообще существует). Вот частичное применение смысл имеет.
2. В этом примере есть специальная поддержка от компилятора (или макросов, или еще чего). Это заставляет предположить, что и реактивное программирование поддерживается компилятором (я асинк делал как небольшую обертку поверх реактивного программирования). Плюс комбинаторы парсеров и т.д. В случае с каррированием я асинк делают в рамках базового языка, без специальной поддержки.
3. Что самое интересное, даже в этом вашем примере можно углядеть, где может понадобится каррирование. Вот там есть await. Для одного значения это можно пережить. А вот если вы будете много параметров передавать? Поэтому пример хочется переписать в более интересном виде:

  Скрытый текст
// javascript:
function processDataAsync() {
  var task = handleFileAsync("..."); //returns Async<int>;
  Async.apply(function(c) { console.write("Count " + c); }, [c]);
}

// Scala
def processDataAsync() = {
  val task = hanleFileAsync("...")
  System.out.println("Count : " + _) <*> task
}

// Scala, compact style
def processDataAsync() : Async<Void> =
  System.out.println("Count : " + _) <*> hanleFileAsync("...")

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

  Скрытый текст
// C#:
Task<int> p1 = doSomething();
Task<int> p2 = doSomethingElse();
Task<int> p3 = doSomethingMore();

int x1 = await p1;
int x2 = await p2;
int x3 = await p3;

doSomethingWithResult(x1, x2, x3)

// Javascipt:
var p1 = doSomething();
var p2 = doSomethingElse();
var p3 = doSomethingMore();

Async.apply(doSomethingWithResult, [x1, x2, x3]);

// Scala:
val p1 = doSomething();
val p2 = doSomethingElse();
val p3 = doSomethingMore();

doSomethingWithResult.curried <*> p1 <*> p2 <*> p3


ARK>Или это актуально исключительно в функциональных языках?

Оно даже среди функциональных языков актуально не везде. Есть как минимум два очень широких класса функциональных языков. Первые умеют делать "нетипизированный" Function.apply. Т.е. я могу некаррированную функцию применить ко всем аргументам. Обычно имеет вид <function>.apply(<this-for-function>, <args>). args — массив позиционных аргументов для функции. В этот класс попадают javascript (и часть типизированных аналогов, но не все), actionscript 3, python, lisp и тому подобные. Моя практика показывает, что для них каррирование не нужно. Там во многих случаях можно писать функции "высшего порядка" в стиле

// javascript:
function highClassFunction(fun, args) {
  var newArgs = [];
  // prepare new args.
  var res = fun.apply(null, newArgs);
  // play with result, etc...
  return res;
}


Второй класс языков имеет "строго типизированные" функции. Т.е. в системе типов для каждой функции описывается и тип ее аргументов. Обобщенное программирование в стиле js на них не позволяет система типов. Этот подкласс разделяется еще на два. Первый подкласс имеет "некаррированные" функции (scala, вероятно еще кто-то). Каррировав функцию можно написать "типизированное" применение функции к одному аргументу. Т.е. в итоге я получаю примерно то же, что и в js, но с другим синтаксисом. Для каждого шага нужно явно указывать, "как именно" применять функцию к следующему аргументу. Второй подкласс имеет изначально каррированные функции (ocaml, haskell). Для него есть все бонусы первого подкласса (т.е. можно использовать те же комбинаторы с тем же синтаксисом). Плюс в этой подгруппе заметно меняется стиль кода. Становится много локальных функций, которые получаются в результате "частичного применения" других функций. Благо функции карированные и частичное применение там выглядит естественно. Но эта подгруппа (и ее особый стиль) к каррированию в scala отношения не имеет.

Вроде бы есть языки, не попадающие ни в одну из двух предыдущих категорий. Там есть строгая система типов для некаррированных функций (соответственно, нет apply). При этом каррирования просто нет. Программирование чего-то в "другой" модели на подобных языках превращается в грусть и печаль. Вроде бы haxe относится к данной категории языков, но я не проверял и могу ошибаться.


А теперь примеры.

Первый. Асинк. Игра. Загружаем начальные ресурсы. Нужно загрузить игрока, подгрузить необходимые игровые ресурсы. Это анимации и предметы в инвентаре. Иконки и анимация описаны в справочниках. Т.е. для подгрузки иконок инвентаря нужен игрок и справочник предметов. Для подгрузки анимации нужен игрок и справочник анимаций.

  Скрытый текст
//C#. 
/* Сложно как-то. В одном методе await не сделать. 
 * Если раньше загрузится справочник предметов, можно 
 * подгружать иконки, а если раньше загрузится справочник
 * анимаций, можно начинать грузить их. Писать лишние
 * методы мне лень.
 */

// Actionscript3 (похоже на javascript, еще более похоже на 
// типизированые обертки поверх js вроде coffescript).
function preloadPlayer() : Process {
  const playerProc : Process = loadPlayerData();
  const itemDict : Process = loadItemsDict();
  const anims : Process = loadAnimationDict();
  // Can use "inline version" like
  // const playerProc : Process = Process.applySync(parsePlayer, [queryServer("/whoami")]);

  const itemPreload : Process = Process.applyAsync(preloadItemIcons, [playerProc, itemDict]);
  const animPreload : Process = Process.applyAsync(preloadAnims, [playerProc, anims]);

  return Process.gather([playerProc, itemDict, anims, itemPreload, animPreload]);
}

// preloadAnims is quite similar.
function preloadItemIcons(player : PlayerData, items : ItemDictionary) : Process {
  const res = [];
  for (var itemId : String in player.inventory)
    res.push(assetLoader.preload(items.getItem(itemId).icon));
  return Process.gather(res); 
}

// Similar to other load* functions.
function loadPlayerData() : Process {
  return Process.applySync(parsePlayer, [querySever("/whoami")]);
}

function parsePlayer(input: String) : PlayerData {
  // Do parsing.
}

// All the same, but Scala:
def preloadPlayer() : Process<Unit> = {
  val playerProc = loadPlayerData()
  val itemDict = loadItemsDict()
  val anims = loadAnimationDict()
  // Can use "inline version" like
  // val playerProc = parsePlayer _ <*> queryServer("/whoami");

  val itemPreload = preloadItemIcons.curried <*> playerProc <**> itemDict
  val animPreload = preloadAnims.curried <*> playerProc <**> anims
  // Or val animPreload = Process.flatten(preloadAnims.curried <*> playerProc <*> anims)
  
  Process.gather(playerProc, itemDict, anims, itemPreload, animPreload)
}

def preloadItemsIcons(player : PlayerData, items : ItemDictionary) : Process<Unit> = 
  Process.gather(
    player.inventory.map(item => assetLoader.preload(items.getItem(item).icon)) : _*)

def loadPlayerData() : Process<PlayerData> = 
  parsePlayer _ <*> queryServer("/whoami")


Плюсы подхода. Я имею parsePlayer и прочие парсеры в качестве отдельной функции. Я могу их протестировать. Комбинаторы для асинхронных процессов — достаточно простые в использовании и их достаточно оттестировать один раз. У меня первым будет начнет загружаться то, для чего данные раньше пришли. Плюс я могу похожие комбинаторы делать для всего, что напоминает монады (на самом деле я на практике Applicative пользуюсь а не монадами, потому что монады без синтаксической поддержки не очень удобны для реальных задач).
Минусы. Я теряю поддержку async внутри циклов (это то, что дает специальная обработка компилятором). Но это вполне нормально, она мне ни разу не нужна была.

Что еще. Можно написать гору перегрузок для Process.applyAsync/Process.applySync. Это значит, что вместо одной функции теперь нужно тестировать несколько. Плюс в оригинале Process.apply* в качестве аргументов может получать не только значения, но и простые константы. После чего перегрузки становится писать очень грустно. Вместе с Process.applySync[A, B, C]((A, B) => C, Process<A>, Process<B>) : Process<C> нужно еще и Process.applySync[A, B, C]((A, B) => C, A, Process<B>) : Process<C>, и Process.applySync[A, B, C]((A, B) => C, Process<A>, B) : Process<C>. Для каких-нибудь пяти аргументов все еще хуже. На практике что-то порядка 8-9 параметров может подобным образом ходить (всякие внешине зависимости вроде кэшей ресурсов, UI-стека (менеджер модальности), и прочего подобного). Много раобты на перегрузках получается.

Что-то очень похожее идет и для реактивного программирования.
  Скрытый текст
// scala.
val playerSprite : Sprite = ...;
val playerScore : State[Int] = player.score
val playerLevel : State[Int] = levelForScore _ <*> playerScore
val nominalHP = hpForLevel _ <*> playerLevel
val effects : State[Seq[Effect]] = player.activeEffects
val animSet : State[String] = getAnimSet.curried <*> player.hp <*> nominalHP <*> effects
applySpriteAnim.curried <*> animSet <*> playerSprite

def getAnimSet(hp : Int, nominalHp : Int, effects : Seq[Effect]) : String = {
  if (effects.contains(Effect.PHYSICAL_DAMAGE_IMMUNITY)) "immortal"
  else if (hp > nominalHp) "heroic"
  else if (hp > 0.7 * nominalHp) "normal"
  else if (hp > 0.3 * nominalHp) "tired"
  else "near-death"
}

def applySpriteAnim(ui : Sprite, animName : String) : Unit = {
  if (ui.currentAnim == animName)
    return
  ui.currentAnim = animName
  ui.animSequence = anims.get(animName)
  ui.currentFrame = ui.animSequence.head()
}

Больше нет необходимости следить за анимацией. В зависимости от текущего состояния (уровень, жизнь, эффекты) будет применяться нужная анимация. И не нужно ручного управления слушателями для всей этой горы состояний. Ну а карринг здесь для применения "универсальных функций". Была бы возможность написать мета-функцию (как в js, которая работает со списками аргументов), я бы написал только такую функцию. А так приходится каждое применение ручками расписывать (это абсолютно нормально для второго подкласса второго класса (см. выше), здесь смотрится не так хорошо, но хотя бы совместимо с системой типов).
Re[4]: [Scala] В чем пракитическая польза карринга?
От: AlexRK  
Дата: 29.07.14 09:25
Оценка:
Здравствуйте, maxkar, Вы писали:

Спасибо за ответ. Сразу все осознать не получается, потом еще над этим всем помедитирую.

Правильно ли я понимаю, что каррирование в основном упирается в возможность вызвать функцию с произвольными аргументами?

Если бы язык позволял такое, то каррирование было бы не актуально?
  void Apply<T...>(Func<T> func, T args)
  {
    func(args);  // correct
  }

  void MyFunc1(int a, string b) { ... }

  void MyFunc2(DateTime c, double d, decimal e) { ... }

  Apply(MyFunc1, 23, "qwe");  // correct
  Apply(MyFunc2, DateTime.Now, 2.4, 451M);  // correct


M>3. Что самое интересное, даже в этом вашем примере можно углядеть, где может понадобится каррирование. Вот там есть await. Для одного значения это можно пережить. А вот если вы будете много параметров передавать?


Tuple<int, string> ?
Re[4]: [Scala] В чем пракитическая польза карринга?
От: Кодт Россия  
Дата: 30.07.14 09:28
Оценка:
Здравствуйте, trop, Вы писали:

T>curry, который неприлично рифмовался


Как?

Тем более, что в итоге они скрестили хаскелл с прологом и всё-таки назвали его карри.
Перекуём баги на фичи!
Re[2]: [Scala] В чем пракитическая польза карринга?
От: Кодт Россия  
Дата: 30.07.14 09:39
Оценка: +1
Здравствуйте, maxkar, Вы писали:

M>Про частичное применение написали уже много. Но вот ключевую особенность карринга по сравнению с частичным применением не упомянули. Связана она с "полиморфизмом по числу аргументов". Т.е. каррирование дает возможность использовать функцию от N аргументов "в том же контексте", что и функцию от одного аргумента.


<>

Заодно находим тесную связь с продолжениями. (Которые, как уже показано в хаскелле, суть монада).

И заодно, с ООП и техническим приёмом "вернуть из метода объект с методами" (как известно, объекты — это замыкания для бедных, и наоборот)
cout << fixed << setw(10) << setprecision(5) << 1.23;
printf("%*.*f", 10, 5, 1.23);

(понятно, что в обычных ООП-языках нет сахара для карринга, — для этого есть рефакторинг, руки и голова программиста).
Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.