读书人

函数式语言的体会

发布时间: 2012-12-21 12:03:49 作者: rapoo

函数式语言的体验

?

图?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)?)?} ?
  4. 1?
  5. 2?
  6. 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]。

  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) ?
  4. 1?
  5. 3?
  6. 5?

接着用以下的例子看一下组合模式匹配和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语言更深入的部分。包括隐式转换、范型和单子等有趣的话题。

?

读书人网 >编程

热点推荐