F # функция карри

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

12.08.2008 04:11:15
6 ОТВЕТОВ
РЕШЕНИЕ

(Правка: маленький Коам Ocaml FP, чтобы начать все с начала)

Коан Карри (Коан о еде, а не о еде)

Студент подошел к Жаку Гарригу и сказал: «Я не понимаю, для чего полезно карри». Жак ответил: «Скажи мне свое любимое блюдо и твой любимый десерт». Озадаченный студент ответил, что ему нравятся окономияки и кантен, но, хотя в его любимом ресторане подают отличные окономияки, их кантен всегда вызывал у него боль в животе на следующее утро. Итак, Жак отвел студента в ресторан, который обслуживал окономияки так же хорошо, как любимый учеником, а затем отвез его через весь город в магазин, где был отличный кантен, где ученик с радостью применял оставшуюся часть своего аппетита. Студент был насыщен, но он не был просветленным ... до следующего утра, когда он проснулся, и его живот чувствовал себя хорошо.

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

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

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.

21
23.05.2017 12:33:26
Этот ответ описывает частичное применение функции, которая связана с карри, но не одно и то же
phoog 9.02.2013 04:29:17

Это довольно простой процесс. Возьмите функцию, свяжите один из ее аргументов и верните новую функцию. Например:

let concatStrings left right = left + right
let makeCommandPrompt= appendString "c:\> "

Теперь, каррируя простую функцию concatStrings, вы можете легко добавить командную строку в стиле DOS в начало любой строки! Действительно полезно!

Ладно не совсем Более полезный случай, который я нахожу, это когда я хочу иметь функцию make, которая возвращает мне данные в виде потока.

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 (), у вас просто есть функция, которая не может быть видоизменена из-под вас.

3
12.08.2008 04:26:41
Этот ответ описывает частичное применение функции, которая связана с карри, но не одно и то же
phoog 9.02.2013 04:30:37

В то время как предыдущие примеры ответили на вопрос, вот два простых примера того, как Curry может быть полезен для 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];;
15
12.08.2008 16:07:24
Этот ответ описывает частичное применение функции, которая связана с карри, но не одно и то же
phoog 9.02.2013 04:30:04

Вы знаете, что можете отобразить функцию в списке? Например, сопоставление функции для добавления по одному в каждый элемент списка:

> 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]]
3
10.05.2010 03:42:30
Этот ответ описывает частичное применение функции, которая связана с карри, но не одно и то же
phoog 9.02.2013 04:30:53
@phoog Этот ответ правильно объясняет, что «Без каррирования вы не могли бы частично применить эти функции».
Jon Harrop 9.02.2013 08:58:40

Я привел хороший пример симуляции карри в C # в своем блоге . Суть в том, что вы можете создать функцию, которая будет закрыта по параметру (в моем примере - создать функцию для расчета налога с продаж, закрытого по значению данного муниципалитета) из существующей многопараметрической функции.

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

1
2.06.2011 23:09:47
+1, хотя ваша ссылка в блоге, похоже, не работает, я подозреваю, что в вашем примере показана действительная функция каррирования в C #, а не симуляция. Этот ответ является единственным, который на самом деле описывает карри как то, что позволяет частично применять функцию, а не путать ее с частичным применением функции.
phoog 9.02.2013 04:33:56
Я недавно перенес свой блог с CommunityServer на Sitefinity. Я не удосужился написать утилиту для импорта данных моего старого блога :( но я использовал перегрузку функций для имитации карри. Если вы передаете оба параметра, вы получите результат, если вы передадите один, вы получите функцию, которая принимает второй и возвращает результат. Не так элегантно, как настоящее карри, но это работает;)
Michael Brown 12.02.2013 18:48:02

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

10
9.02.2013 05:22:59