`

函数式语言的体验

阅读更多

 

(转自http://www.qqread.com/other-devtool/f484352.html

序言

这一次讲的不是作为Java改良版的Scala语言中所具有强大的纯面向对象功能,而是以函数式语言来介绍他。函数本身是对象,他可以被赋值给变量,或者作为方法的参数来传递,我们把他作为“第一类对象”来看一下他的处理方法。另外也让读者体验一下函数式语言特有的模式匹配的强大功能。好,让我们马上出发,开始我们第三次迷你旅行吧。

Scala的函数定义

在Scala中方法被作为函数用def语句以“def 函数名(参数类表): 返回值 = 函数体”格式来定义。

  1. def foo(s: String, n: Int): Int = {  
  2. s.length * n  
  3. }  

但是函数体仅由单个句子来构成的话可以省略{}。

  1. def foo(s: String, n: Int): Int = s.length * n 

还有,类型推断对于返回值也是有效的,在允许的情况下是可以省略他的类型的(函数定义中,参数的类型则不可省略)。但是为了理解方便,除了交互式环境下以脚本语言方式使用外,还是作为标记保留下来比较好吧。

  1. scala> def foo(s: String, n: Int) = s.length * n  
  2. foo: (String,Int)Int  
  3. scala> foo("Zhang Fei", 3)  
  4. res0: Int = 27 

为了声明无返回值的函数可以将返回值定义为Unit。这个与Java中的void相同。

  1. def bar(s: String, n: Int): Unit = for(i <- 1 to n) print(s) 

上述函数的目的是为了执行被认为是副作用的打印n次传入字符串,所以返回值是Unit。附带说一下,Unit唯一的实例是用()文本来表示。

引入单例对象内的方法

这些方法一般都定义在类之中,但是如果想单独使用它的话,通常将其定义在单例对象中。

  1. object MyFunctions {  
  2. def foo(s: String, n: Int): Int = s.length * n  
  3. def bar(s: String, n: Int): Unit = for(i <- 1 to n) print(s)  
  4. }  

为了使用foo或bar这些方法,通常指定单例对象名和方法名来调用他。

  1. scala> MyFunctions.foo("Zhang Fei", 3)  
  2. res1: Int = 27 
  3. scala> MyFunctions.bar("Zhang Fei", 3)  
  4. Zhang FeiZhang FeiZhang Fei  

如下所示将方法引入之后就不用一次一次的指定单例对象名了。下面引入了所有MyFunctions里的方法。

  1. scala> import MyFunctions._  
  2. import MyFunctions._  
  3. scala> foo("Zhang Fei", 3)  
  4. res0: Int = 27 
  5. scala> bar("Zhang Fei", 3)  
  6. Zhang FeiZhang FeiZhang Fei  

匿名函数的定义

到此为止,每一次的函数定义中都指定了函数名,但是如果能不指定函数名就更方便了。因为即使没有函数名,只要将函数体作为参数来传递或赋值给变量之后,该函数实例也就能确定了。这类函数称为匿名函数(anonymous function),以“参数表 => 函数体”格式来定义。例如可以用如下形式来定义取得字符串长度的函数。

  1. scala> (s:String) => s.length 

如果仅这样定义的话,该语句结束后该函数就消失了,为了能够持续使用该函数就需要,或者持续定义该函数并适用他,或者将他赋值给变量,或者将他作为参数传给别的函数。

  1. scala> ((s:String) => s.length)( "Zhang Fei") //对字符串直接适用函数文本  
  2. res2: Int = 9 
  3.  
  4. scala> val ssize = (s:String) => s.length //将函数赋值给变量  
  5. ssize: (String) => Int = <function>  
  6. scala> ssize("Zhang Fei") //用变量来调用函数  
  7. res3: Int = 9 
  8. scala> List("Zhang ", "Fei").map((s:String) => s.length) //对于列表每一项目都适用同一函数文本  
  9. res4: List[Int] = List(6, 3)  
  10. scala> List("Zhang ", "Fei").map(ssize) //对于列表每一项目都适用同一函数变量  
  11. res5: List[Int] = List(6, 3)   

上述最后两个例子中使用了map函数,他对列表中的每一项目都适用作为参数传入的函数之后将适用结果作为列表返回。函数则是由函数文本(s:String) => s.length或函数变量ssize来指定的。这也是闭包的一个例子,在Scala中用函数来定义闭包。任意的函数都可以作为参数来传给别的函数。

例如前面的bar函数如下所示

  1. def bar(s: String, n: Int): Unit = for(i <- 1 to n) print(s) 

这也可以用匿名函数来定义,这次是有两个参数且返回值是Unit的函数。

  1. scala> val f0 = (s:String, n:Int) => for(i <- 1 to n) print(s)  
  2. f0: (String, Int) => Unit = <function> 

这个函数中用for语句进行了n次循环,其实还可以改写成如下形式。

  1. 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”的加法表达式。

  1. scala> ((_:String).length)("abcde")  
  2. res6: Int = 5 
  3. scala> ((_:Int)+(_:Int))(3, 4)  
  4. res7: Int = 7 
  5. scala> ((_:String).length + (_:Int)) ("abc", 4)  
  6. res8: Int = 7 

部分函数的定义

Scala中不仅可以用到现在所看到的式子来定义,还可以通过将具体的实例一排排列出后,用类似于数学中学到的映像图的形式来描述。声明了“f1:A=>B”之后可以认为是定义了将类型A映像为类型B的函数f1。实际上这可以认为是将函数定义为类Function1[A, B]的实例(图 6-1)。

  1. def f1: Symbol=>Int = {  
  2. case 'a => 1 
  3. case 'b => 2 
  4. case 'c => 3 
  5. }  
  6. scala> f1('c)  
  7. res9: Int = 3 
  8. scala> f1('d)  
  9. scala.MatchError: 'd  
  10. at $anonfun$f1$1.apply(<console>:8)  
  11. at $anonfun$f1$1.apply(<console>:8)  
  12. at .<init>(<console>:10)  
  13. at .<clinit>(<console>)  
  14. at RequestResult$.<init>(<console>:3)  
  15. at RequestResult$.<clinit>(<console>)  
  16. at RequestResult$result(<console>)  
  17. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
  18. at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)... 

 

图 6-1定义为源值域与目标值域映像的函数

函数本来不就因该是这样的吗?但是问题是,如果将函数定义域中没有的参数传给f1函数后将会抛出例外。为了避免这种情况在对于某一值适用函数前可以先检查一下该值是否在定义域中。部分函数(PartialFunction)定义为我们提供了这种结构。

  1. def f2: PartialFunction[Symbol, Int] =  
  2. {case 'a => 1; case 'b => 2; case 'c => 3}  
  3. scala> for(s <- List('a, 'b, 'c, 'd)){ if( f2.isDefinedAt(s) ) println( f2(s) ) }  

用部分函数定义了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]。

  1. def double(n:Int):Int = n * 2 

上述函数基本上与下述定义是等同的。

  1. object double extends Function1[Int, Int] {  
  2. def apply(n: Int): Int = n * 2 
  3. }  
  4. scala> double(10)  
  5. res1: Int = 20 

那么各个对象的方法也可以称得上对象吗?作为测试,试着将MyFunctions对象的方法绑定于变量。

  1. scala> val f1 = MyFunctions.foo  
  2. <console>:8: error: missing arguments for method foo in object MyFunctions;  
  3. follow this method with `_' if you want to treat it as a partially applied funct  
  4. ion  
  5. val f1 = MyFunctions.foo  

