美文网首页
什么是柯里化

什么是柯里化

作者: ettingshausen | 来源:发表于2017-11-15 09:07 被阅读73次
    2017年11月 国子监

    柯里化的概念

    在函数式编程(Functional Programming)相关的文章中,经常能看到柯里化 (Currying)这个名词。它是数学家柯里(Haskell Curry)提出的。

    柯里化,用一句话解释就是,把一个多参数的函数转化为单参数函数的方法。

    这是一个两个参数的普通函数:

    function plus(x, y){
        return x + y
    }
    
    plus(1, 2) // 输出 3
    

    经过柯里化后这个函数变成这样:

    function plus(y){
        return function (x){
            return x + y
        }
    }
    
    plus(1)(2) // 输出 3
    

    复合函数与柯里化

    刚开始接触的时候,第一感觉和泰勒级数 (Taylor Series) 展开有些相似,然后我就去高数的书本里边翻,然而并没有 :) 实际上,编程相关的文章,只要提到柯里化,就是指的把一个多参数的函数转化为单参数函数的方法,只不过,文章中顺便提及了柯里化的由来,再加上 Haskell 高大上的编程语言,总觉着这得是个多复杂的公式。

    复合函数,中数学课本就有(忘记是高中还是初中了,反正大学学的都忘记了 :p),形式上是这样:

    $$F(x) = f(g(x))$$

    可以这么理解这个函数,从西安去北京,可以乘坐直达车,也可以从郑州中转。$$F(x)$$就是整个旅程,$$g(x)$$是从西安到郑州,$$f(x)$$是从郑州到北京。如果把公式用编程语言表示,就是这样:

    function travel(point2){
        let dest = '北京'
        return function(point1){
            return point1 + '->' + point2 
                          + '->' + dest
        }
    }
    
    travel('郑州')('西安') //输出 西安->郑州->北京
    

    柯里化的作用

    • 惰性求值 (Lazy Evaluation)

    从上文的代码来看,柯里化收的函数是分步执行的,第一次调用返回的是一个函数,第二次调用的时候才会进行计算。起到延时计算的作用,通过延时计算求值,称之为惰性求值。

    • 动态生成函数

    假如实际编程中需要求不通数的若干次幂(整数),可能需要求2次幂,也能需要4次幂或者其他次幂,如果不用柯里化,那么需要求几次幂,就得写几个对应方法。通过柯里化,可以写在一个方法中:

    function power(n){
        return function (number){
            let result = 1;
            for(let i = 0; i < n; ++i){
                result *= number;
            }
            return result;
        }
    }
    

    需要求平方的时候,可以直接生成一个求平方的方法。

    let p2 = power(2); 
    p2(4) // 输出16
    p2(5) // 输出25
    

    同样,需要求立方也可以直接生成一个求立方的方法,不用每个幂次都写一个方法。

    let p3 = power(3); //求立方
    p3(2) // 输出8
    p3(4) // 输出64
    

    柯里化与闭包

    闭包 (Closure) 在前端圈讨论的非常的多,不过多数为刚入门的同学。闭包的概念,看专业的解释非常的复杂:

    闭包包含自由(未绑定到特定对象)变量;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。

    这一大段话浓缩成一句话是,在一个函数内部定义一个局部变量,并通过一个函数将其返回。很明细上文提到的动态生成函数中用的例子就是一个闭包函数。

    从形式来看,柯里化和闭包非常的相似,这二者有什么关系呢?
    没什么关系,只不过看起来有些像。事实上,网络上大多的文章讨论柯里化与闭包都是分开来谈的,几乎没有明确的说明,他俩究竟有什么关系。首先,这是两个不通的概念,通过定义来看,作用也不同。只不过都用到了匿名函数。

    柯里化与Lambda表达式

    柯里化也经常与$$λ$$ (Lambda)表达式一起使用。提到Lambda表达式就必须得提到匿名函数。
    匿名函数,在底层语言中不是现成的,比如C语言中就没有匿名函数,因为C语言在函数调用之前都得声明一下,或者把函数定义在调用之前,所以也就谈不上匿名函数了:p,C++ 11中才引入了Lambda表达式,支持匿名函数。所以一些资料有时也把匿名函数直接等同于Lambda表达式。
    有种观点认为:

    从纯粹的语义上,柯里化就是Lambda表达式的一个糖

    这里其实就是把Lambda当作是匿名函数了。严格的来说,不是所有的匿名函数都是Lambda表达式。C# 2.0中引入了委托与匿名函数,3.0之后才引入Lambda表达式。

    list.Where( delegate( object item ) { 
            return item != null; 
        });
    

    这个是委托的写法

    list.Where( item => item != null );
    

    这个才是Lambda的写法(这里补充一点,经过自己的实践,其实在.NET 2.0的平台也可以使用Lambda表达式,只不过编译的时候调用的是高版本的编译器,所以在.NET 2.0的工程中使用高版本的C#特性,编译和运行都不会有问题)
    Lambda表达式有固定的规范,写成 lambda x . bodyprefix args separator expression)表示一个参数参数为 x 的函数,它的返回值为 body 的计算结果。在不同的语言中Lambda表达式的形式也是不同的,比如Python:

    lambda x : x + 1
    

    prefix 是可选的, 再比如Java

    item -> item != null
    

    这么说来,柯里化与Lambda表达式也没有关系。

    柯里化在Lambda表达式中应用

    从上文来看,纯正的Lambda表达式的规范,是只有个一个入参的,如果多个参数怎么办呢?
    柯里化的作用就能体现出来了,比如要实现一个两个数求和的表达式,用纯正的Lambda表达式,可以写成这样:

    let foo = (x) => {
        return y => x + y
    }
    
    foo(1)(2)  //输出 3
    

    这么显然有些麻烦,实践中是这么写的

    let foo = (x, y) => x + y
    
    foo(1, 2) //输出 3
    

    是不是实践中的写法不符合Lambda表达式的规范呢?
    可以理解为第二种写法为第一种写法的简写,是语法糖,第一种单个参数的写法是脱糖后的形式。

    相关文章

      网友评论

          本文标题:什么是柯里化

          本文链接:https://www.haomeiwen.com/subject/kjnqvxtx.html