首页 文章

什么是'Currying'?

提问于
浏览
528

我在几篇文章和博客中看到了对curried函数的引用,但我找不到一个好的解释(或者至少有一个有意义的解释!)

15 回答

  • -1

    Currying是指您分解一个函数,该函数将多个参数带入一系列参数中的函数 . 这是JavaScript中的一个示例:

    function add (a, b) {
      return a + b;
    }
    
    add(3, 4); // returns 7
    

    这是一个带有两个参数a和b的函数,并返回它们的总和 . 我们现在将讨论这个功能:

    function add (a) {
      return function (b) {
        return a + b;
      }
    }
    

    这是一个函数,它接受一个参数a,并返回一个带有另一个参数b的函数,该函数返回它们的总和 .

    add(3)(4);
    
    var add3 = add(3);
    
    add3(4);
    

    第一个语句返回7,就像add(3,4)语句一样 . 第二个语句定义了一个名为add3的新函数,它将为其参数添加3 . 这是有些人可能称之为封闭的 . 第三个语句使用add3操作添加3到4,结果再次产生7 .

  • 5

    在函数的代数中,处理带有多个参数的函数(或等价的一个参数是一个N元组)有点不优雅 - 但是,正如MosesSchönfinkel(以及独立地,Haskell Curry)证明的那样,它不是必需的:所有你需要是一个参数的函数 .

    那么你如何处理你自然表达的事情,比如 f(x,y) ?那么,你把它当作 f(x)(y) - f(x) ,称之为 g ,是一个函数,并将该函数应用于 y . 换句话说,你只有一个参数的函数 - 但其中一些函数返回其他函数(也可以带一个参数;-) .

    像往常一样,wikipedia有一个很好的摘要条目,有很多有用的指针(可能包括你最喜欢的语言;-)以及稍微严格的数学处理 .

  • 0

    这是一个具体的例子:

    假设您有一个计算作用在物体上的重力的函数 . 如果你不知道公式,你可以找到here . 此函数将三个必要参数作为参数 .

    现在,在地球上,你只想计算这个星球上物体的力量 . 在函数式语言中,您可以将地球的质量传递给函数,然后对其进行部分评估 . 你得到的是另一个只需要两个参数并计算地球上物体的引力的函数 . 这称为currying .

  • 86

    Currying是一种可以应用于函数的转换,允许它们比以前少一个参数 .

    例如,在F#中你可以定义一个函数: -

    let f x y z = x + y + z
    

    这里函数f取参数x,y和z并将它们加在一起,这样:

    f 1 2 3
    

    退货6 .

    根据我们的定义,我们可以为f定义咖喱函数: -

    let curry f = fun x -> f x
    

    'fun x -> f x'是一个lambda函数,与C#中的x => f(x)等效 . 此函数输入您希望curry的函数并返回一个函数,该函数接受一个参数并返回指定的函数,并将第一个参数设置为input参数 .

    使用我们之前的例子,我们可以获得f的咖喱: -

    let curryf = curry f
    

    然后我们可以做以下事情: -

    let f1 = curryf 1
    

    这为我们提供了与f1 y z = 1 y z等效的函数f1 . 这意味着我们可以做到以下几点: -

    f1 2 3
    

    哪个返回6 .

    这个过程经常与“部分功能应用”混淆,后者可以这样定义: -

    let papply f x = f x
    

    虽然我们可以将它扩展到多个参数,即:-

    let papply2 f x y = f x y
    let papply3 f x y z = f x y z
    etc.
    

    部分应用程序将获取函数和参数并返回需要一个或多个参数的函数,并且前两个示例show直接在标准F#函数定义中实现,因此我们可以实现以前的结果: -

    let f1 = f 1
    f1 2 3
    

    这将返回6的结果 .

    结论:-

    currying和部分功能应用之间的区别在于: -

    Currying接受一个函数并提供一个接受单个参数的新函数,并返回指定函数,并将其第一个参数设置为该参数 . 这允许我们将具有多个参数的函数表示为一系列单个参数函数 . 例:-

    let f x y z = x + y + z
    let curryf = curry f
    let f1 = curryf 1
    let f2 = curryf 2
    f1 2 3
    6
    f2 1 3
    6
    

    部分函数应用程序更直接 - 它接受一个函数和一个或多个参数,并返回一个函数,其中前n个参数设置为指定的n个参数 . 例:-

    let f x y z = x + y + z
    let f1 = f 1
    let f2 = f 2
    f1 2 3
    6
    f2 1 3
    6
    
  • 27

    它可以是一种使用函数来创建其他函数的方法 .

    在JavaScript的:

    let add = function(x){
      return function(y){ 
       return x + y
      };
    };
    

    允许我们像这样称呼它:

    let addTen = add(10);
    

    当这个运行时 10 作为 x 传入;

    let add = function(10){
      return function(y){
        return 10 + y 
      };
    };
    

    这意味着我们返回此功能:

    function(y) { 10 + y };
    

    所以当你打电话

    addTen();
    

    你真打电话:

    function(y) { 10 + y };
    

    所以,如果你这样做:

    addTen(4)
    

    它是一样的:

    function(4) { 10 + 4} // 14
    

    所以我们的 addTen() 总是在我们传入的任何内容中加上十个 . 我们可以用相同的方式制作类似的函数:

    let addTwo = add(2)       // addTwo(); will add two to whatever you pass in
    let addSeventy = add(70)  // ... and so on...
    
  • 1

    curried函数是几个重写的参数的函数,它接受第一个参数并返回一个接受第二个参数的函数,依此类推 . 这允许多个参数的函数部分应用它们的一些初始参数 .

  • 2

    这是Python中的一个玩具示例:

    >>> from functools import partial as curry
    
    >>> # Original function taking three parameters:
    >>> def display_quote(who, subject, quote):
            print who, 'said regarding', subject + ':'
            print '"' + quote + '"'
    
    
    >>> display_quote("hoohoo", "functional languages",
               "I like Erlang, not sure yet about Haskell.")
    hoohoo said regarding functional languages:
    "I like Erlang, not sure yet about Haskell."
    
    >>> # Let's curry the function to get another that always quotes Alex...
    >>> am_quote = curry(display_quote, "Alex Martelli")
    
    >>> am_quote("currying", "As usual, wikipedia has a nice summary...")
    Alex Martelli said regarding currying:
    "As usual, wikipedia has a nice summary..."
    

    (只需使用串联来避免非Python程序员分心 . )

    编辑添加:

    请参阅http://docs.python.org/library/functools.html?highlight=partial#functools.partial,它还以Python实现此方式的方式显示了部分对象与函数的区别 .

  • 690

    如果你明白 partial 你就在那里 . partial 的想法是预先应用函数的参数并返回一个只需要剩余参数的新函数 . 当调用这个新函数时,它包含预加载的参数以及提供给它的任何参数 .

    在Clojure中 + 是一个功能,但要明确说明:

    (defn add [a b] (+ a b))
    

    你可能知道 inc 函数只是将1加到它传递的任何数字上 .

    (inc 7) # => 8
    

    让我们自己使用_1384834 Build 它:

    (def inc (partial add 1))
    

    这里我们返回另一个函数,它将1加载到 add 的第一个参数中 . 由于 add 接受两个参数,因此新的 inc 函数只需要 b 参数 - 而不是之前的2个参数,因为1已经部分应用了 . 因此 partial 是一个工具,可以使用预先提供的默认值创建新函数 . 这就是为什么在功能语言中,函数通常会将参数从一般到特定 . 这使得重用这些函数更容易,从中构造其他函数 .

    现在想象一下,如果语言足够聪明,可以自省地理解 add 想要两个论点 . 当我们通过一个论证,而不是唠叨时,如果函数部分地应用了我们代表我们传递它的论点,我们可能意味着稍后提供另一个论点怎么办?然后我们可以在不明确使用 partial 的情况下定义 inc .

    (def inc (add 1)) #partial is implied
    

    这是一些语言的行为方式 . 当人们希望将函数组合成更大的变换时,它非常有用 . 这将导致一个换能器 .

  • 3

    我发现这篇文章及其引用的文章很有用,可以更好地理解currying:http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx

    正如其他人提到的,它只是一种具有单参数功能的方法 .

    这很有用,因为您不必假设将传入多少参数,因此您不需要2参数,3参数和4参数函数 .

  • 42

    库里可以简化您的代码 . 这是使用它的主要原因之一 . Currying是一个将接受n个参数的函数转换为只接受一个参数的n个函数的过程 .

    原理是传递传递函数的参数,使用closure(closure)属性将它们存储在另一个函数中并将其作为返回值处理,这些函数形成一个链,最后的参数传递给完成操作 .

    这样做的好处是它可以通过一次处理一个参数来简化参数处理,这也可以提高程序的灵活性和可读性 . 这也使程序更易于管理 . 将代码分成更小的部分将使其重用友好 .

    例如:

    function curryMinus(x) 
    {
      return function(y) 
      {
        return x - y;
      }
    }
    
    var minus5 = curryMinus(1);
    minus5(3);
    minus5(5);
    

    我也可以......

    var minus7 = curryMinus(7);
    minus7(3);
    minus7(5);
    

    这非常适合复杂的代码整洁和处理非同步方法等 .

  • 28

    curried函数应用于多个参数列表,而不仅仅是一个 .

    这是一个常规的非curried函数,它添加了两个Int参数x和y:

    scala> def plainOldSum(x: Int, y: Int) = x + y
    plainOldSum: (x: Int,y: Int)Int
    scala> plainOldSum(1, 2)
    res4: Int = 3
    

    这是类似的功能,咖喱 . 而不是一个两个Int参数列表,您将此函数应用于两个一个Int参数列表:

    scala> def curriedSum(x: Int)(y: Int) = x + y
    curriedSum: (x: Int)(y: Int)Intscala> second(2)
    res6: Int = 3
    scala> curriedSum(1)(2)
    res5: Int = 3
    

    这里发生的是当你调用 curriedSum 时,你实际上得到了两个传统的函数调用 . 第一个函数调用采用名为 x 的单个Int参数,并返回第二个函数的函数值 . 第二个函数采用Int参数 y .

    这是一个名为 first 的函数,它在精神上完成了 curriedSum 的第一个传统函数调用:

    scala> def first(x: Int) = (y: Int) => x + y
    first: (x: Int)(Int) => Int
    

    将1应用于第一个函数 - 换句话说,调用第一个函数并传入1 -yields第二个函数:

    scala> val second = first(1)
    second: (Int) => Int = <function1>
    

    将2应用于第二个函数会产生结果:

    scala> second(2)
    res6: Int = 3
    
  • 1

    currying的一个例子就是当你拥有函数时,你只知道其中一个参数:

    例如:

    func aFunction(str: String) {
        let callback = callback(str) // signature now is `NSData -> ()`
        performAsyncRequest(callback)
    }
    
    func callback(str: String, data: NSData) {
        // Callback code
    }
    
    func performAsyncRequest(callback: NSData -> ()) {
        // Async code that will call callback with NSData as parameter
    }
    

    这里,由于在将它发送到 performAsyncRequest(_:) 时你不知道回调的第二个参数,你必须创建另一个lambda / closure来将它发送给函数 .

  • 1

    正如所有其他答案currying有助于创建部分应用的功能 . Javascript不提供自动currying的本机支持 . 因此,上面提供的示例可能无助于实际编码 . 在lifecript中有一些很好的例子(基本上编译为js)http://livescript.net/

    times = (x, y) --> x * y
    times 2, 3       #=> 6 (normal use works as expected)
    double = times 2
    double 5         #=> 10
    

    在上面的例子中,当你给出较少的参数时,livescript会为你生成新的curried函数(double)

  • 2

    给出一个真实世界(也可能是有用的)currying示例,查看如何使用fetch库在javascript中进行服务器调用

    Get(url) {
            let fullUrl = toFullUrl(url);
    
            let promise = getPromiseForFetchWithToken((token) => {
    
            let headers = Object.assign(
                getDefaultHeaders(token),
                jsonHeaders);
    
            let config = {
                method: "GET",
                headers: headers
            };
    
            return fetch(fullUrl, config);
        });
    
        return promise;
    }
    

    其中 getPromiseForFetchWithToken 是一个curried函数,它返回带有fetch结果的 Promise ,如下所示:

    function getPromiseForFetchWithToken(tokenConsumingFetch) {
       function resolver(resolve, reject) {
    
        let token = localStorage.getItem("token");
    
        tokenConsumingFetch(token)
            .then(checkForError)
            .then((response) => {
                if (response) resolve(response);
            })
            .catch(reject);
       }
    
       var promise = new Promise(resolver);
    
       return promise;
    }
    

    这允许您等待 Get 函数的调用,然后适当地处理返回值,无论它是什么,您可以在需要的任何地方重新使用 getPromiseForFetchWithToken 函数来进行需要包含承载令牌的服务器调用 . (放,删除,发布等)

  • 110

    在这里,您可以找到C#中currying实现的简单解释 . 在评论中,我试图展示currying如何有用:

    public static class FuncExtensions {
        public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
        {
            return x1 => x2 => func(x1, x2);
        }
    }
    
    //Usage
    var add = new Func<int, int, int>((x, y) => x + y).Curry();
    var func = add(1);
    
    //Obtaining the next parameter here, calling later the func with next parameter.
    //Or you can prepare some base calculations at the previous step and then
    //use the result of those calculations when calling the func multiple times 
    //with different input parameters.
    
    int result = func(1);
    

相关问题