2008-08-12 2 views
17

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

ответ

19

(Edit: небольшая Ocaml FP Koan, чтобы начать вещи)

Коан карирования (коан о еде, то есть не о еде)

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

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

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

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 

Так что это простой случай не является убедительным. Это действительно так, и мощно, как только вы используете язык более и, естественно, сталкиваетесь с этими ситуациями. Другой пример - повторное использование кода в качестве currying. A recurrence relation to create prime numbers.Ужасно много сходства там:

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 

Хорошо, теперь Роуленд и Cloitre карированы функции, так как они имеют свободные переменные, и мы можем получить какой-либо индекс это последовательность, не зная, или беспокоиться о f_recurrence.

+3

В этом ответе описывается приложение с частичной функцией, [которое связано с currying, но не то же самое] (http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog 2013-02-09 04:29:17

2

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

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

+2

В этом ответе описывается приложение с частичными функциями, [которое связано с каррированием, но не одно и то же) (http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog 2013-02-09 04:30:37

14

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

В этом ответе описывается приложение с частичной функцией [, которое связано с каррированием, но не с тем же) http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog 2013-02-09 04:30:04

2

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

> 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]] 
+2

В этом ответе описывается приложение с частичной функцией [, которое связано с каррированием, но не с тем же) (http://en.wikipedia.org/wiki/ Currying # Contrast_with_partial_function_application) – phoog 2013-02-09 04:30:53

+0

@phoog Этот ответ правильно expl что «Без currying вы не можете частично применять эти функции». – 2013-02-09 08:58:40

1

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

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

+0

+1, хотя ваша ссылка в блоге кажется сломанной, я подозреваю, что ваш пример показал фактическую функцию currying на C#, а не моделировался. Этот ответ является единственным, который фактически описывает currying как вещь, которая позволяет приложение частичной функции, а не путать его с приложением частичных функций. – phoog 2013-02-09 04:33:56

9

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

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, так как, насколько я могу сказать, что нет никакого способа, чтобы указать, какой 3- аргумент перегружать, чтобы выбрать, вы можете использовать общее нерешение:

let curryCompare s1 s2 (b:bool) = String.Compare(s1, s2, b) 
let backwardsCurryCompare (b:bool) s1 s2 = String.Compare(s1, s2, b) 

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

Смежные вопросы