看来光是方法原样是不能作为函数对象来处理的。实际上只要将方法简单地转换一下就可以作为对象来使用了。在方法名后空一格加上“_”就可以了。

  1. scala> val f1 = MyFunctions.foo _  
  2. f1: (String, Int) => Int = <function>  
  3. scala> f1("abcde", 3)  
  4. res13: Int = 15 

这样处理之后,我们就可以明白对象的方法也可以像属性一样作为对象来统一处理了。Scala语言在这一点上可以说比Smalltalk那种纯面向对象语言还贯彻了面向对象的思想。

高阶函数和延迟评估参数

因为Scala的函数是对象,所以不要做什么特殊处理只要将他作为参数传给别的函数就自然而然地成为使用高阶函数了。函数将别的函数作为参数来使用,所以称之为高阶函数。这时被传递的函数就称为闭包。

用于List统一操作的函数群就是高阶函数的典型例。下面的foreach函数,接受了以()或{}形式定义的闭包作为参数,然后将其逐一适用于接受者列表的所有元素。

  1. scala> val list = List("Scala", "is", "functional", "language")  
  2. list: List[java.lang.String] = List(Scala, is, functional, language)  
  3. scala> list.foreach { e => println(e) }  
  4. Scala  
  5. is  
  6. functional  
  7. language  

对于同一列表list适用map函数后,对于列表list的所有元素适用s => s + “!”函数后将适用结果以列表的形式返回。这里用空格代替了调用方法的“.”,然后用( _ + “!”)替代(s => s + “!”)也是可以的。

  1. scala> list map(s => s + "!")  
  2. res15: List[java.lang.String] = List(Scala!, is!, functional!, language!)  
  3. scala> list map( _ + "!")  
  4. res16: List[java.lang.String] = List(Scala!, is!, functional!, language!)  

