F#curried功能

时间:2008-08-12 04:11:16

标签: f# functional-programming currying

任何人都有一个不错的例子,最好是实用/有用,他们可以发布演示这个概念吗?

6 个答案:

答案 0 :(得分:20)

  

(编辑:小Ocaml FP Koan开始关闭)

     
    

Currying的Koan(关于食物的一种关系,与食物无关)

         
      

一名学生来到雅克·加里格说,“我不明白什么是好事。”雅克回答说:“告诉我你最喜欢的一餐和你最喜欢的甜点”。困惑的学生回答他喜欢御好烧和kanten,但他最喜欢的餐厅服务很棒的御好烧,他们的kanten第二天早上总是让他肚子疼。因此,雅克带着学生在一家餐厅吃饭,这家餐厅的服务质量和学生们最喜欢的一样好,然后带他穿过城镇去了一家商店,这家商店非常适合学生愉快地应用剩下的食欲。这名学生很满意,但他并没有开悟......直到第二天早上他醒来时,他的胃感觉很好。

    
  

我的示例将涵盖使用它来重用和封装代码。一旦你看到这些,这是相当明显的,并且应该给你一个具体的,简单的例子,你可以考虑在很多情况下应用。

我们想在树上做地图。如果它需要多于一个参数,那么这个函数可以被curry并应用于每个节点 - 因为我们将在节点处应用该函数作为它的最终参数。它不必是curry,但是编写另一个函数(假设此函数在其他实例中与其他变量一起使用)将是一种浪费。

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。一个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

好的,现在rowland和cloitre是curried函数,因为它们有自由变量,我们可以得到它的序列的任何索引而不知道或担心f_recurrence。

答案 1 :(得分: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系列功能提供服务!在咖喱版中,请注意明显缺少lambda。

// 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];;

答案 2 :(得分: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#curry函数:

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#中,函数声明默认是curry。你通常不需要咖喱功能;他们已经咖喱了。这个的主要例外是框架方法和其他重载函数,它们使用包含多个参数的元组。因此,您可能想要讨论这些函数,事实上,当我在寻找可以执行此操作的库函数时,我遇到了这个问题。我认为它是缺失的(如果确实是这样),因为它实现起来非常简单:

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#中部分函数应用程序的用法,因为其他答案已经涵盖了这一点。

答案 3 :(得分:2)

这是一个相当简单的过程。获取一个函数,绑定其中一个参数并返回一个新函数。例如:

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(),你只需要一个不能从你下面变异的函数。 / p>

答案 4 :(得分:2)

您知道可以在列表上映射函数吗?例如,映射函数以将一个函数添加到列表的每个元素:

> List.map ((+) 1) [1; 2; 3];;
val it : int list = [2; 3; 4]

这实际上已经在使用currying,因为(+)运算符用于创建一个函数来向其参数添加一个,但是你可以通过改变它来映射相同函数清单列表:

> List.map (List.map ((+) 1)) [[1; 2]; [3]];;
val it : int list = [[2; 3]; [4]]

如果没有curry,你就不能部分地应用这些函数,而是必须写下这样的东西:

> List.map((fun xs -> List.map((fun n -> n + 1), xs)), [[1; 2]; [3]]);;
val it : int list = [[2; 3]; [4]]

答案 5 :(得分:1)

我举了一个很好的例子来模拟C#on my blog中的currying。要点是,您可以创建一个函数,该函数通过参数(在我的示例中创建一个用于计算特定市政当值的销售税的函数)关闭现有的多参数函数。

这里有吸引力的是,您不必专门为Cook County计算销售税,而是可以在运行时动态创建(并重复使用)该函数。