Scala的函数式编程与高阶函数,匿名函数,偏函数,函数的闭包、柯里化,抽象控制,懒加载等

news2025/1/19 11:13:02

Scala的函数式编程

函数式编程
解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。

  • 例如:请求->用户名、密码->连接 JDBC->读取数据库
  • Scala 语言是一个完全函数式编程语言。万物皆函数。
  • 函数的本质:函数可以当做一个值进行传递。
  • 在 Scala 中函数式编程和面向对象编程完美融合在一起了。

函数的基本语法和使用

函数基本语法

在Scala中,函数是一等公民,可以被赋值给变量、作为参数传递给其他函数,以及作为返回值返回。

  1. 函数的基本语法:
    在这里插入图片描述
  2. 使用案例:
// 定义一个函数,计算两个整数的和
def add(a: Int, b: Int): Int = {
  a + b
}

// 调用函数
val result = add(3, 4)
println(result) // 输出结果为7

函数使用def关键字进行定义,并且需要指定参数列表和返回类型。在函数体中,可以使用return关键字返回一个值,也可以省略return关键字,将最后一行作为返回值。

函数定义

  1. 函数定义

(1) 函数 1:无参,无返回值

(2) 函数 2:无参,有返回值

(3) 函数 3:有参,无返回值

(4) 函数 4:有参,有返回值

(5) 函数 5:多参,无返回值

(6) 函数 6:多参,有返回值

  1. 案例实操

下面示例函数的不同形式的定义:

object Test_FunctionDefine {
  def main(args: Array[String]): Unit = {
    //    (1)函数1:无参,无返回值
    def f1(): Unit = {
      println("1. 无参,无返回值")
    }
    f1()
    println(f1())

    println("=========================")

    //    (2)函数2:无参,有返回值
    def f2(): Int = {
      println("2. 无参,有返回值")
      return 12
    }
    println(f2())

    println("=========================")

    //    (3)函数3:有参,无返回值
    def f3(name: String): Unit = {
      println("3:有参,无返回值 " + name)
    }

    println(f3("alice"))

    println("=========================")

    //    (4)函数4:有参,有返回值
    def f4(name: String): String = {
      println("4:有参,有返回值 " + name)
      return "hi, " + name
    }

    println(f4("alice"))

    println("=========================")

    //    (5)函数5:多参,无返回值
    def f5(name1: String, name2: String): Unit = {
      println("5:多参,无返回值")
      println(s"${name1}${name2}都是我的好朋友")
    }

    f5("alice","bob")

    println("=========================")

    //    (6)函数6:多参,有返回值
    def f6(a: Int, b: Int): Int = {
      println("6:多参,有返回值")
      return a + b
    }

    println(f6(12, 37))
  }
}

函数和方法的区别

  1. 核心概念
    (1) 为完成某一功能的程序语句的集合,称为函数。
    (2) 类中的函数称之方法。
  2. 案例实操
    (1) Scala 语言可以在任何的语法结构中声明任何的语法
    (2) 函数没有重载和重写的概念;方法可以进行重载和重写
    (3) Scala 中函数可以嵌套定义。
object Test_FunctionAndMethod {
  def main(args: Array[String]): Unit = {
    // 定义函数
    def sayHi(name: String): Unit = {
      println("hi, " + name)
    }

    // 调用函数
    sayHi("alice")

    // 调用对象方法
    Test01_FunctionAndMethod.sayHi("bob")

    // 获取方法返回值
    val result = Test01_FunctionAndMethod.sayHello("cary")
    println(result)
  }

  // 定义对象的方法
  def sayHi(name: String): Unit = {
    println("Hi, " + name)
  }

  def sayHello(name: String): String = {
    println("Hello, " + name)
    return "Hello"
  }
}

Scala 中函数嵌套

