【scala 笔记(5)】 Scala中的函数

本文详细介绍了Scala中的函数,包括函数定义、默认参数、带名参数和不定长参数。强调了函数作为值的概念,展示了匿名函数的使用。深入探讨了高阶函数和闭包,特别是柯里化(Currying)的应用,解释了其将多参数函数转化为一系列单参数函数的过程,以及在控制抽象和代码复用方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

scala 除了支持方法(方法是对object的操作,也就是java中定义的class的方法)外还支持函数的; C/C++、Python 也有函数, 而在Java中我们只能通过静态方法来模拟。

函数

函数定义

要定义一个函数,你要给出函数的名称、参数和函数体 ; 例如:

def fac(x:Int) = {
      var result = 1
      for (i <- 1 to x) result = result * i
      result
    }

你必须给出所有参数的类型。 不过,只要函数不是递归的,你就不需要指定返回类型, scala 会根据 = 号右边的表达式推导出返回的类型。或进行显式定义为:

def fac(x:Int) :Int = {
      var result = 1
      for (i <- 1 to x) result = result * i
      result
    }

注: 本例中不需要使用return,当然我们也可以使用return来立即从函数中退出, 不过在scala中使用return并不常见, 我们最好要适应没有return的日子, 我们可以把 return 当做 函数版本的 break 语句, 仅在需要时进行调用。

默认参数和带名参数

我们在调用某些函数时不显式的给出所有参数值,对于这些函数可以使用默认值:

scala> def show(msg:String, left:String="[", right:String="]"){
     |       println("%s%s%s".format(left, msg, right))
     |     }
show: (msg: String, left: String, right: String)Unit

scala> show("hello")    // 调用方式1
[hello]

scala> show("hello", "<", ">") // 调用方式2
<hello>

scala> show(left ="info[", msg ="hello") // 调用方式3
info[hello]

注: 这里定义的函数在Scala中被称为过程; Scala 对于不返回值的函数有特殊的表示法, 如果函数体包含在 {} 中但前面没有 = 号, 那么返回的类型为Unit。 这种函数被称为 过程(procedure) , 也可以显式定义为 Unit返回类型:

def show( ... ): Unit = {
    ......
} 

不定长参数

实现一个可变长参数的函数, 例如:

scala> def show(args: Any *){
     |       for (arg <- args){
     |         arg match {
     |           case i: Int =>
     |             println("type int: %d".format(i))
     |           case s: String =>
     |             println("type string: %s".format(s))
     |           case _ =>
     |             println("unknow type arg ")
     |         }
     |       }
     |     }
show: (args: Any*)Unit

scala> show(1, "2", 3.0)
type int: 1
type string: 2
unknow type arg

scala> show(1 to 3: _*) // 追加 : _* 告诉编译器希望参数被当做参数序列处理
type int: 1
type int: 2
type int: 3

作为值的函数

在scala中, 函数时 “头等公民” ,就和数字一样。 你可以在变量中存放函数(有点像C++中的函数指针):

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

scala> val func : (Int, Int) => Int = add
func: (Int, Int) => Int = $$Lambda$1502/1003761391@5fa842b5

scala> val func1 = add _ // _ 占位符
func1: (Int, Int) => Int = $$Lambda$1503/817093103@5d32d8f

匿名函数

Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体。使用匿名函数后,我们的代码变得更简洁了。

scala> val func = (x: Int) => 2*x
func: Int => Int = $$Lambda$1517/285812804@2640893e

scala> val func = 2*(_:Int)         // 精简版, 通过 _ 占位符指定类型来帮助函数推导出返回值
func: Int => Int = $$Lambda$1556/399984046@787aa9f1

scala> val func: Int=>Int = 2*_     // 精简版, 通过指定func的函数的类型
func: Int => Int = $$Lambda$1557/143470731@6ed810f3

原型

scala> def func = new Function1[Int, Int] {
     |       def apply(v: Int) : Int= {
     |         2 * v
     |       }
     |     }
func: Int => Int

高阶函数

高阶函数(Higher-Order Function)就是操作其他函数的函数。Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。

例如: 常用要熟悉的高阶函数

scala> val lst = 1::2::3::Nil
lst: List[Int] = List(1, 2, 3)

scala> lst.map(2*_)
res35: List[Int] = List(2, 4, 6)

scala> lst.foreach(println)
1
2
3

看下高阶函数如何定义通过源码:

/** 我们最关心的仅是 (f: A => B)函数  
 *   A:表示List[T] T的类型, 
 *   B:表示map后的List[T] T的类型, 取决于传入的函数定义
 */ 

