F# каррированная функция
-
08-06-2019 - |
Вопрос
У кого-нибудь есть достойный пример, желательно практичный/полезный, который он мог бы опубликовать, демонстрируя концепцию?
Решение
(Редактировать:маленький Окамл Ф.П. Коан для начала дела)
Коан о карри (Коан о еде, а не о еде)
Студент пришел к Жаку Гарригу и сказал: «Я не понимаю, для чего хороша карри». Жак ответил: «Расскажи мне свою любимую еду и свой любимый десерт».Озадаченный студент ответил, что ему нравятся окономияки и кантен, но хотя в его любимом ресторане подают отличные окономияки, на следующее утро от их кантена у него всегда болел живот.Поэтому Жак повел студента поесть в ресторан, где окономияки были ничуть не хуже любимых студентов, а затем отвез его через весь город в магазин, где готовили превосходные кантен, где студент с радостью употребил остаток своего аппетита.Ученик насытился, но не просветился...до следующего утра, когда он проснулся и его желудок почувствовал себя хорошо.
Мои примеры будут охватывать его использование для повторного использования и инкапсуляции кода.Это совершенно очевидно, если вы посмотрите на них, и это должно дать вам конкретный, простой пример, который вы можете применить во многих ситуациях.
Мы хотим сделать карту поверх дерева.Эту функцию можно каррировать и применять к каждому узлу, если ей требуется более одного аргумента, поскольку мы будем применять тот, который находится в узле, в качестве последнего аргумента.Его не обязательно каррировать, но писать другой function (при условии, что эта функция используется в других случаях с другими переменными) было бы пустой тратой.
type 'a tree = E of 'a | N of 'a * 'a tree * 'a tree
let rec tree_map f tree = match tree with
| N(x,left,right) -> N(f x, tree_map f left, tree_map f right)
| E(x) -> E(f x)
let sample_tree = N(1,E(3),E(4)
let multiply x y = x * y
let sample_tree2 = tree_map (multiply 3) sample_tree
но это то же самое, что:
let sample_tree2 = tree_map (fun x -> x * 3) sample_tree
Так что этот простой случай неубедителен.Однако это действительно так, и это эффективно, если вы больше используете язык и естественным образом сталкиваетесь с такими ситуациями.Другой пример с повторным использованием кода в качестве каррирования.А рекуррентное отношение для создания простых чисел.Ужасное сходство:
let rec f_recurrence f a seed n =
match n with
| a -> seed
| _ -> let prev = f_recurrence f a seed (n-1) in
prev + (f n prev)
let rowland = f_recurrence gcd 1 7
let cloitre = f_recurrence lcm 1 1
let rowland_prime n = (rowland (n+1)) - (rowland n)
let cloitre_prime n = ((cloitre (n+1))/(cloitre n)) - 1
Хорошо, теперь rowland и cloitre являются каррированными функциями, поскольку у них есть свободные переменные, и мы можем получить любой индекс ее последовательности, не зная и не беспокоясь о f_recurrence.
Другие советы
Хотя предыдущие примеры ответили на этот вопрос, вот два более простых примера того, как карринг может быть полезен для программирования на F#.
open System.IO
let appendFile (fileName : string) (text : string) =
let file = new StreamWriter(fileName, true)
file.WriteLine(text)
file.Close()
// Call it normally
appendFile @"D:\Log.txt" "Processing Event X..."
// If you curry the function, you don't need to keep specifying the
// log file name.
let curriedAppendFile = appendFile @"D:\Log.txt"
// Adds data to "Log.txt"
curriedAppendFile "Processing Event Y..."
И не забывайте, что вы можете каррировать семейство функций Printf!Обратите внимание на явное отсутствие лямбды в версии с каррированием.
// Non curried, Prints 1 2 3
List.iter (fun i -> printf "%d " i) [1 .. 3];;
// Curried, Prints 1 2 3
List.iter (printfn "%d ") [1 .. 3];;
Карринг описывает процесс преобразования функции с несколькими аргументами в цепочку функций с одним аргументом.Пример на C# для функции с тремя аргументами:
Func<T1, Func<T2, Func<T3, T4>>> Curry<T1, T2, T3, T4>(Func<T1, T2, T3, T4> f)
{
return a => b => c => f(a, b, c);
}
void UseACurriedFunction()
{
var curryCompare = Curry<string, string, bool, int>(String.Compare);
var a = "SomeString";
var b = "SOMESTRING";
Console.WriteLine(String.Compare(a, b, true));
Console.WriteLine(curryCompare(a)(b)(true));
//partial application
var compareAWithB = curryCompare(a)(b);
Console.WriteLine(compareAWithB(true));
Console.WriteLine(compareAWithB(false));
}
Теперь логический аргумент, вероятно, нет аргумент, который вы, скорее всего, захотите оставить открытым с частичным применением.Это одна из причин, почему порядок аргументов в функциях F# на первый взгляд может показаться немного странным.Давайте определим другую функцию карри C#:
Func<T3, Func<T2, Func<T1, T4>>> BackwardsCurry<T1, T2, T3, T4>(Func<T1, T2, T3, T4> f)
{
return a => b => c => f(c, b, a);
}
Теперь мы можем сделать что-то более полезное:
void UseADifferentlyCurriedFunction()
{
var curryCompare = BackwardsCurry<string, string, bool, int>(String.Compare);
var caseSensitiveCompare = curryCompare(false);
var caseInsensitiveCompare = curryCompare(true);
var format = Curry<string, string, string, string>(String.Format)("Results of comparing {0} with {1}:");
var strings = new[] {"Hello", "HELLO", "Greetings", "GREETINGS"};
foreach (var s in strings)
{
var caseSensitiveCompareWithS = caseSensitiveCompare(s);
var caseInsensitiveCompareWithS = caseInsensitiveCompare(s);
var formatWithS = format(s);
foreach (var t in strings)
{
Console.WriteLine(formatWithS(t));
Console.WriteLine(caseSensitiveCompareWithS(t));
Console.WriteLine(caseInsensitiveCompareWithS(t));
}
}
}
Почему эти примеры на C#?Потому что в F# объявления функций каррируются по умолчанию.Обычно вам не нужно каррировать функции;они уже карри.Основным исключением из этого правила являются методы платформы и другие перегруженные функции, которые принимают кортеж, содержащий множество аргументов.Поэтому вы можете захотеть каррировать такие функции, и на самом деле я столкнулся с этим вопросом, когда искал библиотечную функцию, которая могла бы это сделать.Я предполагаю, что он отсутствует (если это действительно так), потому что его довольно тривиально реализовать:
let curry f a b c = f(a, b, c)
//overload resolution failure: there are two overloads with three arguments.
//let curryCompare = curry String.Compare
//This one might be more useful; it works because there's only one 3-argument overload
let backCurry f a b c = f(c, b, a)
let intParse = backCurry Int32.Parse
let intParseCurrentCultureAnyStyle = intParse CultureInfo.CurrentCulture NumberStyles.Any
let myInt = intParseCurrentCultureAnyStyle "23"
let myOtherInt = intParseCurrentCultureAnyStyle "42"
Чтобы обойти ошибку с помощью String.Compare, поскольку, насколько я могу судить, нет способа указать, какую перегрузку с тремя аргументами выбрать, вы можете использовать необщее решение:
let curryCompare s1 s2 (b:bool) = String.Compare(s1, s2, b)
let backwardsCurryCompare (b:bool) s1 s2 = String.Compare(s1, s2, b)
Я не буду вдаваться в подробности использования приложения частичных функций в F #, потому что это уже описано в других ответах.
Это довольно простой процесс.Возьмите функцию, привяжите один из ее аргументов и верните новую функцию.Например:
let concatStrings left right = left + right
let makeCommandPrompt= appendString "c:\> "
Теперь, используя простую функцию concatStrings, вы можете легко добавить командную строку в стиле DOS в начало любой строки!Действительно полезно!
Ладно, не совсем.Более полезный случай, который я нахожу, — это когда я хочу создать функцию, которая возвращает мне данные в потоковом режиме.
let readDWORD array i = array[i] | array[i + 1] << 8 | array[i + 2] << 16 |
array[i + 3] << 24 //I've actually used this function in Python.
Удобство этого заключается в том, что вместо того, чтобы создавать целый класс для подобных вещей, вызывать конструктор, вызывать obj.readDWORD(), у вас просто есть функция, которую невозможно изменить.
Вы знаете, что можете сопоставить функцию со списком?Например, сопоставление функции для добавления по одной к каждому элементу списка:
> List.map ((+) 1) [1; 2; 3];;
val it : int list = [2; 3; 4]
На самом деле здесь уже используется каррирование, потому что (+)
Оператор использовался для создания функции для добавления единицы к своему аргументу, но вы можете выжать из этого примера немного больше, изменив его так, чтобы он отображал ту же функцию для списка списков:
> List.map (List.map ((+) 1)) [[1; 2]; [3]];;
val it : int list = [[2; 3]; [4]]
Без каррирования вы не сможете частично применить эти функции и вместо этого вам придется написать что-то вроде этого:
> List.map((fun xs -> List.map((fun n -> n + 1), xs)), [[1; 2]; [3]]);;
val it : int list = [[2; 3]; [4]]
Я привел хороший пример моделирования каррирования на C#. в моем блоге.Суть в том, что вы можете создать функцию, замкнутую по параметру (в моем примере создайте функцию для расчета налога с продаж, закрытого по стоимости данного муниципалитета), из существующей многопараметрической функции.
Что здесь привлекательно, так это то, что вместо того, чтобы создавать отдельную функцию специально для расчета налога с продаж в округе Кук, вы можете создавать (и повторно использовать) функцию динамически во время выполнения.