object TestFunction {

// (2)方法可以进行重载和重写,程序可以执行
def main(): Unit = {
}
def main(args: Array[String]): Unit = {
// (1)Scala 语言可以在任何的语法结构中声明任何的语法
import java.util.Date
new Date()

// (2)函数没有重载和重写的概念,程序报错
def test(): Unit ={
println("无参,无返回值")
}
test()
def test(name:String):Unit={
println()
}

//(3)Scala 中函数可以嵌套定义
def test2(): Unit ={
    println("函数可以嵌套定义")
    }
   }
 }
}

除了常规的函数定义,Scala还支持匿名函数、高阶函数、偏函数等概念,下面将逐一介绍。

匿名函数

匿名函数是一种没有命名的函数,它可以直接作为表达式传递给其他函数或赋值给变量。以下是匿名函数的示例:

// 定义一个函数,接受一个函数和两个整数作为参数,并将结果打印出来
def operate(f: (Int, Int) => Int, a: Int, b: Int): Unit = {
  val result = f(a, b)
  println(result)
}

// 调用operate函数,并传递一个匿名函数作为参数
operate((a, b) => a + b, 3, 4) // 输出结果为7

上述示例中,匿名函数(a, b) => a + b接受两个整数并返回它们的和。将该匿名函数作为参数传递给operate函数后,可以在operate函数内部调用该匿名函数并得到结果。

高阶函数

高阶函数是指接受一个或多个函数作为参数,并/或返回一个函数的函数。在Scala中,高阶函数的定义和使用非常灵活,以下是高阶函数的语法规则和几种常见类型的示例:

高阶函数常见类型

1. 参数为函数的高阶函数:

def operate(f: (Int, Int) => Int, a: Int, b: Int): Int = {
  f(a, b)
}

val add = (a: Int, b: Int) => a + b
val result = operate(add, 3, 4) // 调用operate函数,传入add函数作为参数
println(result) // 输出结果为7

在上述示例中,operate函数接受一个函数f作为参数,并将ab作为实参调用了f函数。

2. 函数返回值为函数的高阶函数:

def multiplyBy(factor: Int): Int => Int = {
  (x: Int) => x * factor
}

val multiplyByTwo = multiplyBy(2) // 调用multiplyBy函数,返回一个新的函数
val result = multiplyByTwo(5) // 调用返回的新函数
println(result) // 输出结果为10

在上述示例中,multiplyBy函数返回了一个新的函数,新函数会将其参数与factor相乘。这里 Int => Int 代表参数为Int类型,返回值也为Int类型的函数,这种是对lambda表达式的简写。

3. 参数和返回值都为函数的高阶函数

def compose(f: Int => Int, g: Int => Int): Int => Int = {
  (x: Int) => f(g(x))
}
val addOne = (x: Int) => x + 1
val multiplyByTwo = (x: Int) => x * 2
val result = compose(addOne, multiplyByTwo)(3) // 调用compose函数,并传入两个函数作为参数
println(result) // 输出结果为7

在上述示例中,compose函数接受两个函数fg作为参数,并返回一个新的函数,新函数将f应用于g的结果。输出结果为7,函数执行的过程为:定义了两个函数分别为:addOne和multiplyByTwo,调用compose(f, g)函数,将addOne和multiplyByTwo函数作为参数传入,则返回一个新的函数,参数类型为一个Int,函数体为f(g(x)),那么返回的新函数为:addOne( multiplyByTwo ( x: Int ) ),调用compose(addOne, multiplyByTwo)(3)函数,x参数为3,里层函数为 multiplyByTwo(3) => 6,结果返给外层函数addOne(x: Int),最终结果7.


高阶函数是指接受一个或多个函数作为参数,并/或返回一个函数的函数。Scala提供了多种高阶函数,如mapflatMapfilter等。

scala常见的高阶函数及使用方法

  • map:对集合中的每个元素应用一个函数,并返回一个新的集合。