进一步,Scala中除了有f1(p1:T1)这种通常的“基于值的参数传递(by value parameter)”,还有表示为f2(p2 => T2)的“基于名称的参数传递(by name parameter)”,后者用于参数的延时评估。将这个结构和高阶函数相混合后,就可以简单地定义新的语言控制结构了。下面是新语言结构MyWhile的定义和使用例。

  1. def MyWhile (p: => Boolean) (s: => Unit) {  
  2. if (p) { s ; MyWhile( p )( s ) }  
  3. }  
  4. scala> var i: Int = 0 
  5. i: Int = 0 
  6. scala> MyWhile(i < 3) {i=i+1; print("World ") }  
  7. World World World  
  8. scala> MyWhile(true) {print(“World is unlimited”) }  
  9. 无限循环  

像这样充分利用了函数式语言的特点之后,我们会惊奇地发现像定义DSL(特定领域语言)那样进行语言的扩展是多么的容易和自由。

模式匹配

Scala的case语句非常强大,可以处理任何类型的对象。mach{}内部列出了case 模式 => 语句。为了确保覆盖性可以在末尾加上 _。

  1. scala> val value: Any = "string" 
  2. value: Any = string  
  3. scala> value match {  
  4. | case null => println("null!")  
  5. | case i: Int => println("Int: " + i)  
  6. | case s: String => println("String: " + s)  
  7. | case _ => println("Others")  
  8. | }  
  9. String: string  

这次匹配一下Person类的对象。

  1. scala> class Person(name:String)  
  2. defined class Person  
  3. scala> val value : Any = new Person("Zhang Fei")  
  4. value: Any = Person@e90097 
  5. scala> value match {  
  6. | case null => println("null!")  
  7. | case i: Int => println("Int: " + i)  
  8. | case s: String => println("String: " + s)  
  9. | case _ => println("Others")  
  10. | }  
  11. Others  

Case类

在Scala中模式匹配的不仅是对象,对象的属性和类型等也可以作为模式来匹配。

例如,假设想匹配Person类,一般情况下最多就是指定“_ : Person”来匹配属于Person类的对象了。

  1. scala> val value : Any = new Person("Zhang Fei")  
  2. value: Any = Person@1e3c2c6 
  3. scala> value match {  
  4. | case _ : Person => println("person: who")  
  5. | case _ => println("others: what")  
  6. | }  
  7. person: who  

不过如果使用了Case类之后,对象内的公有属性变得也可以匹配了。定义类时只要把“class”换成“case class”之后,编译器就会自动定义和生成同名的单例对象。并且在该单例对象中自动定义了返回该类实例的apply方法,以及返回以构造函数的参数为参数的Some类型(范型)对象的unapply(或unapplySeq)方法。并且,还自动定义了equals、hashCode和toString方法。

定义apply方法的效果是,只要定义好某个Case类之后,就可以用“类名(参数列表)”的形式来创建对象了。定义unapply方法后的效果是,可以在case语句中以Case类的构造函数的参数(对象属性)来作为匹配目标了。

  1. scala> case class Person(name:String) //定义Case类Person  
  2. defined class Person  
  3. scala> val value : Any = Person("Zhang Fei") //不用new就可以创建对象  
  4. value: Any = Person(Zhang Fei)  
  5. scala> value match {  
  6. | case Person(ns) => println("person:" + ns) //可以将Person的属性作为匹配目标  
  7. | case _ => println("others: what")  
  8. | }  
  9. person:Zhang Fei //Person的属性name将会被抽取出来  

下面是将将整数N(v)、Add(l, r)和Mult(l, r)组合后来变现四则运算Term。由于是以case形式定义的类,请注意一下在创建Term对象时,不用new就可以直接调用N(5)、Add(…)、Mult(…)实现了。如此使用Scala的模式匹配功能后就可以很方便地实现对象的解析工作了。

  1. abstract class Term  
  2. case class N (v :Int) extends Term  
  3. case class Add(l :Term, r :Term) extends Term  
  4. case class Mult(l :Term, r :Term) extends Term  
  5. def eval(t :Term) :Int = t match {  
  6. case N (v) => v  
  7. case Add(l, r) => eval(l) + eval(r)  
  8. case Mult(l, r) => eval(l) * eval(r)  
  9. }  
  10. scala> eval(Mult(N (5), Add(N (3), N (4))))  
  11. res7:Int = 35 // 5 * (3 + 4)  

