F#咖喱函数

时间:2020-03-05 18:39:05  来源:igfitidea点击:

任何人都有一个体面的例子,最好是实际的/有用的,他们可以发表说明这个概念的例子吗?

解决方案

回答

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

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()。

回答

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

我的示例将涵盖将其用于代码的重用和封装。一旦我们看了这些,这是相当明显的,并且应该给我们一个具体的,简单的示例,我们可以考虑将其应用在多种情况下。

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

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。

回答

虽然前面的示例回答了这个问题,但下面是两个更简单的示例,说明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];;

回答

我们知道可以在列表上映射功能吗?例如,映射一个函数以向列表的每个元素添加一个:

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

如果不进行粗略介绍,我们将无法部分应用这些功能,而必须编写类似以下内容的代码:

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