F# 柯里化函数

发布于 2024-07-04 18:17:52 字数 43 浏览 11 评论 0 原文

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

Anyone have a decent example, preferably practical/useful, they could post demonstrating the concept?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

话少情深 2024-07-11 18:17:52

我给出了一个在 C# 中模拟柯里化的好例子 在我的博客上。 要点是,您可以从现有的多参数函数中创建一个对参数封闭的函数(在我的示例中,创建一个用于计算对给定市镇的值封闭的销售税的函数)。

这里吸引人的是,您不必专门创建一个单独的函数来计算库克县的销售税,您可以在运行时动态创建(和重用)该函数。

I gave a good example of simulating currying in C# on my blog. The gist is that you can create a function that is closed over a parameter (in my example create a function for calculating the sales tax closed over the value of a given municipality)out of an existing multi-parameter function.

What is appealing here is instead of having to make a separate function specifically for calculating sales tax in Cook County, you can create (and reuse) the function dynamically at runtime.

空心↖ 2024-07-11 18:17:52

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

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(),而只需拥有一个无法从您手下进行变异的函数。

It's a fairly simple process. Take a function, bind one of its arguments and return a new function. For example:

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

Now by currying the simple concatStrings function, you can easily add a DOS style command prompt to the front of any string! Really useful!

Okay, not really. A more useful case I find is when I want to have a make a function that returns me data in a stream like manner.

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.

The convenient part about it is that rather than creating an entire class for this sort of thing, calling the constructor, calling obj.readDWORD(), you just have a function that can't be mutated out from under you.

高速公鹿 2024-07-11 18:17:52

虽然前面的示例回答了这个问题,但这里有两个更简单的示例,说明柯里化如何有利于 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];;

While the previous examples answered the question, here are two simpler examples of how Currying can be beneficial for F# programming.

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..."

And don't forget you can curry the Printf family of function! In the curried version, notice the distinct lack of a 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];;
老街孤人 2024-07-11 18:17:52

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

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

You know you can map a function over a list? For example, mapping a function to add one to each element of a list:

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

This is actually already using currying because the (+) operator was used to create a function to add one to its argument but you can squeeze a little more out of this example by altering it to map the same function of a list of lists:

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

Without currying you could not partially apply these functions and would have to write something like this instead:

> List.map((fun xs -> List.map((fun n -> n + 1), xs)), [[1; 2]; [3]]);;
val it : int list = [[2; 3]; [4]]
故事未完 2024-07-11 18:17:52

(编辑:一个小Ocaml FP Koan 开始)

<块引用>

咖喱公案(关于食物的公案,但与食物无关)

<块引用>

一名学生找到雅克·加里格 (Jacques Garrigue) 说:“我不明白柯里化有什么好处。” 雅克回答说:“告诉我你最喜欢的一餐和你最喜欢的甜点”。 这位困惑的学生回答说,他喜欢御好烧和寒天,但是虽然他最喜欢的餐厅提供美味的御好烧,但第二天早上他们的寒天总是让他胃痛。 于是,雅克带学生去一家餐厅吃饭,那里的御好烧和学生最喜欢的一样好,然后带他穿过城镇到一家制作美味寒天的商店,学生高兴地吃掉了剩下的胃口。 学生很满足,但他并没有开悟……直到第二天早上,他醒来时,胃部感觉很好。


我的示例将介绍如何使用它来重用和封装代码。 一旦您查看这些内容,就会发现这一点相当明显,并且应该为您提供一个具体、简单的示例,您可以考虑将其应用于多种情况。

我们想要在树上绘制地图。 如果该函数需要多个参数,则可以对该函数进行柯里化并应用于每个节点——因为我们将在节点上应用该参数作为它的最终参数。 它不必进行柯里化,但编写另一个函数(假设该函数在其他实例中与其他变量一起使用)将是一种浪费。

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。

(Edit: a small Ocaml FP Koan to start things off)

The Koan of Currying (A koan about food, that is not about food)

A student came to Jacques Garrigue and said, "I do not understand what currying is good for." Jacques replied, "Tell me your favorite meal and your favorite dessert". The puzzled student replied that he liked okonomiyaki and kanten, but while his favorite restaurant served great okonomiyaki, their kanten always gave him a stomach ache the following morning. So Jacques took the student to eat at a restaurant that served okonomiyaki every bit as good as the student's favorite, then took him across town to a shop that made excellent kanten where the student happily applied the remainder of his appetite. The student was sated, but he was not enlightened ... until the next morning when he woke up and his stomach felt fine.

My examples will cover using it for the reuse and encapsulation of code. This is fairly obvious once you look at these and should give you a concrete, simple example that you can think of applying in numerous situations.

We want to do a map over a tree. This function could be curried and applied to each node if it needs more then one argument -- since we'd be applying the one at the node as it's final argument. It doesn't have to be curried, but writing another function (assuming this function is being used in other instances with other variables) would be a waste.

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

but this is the same as:

let sample_tree2 = tree_map (fun x -> x * 3) sample_tree

So this simple case isn't convincing. It really is though, and powerful once you use the language more and naturally come across these situations. The other example with some code reuse as currying. A recurrence relation to create prime numbers. Awful lot of similarity in there:

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

Ok, now rowland and cloitre are curried functions, since they have free variables, and we can get any index of it's sequence without knowing or worrying about f_recurrence.

云巢 2024-07-11 18:17:52

柯里化描述了将具有多个参数的函数转换为单参数函数链的过程。 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# 中,函数声明默认是柯里化的。 通常不需要柯里化函数; 他们已经被咖喱了。 主要的例外是框架方法和其他重载函数,它们采用包含多个参数的元组。 因此,您可能想要柯里化这样的函数,事实上,当我寻找一个可以执行此操作的库函数时,我遇到了这个问题。 我想它丢失了(如果确实如此),因为实现起来非常简单:

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

Currying describes the process of transforming a function with multiple arguments into a chain of single-argument functions. Example in C#, for a three-argument function:

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));
}

Now, the boolean argument is probably not the argument you'd most likely want to leave open with a partial application. This is one reason why the order of arguments in F# functions can seem a little odd at first. Let's define a different C# curry function:

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);
}

Now, we can do something a little more useful:

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));
        }
    }
}

Why are these examples in C#? Because in F#, function declarations are curried by default. You don't usually need to curry functions; they're already curried. The major exception to this is framework methods and other overloaded functions, which take a tuple containing their multiple arguments. You therefore might want to curry such functions, and, in fact, I came upon this question when I was looking for a library function that would do this. I suppose it is missing (if indeed it is) because it's pretty trivial to implement:

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"

To get around the failure with String.Compare, since as far as I can tell there's no way to specify which 3-argument overload to pick, you can use a non-general solution:

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

I won't go into detail about the uses of partial function application in F# because the other answers have covered that already.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文