附带说一下,上述的Term类可以认为是作为N、Add和Mult类的抽象数据类型来定义的。

将模式匹配与for语句组合

下面就看一下将模式匹配与for语句组合在一起的技巧。

  1. scala> val list = List((1, "a"), (2, "b"), (3, "c"), (1, "z"), (1, "a"))  
  2. list: List[(Int, java.lang.String)] = List((1,a), (2,b), (3,c), (1,z), (1,a)) 

这时在<-前面写的是像(1, x)一样的模板。

  1. scala> for( (1, x) <- list ) yield (1, x)  
  2. res6: List[(Int, java.lang.String)] = List((1,a), (1,z), (1,a)) 

而且非常令人惊奇的是<-前面没有变量也是可以的。在<-之前写上(1, “a”)之后,for语句也可以正常地循环并且正确地返回了两个元素。

  1. scala> for( (1, "a") <- list ) yield (1, "a")  
  2. res7: List[(Int, java.lang.String)] = List((1,a), (1,a)) 

还有在使用Option[T]类来避免判断null的情况下,传入List[Option[T]]类型的列表时,不用显示的判断是否是Some还是None就可以一下子返回正确的结果了。

  1. scala> val list = List(Some(1), None, Some(3), None, Some(5))  
  2. list: List[Option[Int]] = List(Some(1), None, Some(3), None, Some(5))  
  3. scala> for(Some(v) <- list) println(v)  

接着用以下的例子看一下组合模式匹配和for语句之后所产生的威力。

  1. scala> val list = List(1, "two", Some(3), 4, "five", 6.0, 7)  
  2. list: List[Any] = List(1, two, Some(3), 4, five, 6.0, 7) 

对上述例表中的元素对象类型进行判别后再分类一下吧。模式匹配里不仅可以使用值来作为模式,从下例可知模式还具有对Some(x)形式中的x也起作用的灵活性。

  1. for(x <- list){ x match{  
  2. case x:Int => println("integer " + x)  
  3. case x:String => println("string " + x)  
  4. case Some(x) => println("some " + x)  
  5. case _ => println("else " + x)  
  6. } }  
  7. scala> for(x <- list){ x match{  
  8. | case x:Int => println("integer " + x)  
  9. | case x:String => println("string " + x)  
  10. | case Some(x) => println("some " + x)  
  11. | case _ => println("else " + x)  
  12. | } }  
  13. integer 1 
  14. string two  
  15. some 3 
  16. integer 4 
  17. string five  
  18. else 6.0 
  19. integer 7 

结束语

看了本文之后大家觉得怎么样呀?应该享受了Scala所具备的,将面向对象式和函数式语言功能充分融合的能力,以及高阶函数和模式匹配功能了吧。

Scala语法的初步介绍就到本讲为止了,接下来的讲座将介绍一下Scala语言更深入的部分。包括隐式转换、范型和单子等有趣的话题。

 

分享到:
评论

