currying 是函数式语言中经常遇到的一个概念,翻译成 柯里化,不是库里化。

currying 指的是将接收多个参数的函数变换成接收一个单一参数,并且返回接收余下的参数而且返回结果的新函数的技术。

说起来比较拗口,直接看下面的代码。

    def add(x: Int, y: Int): Int = x + y

    //call add
add(1, 2)
add(3, 4)

但是如果我们使用 currying 的写法,那么可以将两个参数分开,接收第一个参数(x),然后返回一个函数,这个函数以第二个参数(y)为参数,返回结果:

    def add(x:Int) => (y: Int) = x + y

然后我们就可以这样调用 add 了。

    add(1)(2)
add(3)(4)

scala 对于这种写法提供了语法糖,add 的 currying 写法我们可以更简单地写为:

    def add(x: Int)(y: Int): Int = x + y

尝试将一个函数(这里指f)写成 curry 方式:

    def curry[A, B, C](f: (A, B) => C): A => (B => C) = a => b => f(a, b)

这里的 currying 函数,它接收两个参数,分别为 A 类型和 B 类型,然后返回一个 C 类型的结果。那么我们可以通过得到一个偏函数将其转化为 curry 的方式,即 A => (B => C)。具体流程是,用第一个参数 a ,得到一个函数,这个函数使用 b 作为参数,然后得到结果。这里中间的这个函数,是一个偏函数。

同样我们也可以写一个 uncurry 的方法(即将 currying 的函数f转化成非 currying 的):

    def uncurry[A, B, C](f: A => B => C): (A, B) => C =
(a, b) => f(a)(b)

这里在 currying 的过程中,使用了偏函数(partial applied function),偏函数是什么?这个其实更好理解一些,通俗的讲,可以理解为定制化函数,比如我们有一个函数需要两个参数,那么如果我们返回固定其中一个参数的函数,那么我们就得到了一个偏函数:

    def add(x: Int, y: Int): Int = x + y
def add1(y: Int): Int = add(1, y)

这里我们就是固定了 add 函数中的 x 参数,然后返回了另外一个函数 add1,现在 add1 只有一个参数,我们直接可以调用 add1:

    add1(5) //6

可以看到,currying 和偏函数的主要区别就是,是否固定了参数,偏函数一旦固定参数之后,就返回了新的函数。而 currying 一般并没有固定参数,只是类似于使用了黑魔法,让多个参数的函数,最后变成了一个函数链。

其实在 C 语言中也有偏函数这种用法:

int foo(int a, int b, int c) {
return a + b + c;
} int foo23(int a, int c) {
return foo(a, 23, c);
}

但是在函数式语言中,偏函数以及 currying 能发挥更大的作用,原因是,函数式中,我们的参数还可以是函数。

def sum(f: Int => Int, a: Int, b: Int): Int =
if(a > b) 0
else f(a) + sum(a+1, b)

这里有一个 sum 函数,它将 a 和 b 之间的所有的数,用 f 计算之后相加,比如:

    sum(x => x * x, 1, 10)

那么就是计算 1 到 10 之间所有数的平方和。但是如果我们要计算所有数的立方和呢,四次方呢?

固然我们可以将 f 传递成不同的值,但是我们也可以更简单地直接得到定制化之后的函数,即用不同的 f 得到不同的函数。

为了达到我们的目的,我们将函数写成 currying 形式:

def sum(f: Int => Int)(a: Int, b: Int): Int =
if(a >b) 0 else f(a) + sum(f)(a+1, b)

这个时候 sum 函数的类型是什么呢?是:(Int => Int) => (( Int, Int) => Int)。第一层函数是一个参数类型为 Int => Int 的函数,其返回是一个函数,这个函数以 (Int, Int) => Int 类型为返回值。因此,如果我们传递不同的第一层函数的参数,即 Int => Int,那么就将得到不同的第二层函数,即类型为 (Int, Int) => Int 的函数。

因此,我们可以传入不同的 f,传入 f 之后,得到的函数就是以 a 和 b 为参数的函数:

为此我们将 sumT 函数定义成如下形式,使得 sumT 函数返回一个类型为 (Int, Int) => Int 的函数:

def sumT(f: Int => Int): (Int, Int) => Int = {
def sumF(a: Int, b: Int): Int =
if(a > b) 0
else f(a) + sumF(a+1, b)
sumF
}

然后我们就可以传递不同的 f,获取不同的偏函数:

def sum3 = sum(x => x * x * x)  //三次方
def sum4 = sum(x => x * x * x * x) //四次方

这里可以看出,偏函数和 currying 是有很大的关联的。函数 currying 后,很容易实现某些偏函数。

在 scala 中,如果需要减少函数的参数,即固定函数的某些参数,那么使用偏函数即可。那么,currying的作用是什么?

作用有二,一个是可以将函数的参数写成{}形式,这对于参数是函数时可读性更好。

在currying之前,可能需要这样写:

using (new File(name), f => {......})

在有了 currying 之后,我们可以写成更易读的方式:

using(new File(name)){ f => ......}

第二个作用就是用于类型推导:

一般的 currying 可以写成

def f(arg1)(arg2)......(argn) => E
//等同于
def f(arg1)(arg2)......(argn-1) => { def g(argn) => E; g}
/.更简单点
def f(arg1)(arg2)......(argn-1) => (argsn => E)
//继续最后就可以写成
def f = args1 => args2......=>argsn => E

这样写之后,scala 可以对参数之间的类型进行推导:左边参数的类型可以用于推导右边参数的类型,从而某些参数就可以简化写法:

def dropWhile[A](as: List[A], f: A => Boolean): List[A] = ......
//调用
dropWhile(List(1,2,3,4), (x: Int => x < 4)) //currying之后
dropWhile[A](as:List[A])(f: A => Boolean): List[A] = ......
//调用
dropWhile(List(1,2,3,4))(x => x < 4) //这里不再需要指明 x 的类型

这样就可以用来结合匿名函数的下划线写出更加简洁的代码。

currying 也可以在某些只允许使用单个参数的函数的情景下发挥作用,利用 currying 使用层层嵌套的单参数函数,可以实现语法层面的多参数函数。


[1]: http://stackoverflow.com/questions/8063325/usefulness-as-in-practical-applications-of-currying-v-s-partial-application-i

[2]: http://www.vaikan.com/currying-partial-application/

[3]: http://www.codecommit.com/blog/scala/function-currying-in-scala

[4]: https://book.douban.com/subject/26772149/

[5]: https://www.coursera.org/learn/progfun1/lecture/fOuQ9/lecture-2-2-currying

函数式中的 currying的更多相关文章

  1. 函数式编程之-Currying

    这个系列涉及到了F#这门语言,也许有的人觉得这样的语言遥不可及,的确我几乎花了2-3年的时间去了解他:也许有人觉得学习这样的冷门语言没有必要,我也赞同,那么我为什么要花时间去学习呢?作为一门在Tiob ...

  2. 函数式编程之-定义能够支持Partial application的函数

    是时候介绍如何在F#中定义函数了,在你没有接触过函数式编程语言之前,你也许会觉得C#/Java的语法已经够丰富了,有什么任务做不了呢?当你读过函数式编程之Currying和函数式编程之Partial ...

  3. Vim中的正则表达式[转]

    来自:http://blog.csdn.net/endall/archive/2007/08/29/1764554.aspx Vim中的正则表达式功能很强大,如果能自由运用,则可以完成很多难以想象的操 ...

  4. VIM中的正则表达式及替换命令

    VIM中的正则表达式及替换命令 一.使用正则表达式的命令 使用正则表达式的命令最常见的就是 / (搜索)命令.其格式如下: /正则表达式 另一个很有用的命令就是 :s(替换)命令,将第一个//之间的正 ...

  5. Excel自动从身份证中提取生日、性别、年龄

    现在学生的身份证号已经全部都是18位的新一代身份证了,里面的数字都是有规律的.前6位数字是户籍所在地的代码,7-14位就是出生日期.第17位“2”代表的是性别,偶数为女性,奇数为男性.我们要做的就是把 ...

  6. vim中使用正則表達式

    一.使用正則表達式的命令 使用正則表達式的命令最常见的就是 / (搜索)命令. 其格式例如以下: /正則表達式 还有一个非常实用的命令就是 :s(替换)命令,将第一个//之间的正則表達式替换成第二个/ ...

  7. Scala函数式编程(四)函数式的数据结构 上

    这次来说说函数式的数据结构是什么样子的,本章会先用一个list来举例子说明,最后给出一个Tree数据结构的练习,放在公众号里面,练习里面给出了基本的结构,但代码是空缺的需要补上,此外还有预留的test ...

  8. Reactor3 中文文档(用户手册)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  9. JS 柯里化 (curry)

    用 JS 理解柯里化 函数式编程风格,试图以函数作为参数传递(回调)和无副作用的返回函数(修改程序的状态). 很多语言采用了这种编程风格.JavaScript,Haskell,Clojure,Erla ...

随机推荐

  1. poj 3468(线段树)

    http://poj.org/problem?id=3468 题意:给n个数字,从A1 …………An m次命令,Q是查询,查询a到b的区间和,c是更新,从a到b每个值都增加x.思路:这是一个很明显的线 ...

  2. Linux压缩和解压汇总

    各种后缀的压缩包压缩方法和解压方法 压缩包 压缩 解压 .tar.gz和.tgz tar -czf jpg.tar.gz *.jpg tar -xzvf filename -C path .tar.b ...

  3. Scrapy003-项目流程

    Scrapy003-项目流程 @(Spider)[POSTS] 前两篇文章我们了解到Scrapy的原理和安装的相关知识,这节就需要知道创建项目流程的小知识. 根据官方文档:http://scrapy- ...

  4. java 数组中插入新的元素

    class Demo1 { public static void main(String[] args) { /* 5. 有如下歌曲数组 String[] musics = new String[]{ ...

  5. could not insert new action connection could not find any information for the class named

    玩代码的时候遇到一问题,用的 storyboard 的时候分页不能自动获取到类,Automatic 里是空的,然后自动选到该类下的时候控件貌似可以拖过去,但是当 cennect 的时候会报错:“cou ...

  6. iOS开发UI篇—核心动画(转场动画和组动画)

    转自:http://www.cnblogs.com/wendingding/p/3801454.html iOS开发UI篇—核心动画(转场动画和组动画) 一.转场动画简单介绍 CAAnimation的 ...

  7. C#设计模式之工厂方法

    工厂方法模式: 1.不再提供一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成 如果出现新的按钮类型,只需要为这种新类型的按钮定义一个具体的工厂类就可以创建该新按 ...

  8. 深入理解MVC模式

    一,什么是MVC模式 该模式是一种软件设计典范,他把软件系统划分为三个基本部分:模型层(Model).视图层(View).控制器(Controller) *Model(模型)表示应用程序核心(比如数据 ...

  9. jquery Ajax 案例

    html <div class="data"><ul></ul></div> <div id="load" ...

  10. CodeForces445A DZY Loves Chessboard

    A. DZY Loves Chessboard time limit per test 1 second memory limit per test 256 megabytes input stand ...