序言
这一次讲的不是作为Java改良版的Scala语言中所具有强大的纯面向对象功能,而是以函数式语言来介绍他。函数本身是对象,他可以被赋值给变量,或者作为方法的参数来传递,我们把他作为“第一类对象”来看一下他的处理方法。另外也让读者体验一下函数式语言特有的模式匹配的强大功能。好,让我们马上出发,开始我们第三次迷你旅行吧。
Scala的函数定义
在Scala中方法被作为函数用def语句以“def 函数名(参数类表): 返回值 = 函数体”格式来定义。
- def foo(s: String, n: Int): Int = {
- s.length * n
- }
但是函数体仅由单个句子来构成的话可以省略{}。
- def foo(s: String, n: Int): Int = s.length * n
还有,类型推断对于返回值也是有效的,在允许的情况下是可以省略他的类型的(函数定义中,参数的类型则不可省略)。但是为了理解方便,除了交互式环境下以脚本语言方式使用外,还是作为标记保留下来比较好吧。
- scala> def foo(s: String, n: Int) = s.length * n
- foo: (String,Int)Int
- scala> foo("Zhang Fei", 3)
- res0: Int = 27
为了声明无返回值的函数可以将返回值定义为Unit。这个与Java中的void相同。
- def bar(s: String, n: Int): Unit = for(i <- 1 to n) print(s)
上述函数的目的是为了执行被认为是副作用的打印n次传入字符串,所以返回值是Unit。附带说一下,Unit唯一的实例是用()文本来表示。
引入单例对象内的方法
这些方法一般都定义在类之中,但是如果想单独使用它的话,通常将其定义在单例对象中。
- object MyFunctions {
- def foo(s: String, n: Int): Int = s.length * n
- def bar(s: String, n: Int): Unit = for(i <- 1 to n) print(s)
- }
为了使用foo或bar这些方法,通常指定单例对象名和方法名来调用他。
- scala> MyFunctions.foo("Zhang Fei", 3)
- res1: Int = 27
- scala> MyFunctions.bar("Zhang Fei", 3)
- Zhang FeiZhang FeiZhang Fei
如下所示将方法引入之后就不用一次一次的指定单例对象名了。下面引入了所有MyFunctions里的方法。
- scala> import MyFunctions._
- import MyFunctions._
- scala> foo("Zhang Fei", 3)
- res0: Int = 27
- scala> bar("Zhang Fei", 3)
- Zhang FeiZhang FeiZhang Fei
匿名函数的定义
到此为止,每一次的函数定义中都指定了函数名,但是如果能不指定函数名就更方便了。因为即使没有函数名,只要将函数体作为参数来传递或赋值给变量之后,该函数实例也就能确定了。这类函数称为匿名函数(anonymous
function),以“参数表 => 函数体”格式来定义。例如可以用如下形式来定义取得字符串长度的函数。
- scala> (s:String) => s.length
如果仅这样定义的话,该语句结束后该函数就消失了,为了能够持续使用该函数就需要,或者持续定义该函数并适用他,或者将他赋值给变量,或者将他作为参数传给别的函数。
- scala> ((s:String) => s.length)( "Zhang Fei") //对字符串直接适用函数文本
- res2: Int = 9
-
- scala> val ssize = (s:String) => s.length //将函数赋值给变量
- ssize: (String) => Int = <function>
- scala> ssize("Zhang Fei") //用变量来调用函数
- res3: Int = 9
- scala> List("Zhang ", "Fei").map((s:String) => s.length) //对于列表每一项目都适用同一函数文本
- res4: List[Int] = List(6, 3)
- scala> List("Zhang ", "Fei").map(ssize) //对于列表每一项目都适用同一函数变量
- res5: List[Int] = List(6, 3)
上述最后两个例子中使用了map函数,他对列表中的每一项目都适用作为参数传入的函数之后将适用结果作为列表返回。函数则是由函数文本(s:String)
=> s.length或函数变量ssize来指定的。这也是闭包的一个例子,在Scala中用函数来定义闭包。任意的函数都可以作为参数来传给别的函数。
例如前面的bar函数如下所示
- def bar(s: String, n: Int): Unit = for(i <- 1 to n) print(s)
这也可以用匿名函数来定义,这次是有两个参数且返回值是Unit的函数。
- scala> val f0 = (s:String, n:Int) => for(i <- 1 to n) print(s)
- f0: (String, Int) => Unit = <function>
这个函数中用for语句进行了n次循环,其实还可以改写成如下形式。
- def bar(s: String, n: Int): Unit = 1 to n foreach {i => print(s)}
函数体中出现的{i => print(s)}就是以匿名函数形式定义的闭包。1 to
n是1.to(n)的简化形式,然后将闭包作为参数传递给刚创建的Range对象的foreach方法(参数i在闭包的函数体中并没有被使用,仅是为了语法需要)。
在表达式中作为占位符的下划线
实际上,Scala中备有比匿名函数更简洁的描述方式。
如下所示,对于“(s:String) => s.length”来说,可以用“_”以“(
_:String).length”形式来描述。还有可以用“(_:Int)+(_:Int)”来定义类型为“(Int, Int) =>
Int”的加法表达式。
- scala> ((_:String).length)("abcde")
- res6: Int = 5
- scala> ((_:Int)+(_:Int))(3, 4)
- res7: Int = 7
- scala> ((_:String).length + (_:Int)) ("abc", 4)
- res8: Int = 7
部分函数的定义
Scala中不仅可以用到现在所看到的式子来定义,还可以通过将具体的实例一排排列出后,用类似于数学中学到的映像图的形式来描述。声明了“f1:A=>B”之后可以认为是定义了将类型A映像为类型B的函数f1。实际上这可以认为是将函数定义为类Function1[A,
B]的实例(图 6-1)。
- def f1: Symbol=>Int = {
- case 'a => 1
- case 'b => 2
- case 'c => 3
- }
- scala> f1('c)
- res9: Int = 3
- scala> f1('d)
- scala.MatchError: 'd
- at $anonfun$f1$1.apply(<console>:8)
- at $anonfun$f1$1.apply(<console>:8)
- at .<init>(<console>:10)
- at .<clinit>(<console>)
- at RequestResult$.<init>(<console>:3)
- at RequestResult$.<clinit>(<console>)
- at RequestResult$result(<console>)
- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
- at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)...
图 6-1定义为源值域与目标值域映像的函数
函数本来不就因该是这样的吗?但是问题是,如果将函数定义域中没有的参数传给f1函数后将会抛出例外。为了避免这种情况在对于某一值适用函数前可以先检查一下该值是否在定义域中。部分函数(PartialFunction)定义为我们提供了这种结构。
- def f2: PartialFunction[Symbol, Int] =
- {case 'a => 1; case 'b => 2; case 'c => 3}
- scala> for(s <- List('a, 'b, 'c, 'd)){ if( f2.isDefinedAt(s) ) println( f2(s) ) }
- 1
- 2
- 3
用部分函数定义了f2:A=>B函数之后,就可以在适用函数前先使用isDefinedAt(x:A)方法来确定定义域
了。所谓的部分函数就是,对于反应源值域到目标值域的映射的函数f:A=>B,不一定存在对应于x<-A的f(x)。反过来如果对于任意的x<-A都存在f(x)的话,那f就称为全函数。
Scala中方法和函数的关系
Scala即是纯面向对象语言又是函数式语言,给人一种朦胧的感觉。所谓的纯面向对象就是所有的语言元素都是作为对象来处理的。各个对象所持有的属性不管是数还是字符串还是数组还是Person等实例都是对象。
因此,当然函数也是对象。实际上函数f:
(ArgType1,...ArgTypeN)=>ReturnTyp是以类FunctionN[ArgType1,..., ArgTypeN,
ReturnType]的实例形式被定义的。N是表示参数个数的正整数。如果是1个参数的话则是Function1[ArgType1,
ReturnType]。
- def double(n:Int):Int = n * 2
上述函数基本上与下述定义是等同的。
- object double extends Function1[Int, Int] {
- def apply(n: Int): Int = n * 2
- }
- scala> double(10)
- res1: Int = 20
那么各个对象的方法也可以称得上对象吗?作为测试,试着将MyFunctions对象的方法绑定于变量。
- scala> val f1 = MyFunctions.foo
- <console>:8: error: missing arguments for method foo in object MyFunctions;
- follow this method with `_' if you want to treat it as a partially applied funct
- ion
- val f1 = MyFunctions.foo
看来光是方法原样是不能作为函数对象来处理的。实际上只要将方法简单地转换一下就可以作为对象来使用了。在方法名后空一格加上“_”就可以了。
- scala> val f1 = MyFunctions.foo _
- f1: (String, Int) => Int = <function>
- scala> f1("abcde", 3)
- res13: Int = 15
这样处理之后,我们就可以明白对象的方法也可以像属性一样作为对象来统一处理了。Scala语言在这一点上可以说比Smalltalk那种纯面向对象语言还贯彻了面向对象的思想。
高阶函数和延迟评估参数
因为Scala的函数是对象,所以不要做什么特殊处理只要将他作为参数传给别的函数就自然而然地成为使用高阶函数了。函数将别的函数作为参数来使用,所以称之为高阶函数。这时被传递的函数就称为闭包。
用于List统一操作的函数群就是高阶函数的典型例。下面的foreach函数,接受了以()或{}形式定义的闭包作为参数,然后将其逐一适用于接受者列表的所有元素。
- scala> val list = List("Scala", "is", "functional", "language")
- list: List[java.lang.String] = List(Scala, is, functional, language)
- scala> list.foreach { e => println(e) }
- Scala
- is
- functional
- language
对于同一列表list适用map函数后,对于列表list的所有元素适用s => s +
“!”函数后将适用结果以列表的形式返回。这里用空格代替了调用方法的“.”,然后用( _ + “!”)替代(s => s + “!”)也是可以的。
- scala> list map(s => s + "!")
- res15: List[java.lang.String] = List(Scala!, is!, functional!, language!)
- scala> list map( _ + "!")
- res16: List[java.lang.String] = List(Scala!, is!, functional!, language!)
进一步,Scala中除了有f1(p1:T1)这种通常的“基于值的参数传递(by value parameter)”,还有表示为f2(p2 =>
T2)的“基于名称的参数传递(by name
parameter)”,后者用于参数的延时评估。将这个结构和高阶函数相混合后,就可以简单地定义新的语言控制结构了。下面是新语言结构MyWhile的定义和使用例。
- def MyWhile (p: => Boolean) (s: => Unit) {
- if (p) { s ; MyWhile( p )( s ) }
- }
- scala> var i: Int = 0
- i: Int = 0
- scala> MyWhile(i < 3) {i=i+1; print("World ") }
- World World World
- scala> MyWhile(true) {print(“World is unlimited”) }
- 无限循环
像这样充分利用了函数式语言的特点之后,我们会惊奇地发现像定义DSL(特定领域语言)那样进行语言的扩展是多么的容易和自由。
模式匹配
Scala的case语句非常强大,可以处理任何类型的对象。mach{}内部列出了case 模式 => 语句。为了确保覆盖性可以在末尾加上
_。
- scala> val value: Any = "string"
- value: Any = string
- scala> value match {
- | case null => println("null!")
- | case i: Int => println("Int: " + i)
- | case s: String => println("String: " + s)
- | case _ => println("Others")
- | }
- String: string
这次匹配一下Person类的对象。
- scala> class Person(name:String)
- defined class Person
- scala> val value : Any = new Person("Zhang Fei")
- value: Any = Person@e90097
- scala> value match {
- | case null => println("null!")
- | case i: Int => println("Int: " + i)
- | case s: String => println("String: " + s)
- | case _ => println("Others")
- | }
- Others
Case类
在Scala中模式匹配的不仅是对象,对象的属性和类型等也可以作为模式来匹配。
例如,假设想匹配Person类,一般情况下最多就是指定“_ : Person”来匹配属于Person类的对象了。
- scala> val value : Any = new Person("Zhang Fei")
- value: Any = Person@1e3c2c6
- scala> value match {
- | case _ : Person => println("person: who")
- | case _ => println("others: what")
- | }
- person: who
不过如果使用了Case类之后,对象内的公有属性变得也可以匹配了。定义类时只要把“class”换成“case
class”之后,编译器就会自动定义和生成同名的单例对象。并且在该单例对象中自动定义了返回该类实例的apply方法,以及返回以构造函数的参数为参数的Some类型(范型)对象的unapply(或unapplySeq)方法。并且,还自动定义了equals、hashCode和toString方法。
定义apply方法的效果是,只要定义好某个Case类之后,就可以用“类名(参数列表)”的形式来创建对象了。定义unapply方法后的效果是,可以在case语句中以Case类的构造函数的参数(对象属性)来作为匹配目标了。
- scala> case class Person(name:String) //定义Case类Person
- defined class Person
- scala> val value : Any = Person("Zhang Fei") //不用new就可以创建对象
- value: Any = Person(Zhang Fei)
- scala> value match {
- | case Person(ns) => println("person:" + ns) //可以将Person的属性作为匹配目标
- | case _ => println("others: what")
- | }
- person:Zhang Fei //Person的属性name将会被抽取出来
下面是将将整数N(v)、Add(l, r)和Mult(l,
r)组合后来变现四则运算Term。由于是以case形式定义的类,请注意一下在创建Term对象时,不用new就可以直接调用N(5)、Add(…)、Mult(…)实现了。如此使用Scala的模式匹配功能后就可以很方便地实现对象的解析工作了。
- abstract class Term
- case class N (v :Int) extends Term
- case class Add(l :Term, r :Term) extends Term
- case class Mult(l :Term, r :Term) extends Term
- def eval(t :Term) :Int = t match {
- case N (v) => v
- case Add(l, r) => eval(l) + eval(r)
- case Mult(l, r) => eval(l) * eval(r)
- }
- scala> eval(Mult(N (5), Add(N (3), N (4))))
- res7:Int = 35 // 5 * (3 + 4)
附带说一下,上述的Term类可以认为是作为N、Add和Mult类的抽象数据类型来定义的。
将模式匹配与for语句组合
下面就看一下将模式匹配与for语句组合在一起的技巧。
- scala> val list = List((1, "a"), (2, "b"), (3, "c"), (1, "z"), (1, "a"))
- list: List[(Int, java.lang.String)] = List((1,a), (2,b), (3,c), (1,z), (1,a))
这时在<-前面写的是像(1, x)一样的模板。
- scala> for( (1, x) <- list ) yield (1, x)
- res6: List[(Int, java.lang.String)] = List((1,a), (1,z), (1,a))
而且非常令人惊奇的是<-前面没有变量也是可以的。在<-之前写上(1,
“a”)之后,for语句也可以正常地循环并且正确地返回了两个元素。
- scala> for( (1, "a") <- list ) yield (1, "a")
- res7: List[(Int, java.lang.String)] = List((1,a), (1,a))
还有在使用Option[T]类来避免判断null的情况下,传入List[Option[T]]类型的列表时,不用显示的判断是否是Some还是None就可以一下子返回正确的结果了。
- scala> val list = List(Some(1), None, Some(3), None, Some(5))
- list: List[Option[Int]] = List(Some(1), None, Some(3), None, Some(5))
- scala> for(Some(v) <- list) println(v)
- 1
- 3
- 5
接着用以下的例子看一下组合模式匹配和for语句之后所产生的威力。
- scala> val list = List(1, "two", Some(3), 4, "five", 6.0, 7)
- list: List[Any] = List(1, two, Some(3), 4, five, 6.0, 7)
对上述例表中的元素对象类型进行判别后再分类一下吧。模式匹配里不仅可以使用值来作为模式,从下例可知模式还具有对Some(x)形式中的x也起作用的灵活性。
- for(x <- list){ x match{
- case x:Int => println("integer " + x)
- case x:String => println("string " + x)
- case Some(x) => println("some " + x)
- case _ => println("else " + x)
- } }
- scala> for(x <- list){ x match{
- | case x:Int => println("integer " + x)
- | case x:String => println("string " + x)
- | case Some(x) => println("some " + x)
- | case _ => println("else " + x)
- | } }
- integer 1
- string two
- some 3
- integer 4
- string five
- else 6.0
- integer 7
结束语
看了本文之后大家觉得怎么样呀?应该享受了Scala所具备的,将面向对象式和函数式语言功能充分融合的能力,以及高阶函数和模式匹配功能了吧。
Scala语法的初步介绍就到本讲为止了,接下来的讲座将介绍一下Scala语言更深入的部分。包括隐式转换、范型和单子等有趣的话题。
相关推荐
Swift的函数式特性不仅仅是为了在技术上与Haskell、Erlang等函数式语言看齐,更是为了提高软件开发的效率、可靠性和维护性。同时,Swift社区也提供了许多便于实现函数式编程思想的工具和库,比如Swift的函数式扩展和...
【C#函数编程详解】 函数式编程是一种编程范式,它强调使用...尽管F#作为.NET框架下的纯函数式语言提供了更全面的函数式编程支持,但C#的函数式特性使其在不改变主要编程风格的前提下,也能享受到函数式编程的益处。
1. **Lisp** - 由John McCarthy于1958年提出,Lisp是最古老的函数式语言之一,以其括号表示法和强大的元编程能力著称。它的动态类型和递归特性使其成为人工智能研究的重要工具。 2. **Haskell** - 作为纯函数式语言...
一、函数式语言家族成员调研 1. Lisp Lisp是一种古老的函数式编程语言,其主要特点是使用S-表达式(Symbolic Expression)表示数据和程序。Lisp的数据结构核心是列表,而列表是通过递归定义的,这使得在Lisp中实现...
函数式编程是一种编程范式,它强调通过使用不可变数据和纯...参加这次研讨会,你将有机会亲身体验函数式编程的魅力,并掌握在实际工作中应用它的技能。无论是初学者还是经验丰富的开发者,都能从这个研讨会中受益匪浅。
FC++ 是一个专为 C++ 设计的函数式编程库,它允许开发者在 C++ 中体验和利用函数式编程的优势。C++ 本身是一种多范式语言,支持面向对象、过程化以及模板元编程等多种编程风格,但原生并不直接支持函数式编程。FC++ ...
TRE,全称可能代表一种特定的编程范式或技术,是一种结合了面向对象和函数式编程特性的语言。它的核心理念是为用户提供一种能力,能够在程序运行时全面洞察所有进程在内存中的动态变化。这种特性对于调试、性能优化...
**VC6.0 函数式计算器** VC6.0,全称Microsoft Visual C++ 6.0,是一款经典的集成开发环境(IDE),由微软公司发布,主要用于C++编程语言的开发。这款IDE以其强大的功能、易用性以及对MFC(Microsoft Foundation ...
在Java的学习过程中,函数式接口是一个非常重要的概念,尤其是在Java 8及更高版本中。函数式接口是只有一个抽象方法的接口,这使得它们非常适合用于 Lambda 表达式的实现。Lambda 表达式是Java 8引入的新特性,它...
Swift深受函数式编程语言的影响,支持高阶函数、闭包和纯函数。函数式编程强调将计算视为函数的组合,而不是改变状态。使用函数式编程,可以写出更简洁、易于测试和维护的代码。例如,你可以使用map函数来改变数组中...
"swift-Concurrent-函数式并发原语的集合" 主题着重于如何利用Swift语言的特性来实现高效的并行处理。这里我们将深入探讨Swift中的并发编程概念、函数式并发原语以及如何使用它们来优化代码性能。 首先,Swift 支持...
《faber:函数式编程语言及其编译器》 在计算机科学领域,函数式编程语言是一种以数学函数为核心,强调程序数据的不可变性以及计算过程的表达方式。faber是一种这样的编程语言,它旨在提供一种高效、简洁且富有表达...
在C#函数式编程中,标准高阶函数是函数式编程语言中的核心概念,它们提供了一种简洁、可读性强的方式来处理数据集合。本文将深入探讨三个关键的高阶函数:Map、Filter和Fold。 首先,让我们理解什么是高阶函数。高...
斯宾诺莎(Spinoza)是一种基于JavaScript的纯函数式编程语言,它的设计目标是为开发者提供一种更简洁、更易于理解和维护的编程体验。在JavaScript的基础上,斯宾诺莎引入了函数式编程的哲学,强调代码的可预测性和...
Objeck语言是一种融合了面向对象和函数式编程特点的现代编程语言,旨在提供高效、灵活的编程体验。它的设计灵感可能来源于多种编程范式,同时结合了编译器优化和虚拟机技术,以实现高性能的代码执行。下面将详细介绍...
**Liga:** Liga 是一种基于 LLVM 的新型函数式编程语言,旨在提供高效、灵活的编程体验,特别适合处理复杂计算任务。它结合了函数式编程的抽象能力与LLVM的高性能编译基础设施,使得开发者能够编写出简洁、可维护的...
Bisquit是一种旨在帮助开发者通过类型推断学习和实践静态类型函数式编程的语言。它深受Scala等现代编程语言的影响,特别是在类型系统和函数式编程特性上。在这篇概述中,我们将深入探讨Bisquit的核心概念、类型推断...
Java8中的Lambda表达式和函数式接口 Java8中引入了Lambda表达式和函数式接口,这两大新特性...这些新特性也使得Java更加接近于函数式编程语言 Scala,Java开发者可以使用这些新特性来编写更加简洁、灵活和高效的代码。
Java 函数式编程是一种编程范式,它强调使用函数作为一等公民,即函数可以...无论是使用Java 8的Stream API,还是通过Groovy、Scala或Clojure等JVM语言,函数式编程都能为Java开发带来更现代、高效和优雅的编程体验。
6. **编程技巧**: R语言支持函数式编程,用户可以编写自己的函数来实现特定任务。同时,理解如何利用环境、作用域规则以及错误处理机制也是提高编程效率的关键。 7. **数据导入导出**: R可以处理各种格式的数据文件...