val numbers = List(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map(x => x * x)
println(squaredNumbers) // 输出结果为List(1, 4, 9, 16, 25)
  • flatMap:将集合中的每个元素应用一个函数,并将结果展平为一个新的集合。
val words = List("Hello", "World")
val letters = words.flatMap(word => word.toCharArray)
println(letters) // 输出结果为List(H, e, l, l, o, W, o, r, l, d)
  • filter:根据给定条件过滤集合中的元素。
val numbers = List(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter(x => x % 2 == 0)
println(evenNumbers) // 输出结果为List(2, 4)
  • reduce:将集合中的元素逐个进行累积计算。
val numbers = List(1, 2, 3, 4, 5)
val sum = numbers.reduce((a, b) => a + b)
println(sum) // 输出结果为15
  • fold:将集合中的元素逐个进行累积计算,可以指定一个初始值。
val numbers = List(1, 2, 3, 4, 5)
val sum = numbers.fold(0)((a, b) => a + b)
println(sum) // 输出结果为15
  • groupBy:根据给定的函数对集合中的元素进行分组。
val numbers = List(1, 2, 3, 4, 5)
val groupedNumbers = numbers.groupBy(x => x % 2)
println(groupedNumbers) // 输出结果为Map(1 -> List(1, 3, 5), 0 -> List(2, 4))

通过使用这些常见的高阶函数,您可以以一种函数式的方式对集合进行处理和转换,简化代码,并提高代码的可读性和可维护性。

偏函数的使用

偏函数是一种只对部分输入值进行定义的函数。Scala的偏函数使用PartialFunction类表示。以下是一个使用偏函数的示例:

val divide: PartialFunction[Int, Int] = {
  case d: Int if d != 0 => 42 / d
}

val result1 = divide(6)  // 输出结果为7
val result2 = divide(0)  // 抛出MatchError异常

上述示例中的偏函数divide定义了一个对整数进行除法运算的偏函数,只有当除数不为零时才能有效计算。


如果一个方法中没有match 只有case,这个函数可以定义成PartialFunction偏函数。偏函数定义时,不能使用括号传参,默认定义PartialFunction中传入一个值,匹配上了对应的case,返回一个值,只能匹配同种类型。

一个case语句就可以理解为是一段匿名函数。

/**
  * 一个函数中只有case 没有match ,可以定义成PartailFunction 偏函数
  */
object Lesson_PartialFunction {
  def MyTest : PartialFunction[String,String] = {
    case "scala" =>{"scala"}
    case "hello" =>{"hello"}
    case _ => {"no  match ..."}
  }
  def main(args: Array[String]): Unit = {
      println(MyTest("scala"))
  }
}
/**
  * 第二个例子
  * map和collect的区别。
  */
def main(args: Array[String]): Unit = {
  val list1 = List(1, 3, 5, "seven") map {
    MyTest
  }//List(1, 3, 5, "seven") map { case i: Int => i + 1 }
  list1.foreach(println)
  val list = List(1, 3, 5, "seven") collect {
    MyTest
  }//List(1, 3, 5, "seven") collect { case i: Int => i + 1 }
  list.foreach(println)
}
def MyTest: PartialFunction[Any, Int] = {
  case i: Int => i + 1
}

函数的柯里化、抽象控制和惰性加载

柯里化(Currying)

  • 柯里化(Currying):柯里化是将接受多个参数的函数转换为接受一个参数的函数序列的过程。这可以提供更高的灵活性和复用性。
def add(a: Int)(b: Int): Int = a + b

val addTwo = add(2) _  // 创建一个新函数,固定a为2
val result = addTwo(3) // 输出结果为5

抽象控制

  • 抽象控制:抽象控制是指接受函数作为参数,并在需要的时候调用该函数。它提供了一种灵活的控制流程。
def executeFunction(callback: => Unit): Unit = {
  println("执行前")
  callback
  println("执行后")
}

executeFunction {
  println("回调函数被执行")
}

上述示例中的executeFunction函数接受一个没有参数和返回值的函数,并在适当的时机调用该函数。

抽象控制例子二

object Test_ControlAbstraction {
  def main(args: Array[String]): Unit = {
    // 1. 传值参数
    def f0(a: Int): Unit = {
      println("a: " + a)
      println("a: " + a)
    }
    f0(23)

    def f1(): Int = {
      println("f1调用")
      12
    }
    f0(f1())

    println("========================")

    // 2. 传名参数,传递的不再是具体的值,而是代码块
    def f2(a: =>Int): Unit = {
      println("a: " + a)
      println("a: " + a)
    }

    f2(23)
    f2(f1())

    f2({
      println("这是一个代码块")
      29
    })

  }
}

函数的懒加载机制

  • 惰性加载:惰性加载是指推迟表达式的求值,直到它被实际使用时才计算。这有助于提高程序效率和资源利用率。
lazy val expensiveCalculation: Int = {
  // 执行昂贵的计算
  42
}

println(expensiveCalculation) // 在此处才进行计算和输出结果

在上述示例中,expensiveCalculation变量在声明时并不会立即计算,而是在第一次访问时才进行计算。

柯里化和闭包示例

/*
 * 在scala中,函数是一个对象,方法调用的时候,会在内存中开辟两个空间,
 * 一个是栈内存,方法调用会压栈,同时会将方法对应的函数实例保存到堆内存一份,
 * 对内存的数据是共享的。栈内存是独享的,随着方法调用完毕,栈内存会释放,
 * 但是堆内存的变量仍然还在,这样就实现了函数的闭包。
 */
object Test_ClosureAndCurrying {
  def main(args: Array[String]): Unit = {
    def add(a: Int, b: Int): Int = {
      a + b
    }

    // 1. 考虑固定一个加数的场景
    def addByFour(b: Int): Int = {
      4 + b
    }

    // 2. 扩展固定加数改变的情况
    def addByFive(b: Int): Int = {
      5 + b
    }

    // 3. 将固定加数作为另一个参数传入,但是是作为”第一层参数“传入
    def addByFour1(): Int=>Int = {
      val a = 4
      def addB(b: Int): Int = {
        a + b
      }
      addB
    }

    def addByA(a: Int): Int=>Int = {
      def addB(b: Int): Int = {
        a + b
      }
      addB
    }

    println(addByA(35)(24))

    val addByFour2 = addByA(4)
    val addByFive2 = addByA(5)

    println(addByFour2(13))
    println(addByFive2(25))

    // 4. lambda表达式简写
    def addByA1(a: Int): Int=>Int = {
      (b: Int) => {
        a + b
      }
    }
    def addByA2(a: Int): Int=>Int = {
      b => a + b
    }

    def addByA3(a: Int): Int=>Int = a + _
    val addByFour3 = addByA3(4)
    val addByFive3 = addByA3(5)

    println(addByFour3(13))
    println(addByFive3(25))

    // 5. 柯里化
    def addCurrying(a: Int)(b: Int): Int = {
      a + b
    }

    println(addCurrying(35)(24))
  }
}

总结:

Scala是一门强大的函数式编程语言,提供丰富的函数概念。本文首先介绍了函数的基本语法和使用方法,并列举了一些示例。然后,讨论了匿名函数的特点以及如何使用它们。接下来,我们介绍了高阶函数及其常见用法。然后,我们介绍了偏函数的概念和使用方法。最后,我们涵盖了柯里化、抽象控制和惰性加载等进阶主题。

函数的闭包

函数闭包是指一个函数以及其在创建时所能访问的自由变量(即不是参数也不是局部变量)的组合。闭包允许函数捕获并访问其周围上下文中定义的变量,即使这些变量在函数执行时不再存在。

在Scala中,闭包是一种非常强大且常见的特性,它可以用于创建具有灵活状态的函数。以下是一个示例来说明函数闭包的概念:

def multiplier(factor: Int): Int => Int = {
  (x: Int) => x * factor
}

val multiplyByTwo = multiplier(2) // 使用multiplier函数创建一个闭包
val result1 = multiplyByTwo(5) // 调用闭包
println(result1) // 输出结果为10

val multiplyByThree = multiplier(3) // 使用multiplier函数创建另一个闭包
val result2 = multiplyByThree(5) // 调用另一个闭包
println(result2) // 输出结果为15

在上述示例中,multiplier函数接受一个整数参数factor,并返回一个闭包,闭包是一个匿名函数(x: Int) => x * factor。在闭包中,factor是一个自由变量,它被捕获并保存在闭包中,即使在闭包被调用时,factor的定义已经超出了其作用域。因此,每次调用闭包时,它都可以访问和使用正确的factor值,实现了状态的保留。

闭包在许多场景下非常有用,例如:

  • 创建高阶函数时,可以使用闭包将函数作为参数传递,并携带额外的上下文信息。
  • 在函数式编程中,闭包可以用于构建递归函数,其中函数自身作为参数传递给递归调用。
  • 在异步编程中,使用闭包可以捕获异步操作完成后的结果,并进行后续处理。

需要注意的是,在使用闭包时,需要谨慎处理自由变量的生命周期,确保不会发生对已经超出作用域的变量的意外引用。另外,闭包可能会引起内存泄漏,因此需要适度使用,避免额外的资源占用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/967133.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

zabbix监控平台部署

目录 前言 一、zabbix的基本概述 (一)、zabbix的工作流程 (二)、zabbix的构成 (三)、zabbix的监控对象 (四)、zabbix的常用术语 (五)、zabbix进程详解…

植物根系基因组与数据分析

1.背景 这段内容主要是关于植物对干旱胁迫的反应,并介绍了生活在植物体内外以及根际的真菌和细菌的作用。然而,目前对这些真菌和细菌的稳定性了解甚少。作者通过调查微生物群落组成和微生物相关性的方法,对农业系统中真菌和细菌对干旱的抗性…

windows 2012服务器配置nginx后无法访问域名的问题

环境:Windows 2012 R2 Nginx 问题:确认域名解析到服务器ip已生效(通过ping域名地址确认域名已指向该ip),确认nginx配置无误(绑定域名、配置端口、配置网站文件目录),但无法从外网访…

6年打工人,我的所见、所想、所感。

咪哥杂谈 本篇阅读时间约为 7 分钟。 1 前言 本篇文章全部基于个人心得总结,欢迎大家讨论,无论是赞同还是反对,所有观点均接受。 原本是 5 周年打工人的心得,愣是让我拖了一年变成了 6 周年心得。 最近一年多一直投身于区块链行业…

vue的第2篇 开发环境vscode的安装以及创建项目空间

一 环境的搭建 1.1常见前端开发ide 1.2 安装vs.code 1.下载地址:Visual Studio Code - Code Editing. Redefined 2.进行安装 1.2.1 vscode的中文插件安装 1.在搜索框输入“chinese” 2.安装完成重启,如下变成中文 1.2.2 修改工作区的颜色 选中[浅色]…

回复:c#的Winform如何让ComboBox不显示下拉框?https://bbs.csdn.net/topics/392565412

组合框.Parent this;组合框.Items.AddRange(new object[] { "111", "222", "333", "444" });组合框.DropDownHeight 1;组合框.SelectedIndex 0;//组合框.DropDownStyle ComboBoxStyle.Simple; ComboBox 组合框 new ComboBox();Li…

编写中间件以用于 Express 应用程序

概述 中间件函数能够访问请求对象 (req)、响应对象 (res) 以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为 next 的变量来表示。 中间件函数可以执行以下任务: 执行任何代码。对请求和响应对象进行更改。结束请求/响应循环。调用堆…

忘记了zip密码,怎么解压文件?

Zip压缩包设置了密码,解压的时候就需要输入正确对密码才能顺利解压出文件,正常当我们解压文件或者删除密码的时候,虽然方法多,但是都需要输入正确的密码才能完成。忘记密码就无法进行操作。 那么,忘记了zip压缩包的密…

SpringMVC_基本使用

一、JavaWEB 1.回顾 JavaWEB 1.1新建项目结构 新建 javaweb 项目目录结构 1.2导入依赖 依赖 <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>…

1776_树莓派简介视频学习小结

全部学习汇总&#xff1a; GitHub - GreyZhang/little_bits_of_raspberry_pi: my hacking trip about raspberry pi. 卖树莓派的时候赠送了部分视频资料&#xff0c;今天看了一段&#xff0c;主要是对树莓派进行一个简单的介绍的视频。挑我自己感兴趣的大致总结如下&#xff1a…

第五章 树与二叉树 二、二叉树的定义和常考考点,WPL的算法

一、定义 二叉树可以用以下方式详细定义&#xff1a; 二叉树是由节点构成的树形结构&#xff0c;每个节点最多可以有两个子节点。每个节点有以下几个属性&#xff1a; 值&#xff1a;存储该节点的数据。左子节点&#xff1a;有一个左子节点&#xff0c;如果没有则为空。右子节…

【包过滤防火墙——iptables静态防火墙】的简单使用

文章目录 规则链的分类--五链处理的动作iptables常用参数和作用 防火墙就是堵和通的作用 iptables &#xff1a;包过滤防火墙&#xff0c;是内核防火墙netfilter的管理工具 核心&#xff1a;四表五链 规则链的分类–五链 在进行路由选择前处理的数据包&#xff1a;PREROUTIN…

python实现zscore归一化和minmax标准化

zscore归一化&#xff1a; minmax from sklearn import preprocessing from sklearn.preprocessing import StandardScaler import numpy as np# 数据 x np.array([[1.,-1.,2.],[2.,0.,0.],[0.,1.,-1.]]) print(----------------minmaxscaler标准化-------------) # 调用minma…

RK3568-INPUT输入子系统

本文档以检测按键电平状态为例 硬件连接(KEY0与GPIO3B6引脚相连) 驱动分为三类: 1 自己编写input输入子系统按键驱动 2 使用内核按键中断驱动 /kernel/drivers/input/keyboard/gpio_keys.c 3 使用内核按键轮休驱动 /kernel/drivers/input/keyboard/gpio_keys_polled.c内核…

docker 笔记2 Docker镜像和数据卷

参考&#xff1a; 1.镜像是什么&#xff1f;&#xff08;面试题&#xff09; 是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文…

【LeetCode75】第四十五题 重新规划路线

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们一个表示城市连通情况的有向图&#xff0c;要求每个城市都要可以通向0号城市&#xff0c;不同城市之间只有一条路线&#xff0c;我…

Gteam2.0免授权毛玻璃拟态UI带后台版本修复版

程序使用PHP7版本运行 后台信息/Admin 账号admin 密码123456 后台功能 多管理员、系统日志等等功能

知识图谱实战应用26-基于知识图谱构建《本草纲目》的中药查询与推荐项目应用

大家好,我是微学AI,今天给大家介绍一下知识图谱实战应用26-基于知识图谱构建《本草纲目》的中药查询与推荐项目应用,本文通过Py2neo连接到知识图谱数据库,系统实现了中药的快速查询、关系分析、智能推荐和知识展示等功能。用户可以输入中药的名称或特征进行查询,系统将从知…

1778_树莓派系统安装

全部学习汇总&#xff1a; GitHub - GreyZhang/little_bits_of_raspberry_pi: my hacking trip about raspberry pi. 一段视频学习教程的总结&#xff0c;对我来说基本上用处不大。因为我自己的树莓派简简单单安装完就开机成功了&#xff0c;而且实现了很多视频中介绍的功能。 …

人工智能研究的未来:20 年机器学习和深度学习的论文创意!

“机器学习的美妙之处在于&#xff0c;它可以应用于你想要解决的任何问题&#xff0c;只要你能为计算机提供足够的例子。” 一、说明 该文章列出了 20 年机器学习和深度学习本科课程的 2023 个潜在论文想法。每个论文的想法都包括一个介绍&#xff0c;简要概述了主题和研究目标…