Функции f# Карри

У кого-нибудь есть достойный пример, желательно практичный/полезный, они могут опубликовать демонстрацию концепции?

6 ответов


(Edit: a small Ocaml FP Koan для начала)

Коан Карринга (Коан о еде, то есть не о еде)

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

мы хотим сделать карту над деревом. Эта функция может быть карри и применяется к каждому узлу, если он должен тогда еще один аргумент-так как мы будем применять один узел, так как это последний аргумент. Этого не должно быть. Карри, но пишу другое


хотя предыдущие примеры ответили на вопрос, вот два более простых примера того, как карри может быть полезно для программирования 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];;

Currying описывает процесс преобразования функции с несколькими аргументами в цепочку функций с одним аргументом. Пример на 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# объявления функций карри по умолчанию. Вам обычно не нужно Карри функции; они уже Карри. Основным исключением из этого являются методы framework и другие перегруженные функции, которые принимают кортеж, содержащий их несколько аргументов. Поэтому вы, возможно, захотите выполнить такие функции, и, на самом деле, я наткнулся на это вопрос, когда я искал функцию библиотеки, которая бы это сделала. Я полагаю, что это отсутствует (если это действительно так), потому что это довольно тривиально реализовать:

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"

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

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:\> "

теперь путем currying простая функция 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# мой блог. Суть в том, что вы можете создать функцию, которая закрыта по параметру (в моем примере создайте функцию для расчета налога с продаж, закрытую по значению данного муниципалитета)из существующей многопараметрической функции.

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