相关推荐

    Y分钟学习X种语言

    体验函数式编程的富于表达特征,你需要一种以函数式为主的语言。 尝试Erlang语言 Erlang绝对是一种非常有趣的语言,它能帮你打造永不宕机的高容错并行系统。它提供了非 常棒的模式匹配能力。有很多的产品应用和工具...

    Swift实现播放器项目联系

    4. **函数式编程**:Swift也支持函数式编程范式,包括高阶函数、闭包、不可变性等特性。这使得编写函数式风格的代码变得更加简洁和优雅。 5. **安全性**:Swift引入了多种机制来增强代码的安全性,如类型检查、元组...

    ios基础知识开发介绍.zip

    iOS开发是一个涵盖面极广且深入的技术领域,它涉及到编程语言、框架、界面设计...而Swift则是近年来苹果公司推出的新型编程语言,它语法简洁、类型安全,并且支持面向对象和函数式编程,成为越来越多iOS开发者的首选。

    Kotlin在人工智能领域的应用探索

    随着人工智能技术的飞速发展,越来越多的编程语言开始涉足这一领域。Kotlin作为一种简洁、安全且易于学习的编程语言...Kotlin支持函数式编程,拥有丰富的标准库和扩展功能,使得开发者能够高效构建各种类型的应用程序。

    快学Scala(中文影印版)

    Scala是一门十分有趣又非常实用的语言,它以JVM为目标环境,将面向对象和函数式编程有机结合在一起,带来独特的编程体验。

    js.zip(JavaScript)

    虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式(如函数式编程)范式。 JS文件在网页中用于...

    python项目数学函数绘图软件cs.zip

    5. **技术选型**:项目可能使用了如matplotlib这样的Python绘图库来实现函数图像的绘制,这是一个功能强大且文档完备的库,适合用于交互式绘图。 6. **易用性**:通过Anaconda这类集成开发环境,即使是编程新手也...

    Ruby编程语言

     6.8函数式编程205  第7章类和模块213  7.1定义一个简单类214  7.2方法可见性:PUBLIC、PROTECTED、PRIVATE232  7.3子类化和继承234  7.4对象创建和初始化241  7.5模块247  7.6加载和请求模块252  7.7单键...

    python 查票抢票 源码

    Python本质上是一种函数式编程语言,它可以让开发者快速构建应用程序,而且可以利用它的强大功能构建出更复杂的应用程序。 Python拥有强大的灵活性,可以用于开发几乎所有类型的应用程序,从网络应用程序,数据库...

    Swift应用领域以及对开发者和移动应用开发

    它引入了许多新特性,如可选类型、自动引用计数(ARC)、泛型和函数式编程等。 2. 安全性:Swift在安全性方面下了很大功夫,通过类型检查、空值处理和内存管理等机制,避免了许多常见的编程错误。它还采用了安全的...

    clarity-script:关于可编译为ClarityJavaScript兼容语言的提案

    对于某些人来说,可能会有不错的学习曲线来概念化函数式编程并熟悉新的异常语法。 为了提供流畅的智能合约开发人员体验,我们提议设计和创建一个名为ClarityScriptJavaScript子集,这意味着它只是没有某些功能的...

    python项目文件销毁工具.zip

    它支持多种编程范式,包括面向对象、命令式、函数式和过程式编程。 3. **设计思路与实现**: - **SSD单文件销毁方法**:这种方法专注于在固态硬盘(SSD)上彻底删除单个文件,以确保内容无法被恢复。这通常涉及使用...

    TCPIP协议详解卷2:实现

    目前从事软件需求工程、网络协议验证形式化方法以及函数式语言等方面的研究。 译者序: 我们愿意向广大的读者推荐W. Richard Stevens关于TCP/IP的经典著作(共3卷)的中译本。本书是其中的第2卷:《TCP/IP详解 卷2:...

    enlace:基于Deno和TypeScript的服务器框架

    完善函数式开发体验 自动扫描 Endpoint 与 Controller 完善 Vuepress 文档 编写单元测试 Features: 面向接口: 轻易地扩展任何组件、降低应用程序的耦合度、让维护与调试变得简单... 为通信工作而不是HTTP: 可以...

    seraph脚本编辑器 v8.00 官方版.zip

    Basic语言是最简单和最容易被大家理解、接受的语言,Seraph就是使用Basic语法,使得Seraph代码的阅读和书写变得非常简单和自然。Seraph支持所有VB的内部函数,从数学运算到字串处理,让您从容应对程序写作。

    JavaScript内核系列

    Mocha使用了C的语法,但是设计思想上主要从函数式语言Scheme那里取得了灵 感。当Netscape 2发布的时候,Mocha被改名为LiveScript,当时可能是想让LiveScript为WEB页面注入更多的活力。后来,考虑到这个脚本语言的推 ...

    MSDN杂志 2008年新产品特刊

    &lt;br&gt;F# 入门: 使用 .NET Framework 中的函数式编程技术 本文将向您介绍有关新的 F# 语言的一些概念,该语言组合了函数式和面向对象 .NET 语言的元素。然后,我们将通过编写一些简单的程序来帮助您入门。 ...

    前端开发笔记.docx

    前端开发是指构建和开发Web应用程序的过程,主要涉及创建用户界面、实现交互功能以及优化用户体验。前端开发人员使用各种技术和工具来设计和构建Web页面,使其在不同的设备和浏览器上都能良好运行。 在前端开发的...

    java8stream源码-Java8InAction:Java8InAction

    和函数式编程一书中示例和测验的所有源代码。 您可以在此处购买抢先体验: 我们非常渴望听到您的反馈并根据您的评论改进本书! 所有示例的源代码都可以在目录中找到 第 1 章:Java 8:你为什么要关心? 第 2 章:...

    java8stream源码-lambdasinaction:java8在行动

    和函数式编程一书中示例和测验的所有源代码。 您可以在此处购买抢先体验: 我们非常渴望听到您的反馈并根据您的评论改进本书! 所有示例的源代码都可以在目录中找到 第 1 章:Java 8:你为什么要关心? 第 2 章:...

Global site tag (gtag.js) - Google Analytics