final override def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That = {
    if (bf eq List.ReusableCBF) {
      if (this eq Nil) Nil.asInstanceOf[That] else {
        val h = new ::[B](f(head), Nil)
        var t: ::[B] = h
        var rest = tail
        while (rest ne Nil) {
          val nx = new ::(f(rest.head), Nil)
          t.tl = nx
          t = nx
          rest = rest.tail
        }
        h.asInstanceOf[That]
      }
    }
    else super.map(f)
  }

/** 这个比较简单, 直接进行List遍历调用 f函数 
 */ 
@inline final override def foreach[U](f: A => U) {
    var these = this
    while (!these.isEmpty) {
      f(these.head)
      these = these.tail
    }
  }

闭包

在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。运行时,一旦外部的 函数被执行,一个闭包就形成了,闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用。其中所引用的变量称作上值(upvalue)。

闭包一词经常和匿名函数混淆。这可能是因为两者经常同时使用,但是它们是不同的概念。

scala> val flag = 2
flag: Int = 2

scala> def func(x:Int) = (v: String) =>{
     |       for(i <- 1 to flag*x){
     |         println(v * i)
     |       }
     |     }
func: (x: Int)String => Unit

scala> val f = func(1)
f: String => Unit = $$Lambda$1574/696606344@dcd6f19

scala> f("*")
*
**

scala> val f = func(2)
f: String => Unit = $$Lambda$1574/696606344@74d16083

scala> f("*")
*
**
***
****

柯里化(Currying)

柯里化把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数。

可以换个逻辑理解:
看做是一个数学问题,一个逐次消元的过程。当把函数的元全消掉,就得到了值,值就是零元函数。 例如:
- 二元函数 f(x,y) = x+y
- 在y=1时, g(x) = f(x,1) = x+1
- 在x=2时, g(2) = 2+1=3

接受两个参数的函数

// 函数类型 (Int, Int) => Int
scala> def add(x:Int, y:Int) :Int = {
     |       x+y
     |     }
add: (x: Int, y: Int)Int

柯里化

// 函数类型 Int => (Int => Int)
scala> def add(x:Int) = (y:Int) => x+y
add: (x: Int)Int => Int

那么如何从 函数转换为 柯里化?

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

scala> val f1= add _
f1: (Int, Int) => Int = $$Lambda$1580/18999287@57715987

scala> f1.curried // 柯里化后的函数形式
res39: Int => (Int => Int) = scala.Function2$$Lambda$1433/579868782@12855741
  • 关于 curried 函数

// 看 scala Function2 的定义, 它会将自己转换一个柯里化版本的函数

trait Function2[@specialized(scala.Int, scala.Long, scala.Double) -T1, @specialized(scala.Int, scala.Long, scala.Double) -T2, @specialized(scala.Unit, scala.Boolean, scala.Int, scala.Float, scala.Long, scala.Double) +R] extends AnyRef { self =>
  /** Apply the body of this function to the arguments.
   *  @return   the result of function application.
   */
  def apply(v1: T1, v2: T2): R
  /** Creates a curried version of this function.
   *
   *  @return   a function `f` such that `f(x1)(x2) == apply(x1, x2)`
   */
  @annotation.unspecialized def curried: T1 => T2 => R = {
    (x1: T1) => (x2: T2) => apply(x1, x2)
  }
  /** Creates a tupled version of this function: instead of 2 arguments,
   *  it accepts a single [[scala.Tuple2]] argument.
   *
   *  @return   a function `f` such that `f((x1, x2)) == f(Tuple2(x1, x2)) == apply(x1, x2)`
   */

  @annotation.unspecialized def tupled: Tuple2[T1, T2] => R = {
    case Tuple2(x1, x2) => apply(x1, x2)
  }
  override def toString() = "<function2>"
}

那么问题来了,绕那么大的圈, 柯里化有什么实用场景呢?

  • 例如 控制抽象
 def until(condition: => Boolean)(block: => Unit): Unit = {
      if ( !condition ){
        block
        until(condition)(block)
      }
    }

var flag = 3
// 柯里化
until( flag<0 ) {
      println(flag)
      flag -= 1
    }

// 如果是接受多参函数
// def until(condition: => Boolean, block: => Unit): Unit = { ... }
// until( ... ,  { .... } )

虽然没有柯里化也可以实现相应功能,看起来可能并不美观,但柯里化并不是看起来美观那么简单, 而是函数式编程的一个新的概念。

优点:
- 代码复用;
- 代码逻辑设计分层;
- 抽象计算等;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值