Scala基础

1. 介绍

柯里化(currying, 以逻辑学家Haskell Brooks Curry的名字命名)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数。 在Scala中方法和函数有细微的差别,通常编译器会自动完成方法到函数的转换。如果想了解Scala方法和函数的具体区别,请参考博文Scala基础 - 函数和方法的区别

2. Scala中柯里化的形式

Scala中柯里化方法的定义形式和普通方法类似,区别在于柯里化方法拥有多组参数列表,每组参数用圆括号括起来,例如:

def multiply(x: Int)(y: Int): Int = x * y

multiply方法拥有两组参数,分别是(x: Int)和(y: Int)。
multiply方法对应的柯里化函数类型是:

Int => Int => Int

柯里化函数的类型声明是右结合的,即上面的类型等价于:

Int => (Int => Int)

表明该函数若只接受一个Int参数,则返回一个Int => Int类型的函数,这也和柯里化的过程相吻合。

3. 探究柯里化函数

我们仍以上面定义的multiply方法为例探索柯里化的一些细节:

def multiply(x: Int)(y: Int): Int = x * y

上面的代码定义了一个柯里化方法,在Scala中可以直接操纵函数,但是不能直接操纵方法,所以在使用柯里化方法前,需要将其转换成柯里化函数。最简单的方式是使用编译器提供的语法糖:

val f = multiply _

返回的函数类型是:

Int => Int => Int

使用Scala中的部分应用函数(partially applied function)技巧也可以实现转换,但是请注意转后后得到的并不是柯里化函数,而是一个接受两个(而不是两组)参数的普通函数:

val f = multiply(_: Int)(_: Int)

转后后得到的类型为:

(Int, Int) => Int

其实就是一个接受两个Int型参数的普通函数类型。

先传入第1个参数:

val f1 = f(1)

返回类型为:

Int => Int

即一个接受一个Int参数返回Int类型的函数。 继续传入第2个参数:

val f2 = f1(2)

返回类型为:

Int

两组参数都已经传入,返回一个Int类型结果。

4. 柯里化(currying)函数和部分应用函数(partial applied)的区别

下面代码定义一个普通方法multiply1和一个currying方法multiply2,并将其转换相应的函数类型:

1def multiply1(x: Int, y:Int, z:Int) = x * y * z 2val partialAppliedMultiply = multiply1 _ 3//类型:(x: Int, y: Int, z: Int) => Int 4 5def multiply2(x: Int)(y: Int)(z: Int) = x * y * z 6val curryingMultiply = multiply2 _ 7//类型:Int => (Int => (Int => Int))

在调用时,curryingMultiply可以依次传入各个参数,而partialAppliedMultiply在传入部分参数时,必须显示指定剩余参数的占位符:

1val curryingMultiply1 = curryingMultiply(1) 2//类型:Int => (Int => Int) 3 4val partialAppliedMultiply1 = partialAppliedMultiply(1, _:Int, _: Int) 5//类型:(Int, Int) => Int

另外,curryingMultiply1的类型仍然是currying类型,而partialAppliedMultiply1的类型仍然是普通函数类型。

5. 应用:控制抽象(Control Abstraction)

5.1 控制抽象介绍

对于一些通用的操作可以实现成控制抽象,例如像文件打开、关闭操作。实现成控制抽象的好处是,可以在使用的时候,看起来更像是语言级别提供的功能。

5.2 抽象控制的实现基础

5.2.1 无参函数

无参函数的类型是() => T,在使用时为了简化可以省略(),例如:

1 def runInThread(block: => Unit){ 2 new Thread { 3 override def run() { block } 4 }.start() 5 }

这样定义之后,在使用的时候就可以省略() =>,

1 runInThread{ 2 println("Hi") 3 }

5.2.2 使用{}替代()

如果方法只有一个参数,则可以使用{}替代(),例如:

1 runInThread{ 2 println("Hi") 3 }

5.2.3 传名参数(by-name parameter)

与传名参数相对的是传值参数。传值参数在函数调用之前表达式会被求值,例如Int,Long等数值参数类型;传名参数在函数调用前表达式不会被求值,而是会被包裹成一个匿名函数作为函数参数传递下去,例如高阶函数的函数参数就是传名参数。

5.3 控制抽象示例

withPrintWriter是一个柯里化方法,它接受两组参数,第1组参数是待操作的文件资源,第2组参数是操作文件资源的函数:

1def withPrintWriter(file: File)(op: PrintWriter => Unit) { 2 val writer = new PrintWriter(file) 3 try { 4 op(writer) 5 } finally { 6 writer.close() 7 } 8}

用法如下:

1withPrintWriter(new File("date.txt")) { 2 writer => writer.println(new java.util.Date) 3}

withPrintWriter确保文件资源在被使用之后一定会被关闭,并且在使用的时候,看起来更像是语言内置的关键字函数。

6. 参考

  1. Programming in Scala, 2nd Edition
  2. 快学Scala
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

什么是函数柯里化?

原文链接:什么是函数柯里化?函数柯里化(HaskellBrooksCurry),当一个函数有多个参数的时候先传递一部分参数并且调用它(这部分参数后续不会进行改变),然后返回一个新的函数接收剩余的参数并返回结果。总结柯里化可以让我们给一个函数传递较少的参数得

初识 JS 中的柯里化

作为函数式编程语言,JS带来了很多语言上的有趣特性,比如柯里化。1.简介柯里化(Currying),又称部分求值(PartialEvaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函