Scala的虚无与飘渺

URL: http://my.oschina.net/sulliy/blog/80596


本文名字与内容无关。

很多人抱怨Scala相比于Java过于复杂了:大部分使用过Scala的程序员都没有能深刻理解它的类型系统和Scala的函数式编程。Scala的类型系统跟Java和C++很不一样,Scala想把面向对象纯粹化(学院派的作风?),不能有破坏面向对象的一切因素出现。null、NULL、int、...这些都是不和谐的东西,应该割掉。Scala又给了太多了空间给程序员,你可以使用传统的命令式编程风格,也可以使用函数式风格,一个语言写出了不同的代码。这个跟汉语有很多方言,极为相似。太多的自由会浪费Scala的一片好心。一个思路是在封装完Java的功能后,尽量使用Scala推荐的函数式风格来写面向对象的程序。

进入Scala,你就进入了虚无缥缈的太虚境地。在何为有?何为无?的问题上?Scala的设计走得很远。Scala的有即Any,Scala的无是Null,null,Nil,Nothing,None,Unit。Scala的无太让人手足无措,今天就讨论Scala的无。

要想在正确的地方使用正确的无,就要先理解它们分别表示的含义。

Null&null

很多人一辈子都没有走出这个无。Null是一个Trait,你不能创建她它的实例。但是Scala在语言层面上存在一个Null的实例,那就是null。Java中的null意味着引用并没有指向任何对象。但存在一个悖论,一切都是对象,那没有对象是不是也是对象呢?Scala定义了一个类似于对象语义的Null,和一个值语义的null。这样面向对象在空引用的情况下完备了。如果你写了一个带有Null作为参数的对象,那么你传入的参数只能是null,或者指向Null的引用。

?
 
scala> tryit( "hey" )
<console> : 6 : error : type mismatch;
  found   : java.lang.String( "hey" )
  required : Null
        tryit( "hey" )
              ^
 
scala> val someRef : String = null
someRef : String = null
 
scala> tryit(someRef)
<console> : 7 : error : type mismatch;
  found   : String
  required : Null
        tryit(someRef)
              ^
 
scala> tryit( null )
That worked!
 
scala> val nullRef : Null = null
nullRef : Null = null
 
scala> tryit(nullRef)
That worked!

第四行我们试图传入一个String,自然不能工作。第14行我们传入一个null引用,但是任然不能工作,为什么呢?因为null引用指向的是String类型。它可能在运行时是null,但是在编译时类型检查却不认同,编译器认为他是个String。

Nil

Nil是一个继承List[Nothing]的对象,我们随后讨论Nothing。它就是一个空的列表,下面是一些使用实例:

?
scala> Nil
res 4 : Nil. type = List()
 
scala> Nil.length
res 5 : Int = 0
 
scala> Nil + "ABC"
res 6 : List[java.lang.String] = List(ABC)
 
scala> Nil + Nil
res 7 : List[ object Nil] = List(List())

可以看出,Nil就是一个可以封装任何东西的空容器。它的长度为0。它并不是一无所有,它是一个容器,一个列表,只是没有存放内容而已。

Nothing

Nothing可能比较难理解。Nothing也是一个Trait,它继承自Any,而Any是整个Scala类型系统的根。Nothing是没有实例的,但它时任何对象的子类,他是List的子类,是String的子类,是Int的子类,是任何用户自定义类型的子类。

前面提到Nil是一个空的List[Nothing]。由于Nothing是任何类型的子类,那么Nil就可以当做是一个空的String List,空的Int List,甚至使Any List。Nothing比较适合用来定义基类容器。

?
val emptyStringList : List[String] = List[Nothing]()
emptyStringList : List[String] = List()
 
val emptyIntList : List[Int] = List[Nothing]()
emptyIntList : List[Int] = List()
 
val emptyStringList : List[String] = List[Nothing]( "abc" )
<console> : 4 : error : type mismatch;
  found   : java.lang.String( "abc" )
  required : Nothing
   val emptyStringList : List[String] = List[Nothing]( "abc" )

第一行,我们将一个List[Nothing]赋值给一个List[String]。一个Nothing是一个String,因此是正确的。第四行,我们将一个List[Nothing]赋值给一个List[Int]。一个Nothing是一个Int,因此是正确的。Nothing是任何类型的子类,但是上面的List[Nothing]都不包含任何成员。当我们创建一个包含一个String的List[Nothing]的List,并把它赋值给List[String],会出现什么情况?它会失败,因为Nothing并不是任何类型的父类,并且也不存在Nothing的实例。任何Nothing的容器必然是空的,是Nil。

另一种Nothing的用法是作为不返回函数的返回值。因为Nothing没有任何实例,而函数的返回值必定是一个值,是一个对象,这样定义为Nothing为返回值的函数实际上不可能返回。

None

写Java程序的时候,经常会碰到没有有意义的东西可以返回,我们返回null。但返回null有一些问题,调用方必须检查返回值,不然会有NullPointerException的异常。这逼迫我们去check函数的返回值。还有一种解决办法是使用异常,但增加try/catch块,并不是明智的选择。

Scala内置一种解决办法。如果你想返回一个String,但可能有的时候得不到有意义的返回值,我们可以让函数返回Option[String]。

?
def getAStringMaybe(num : Int) : Option[String] = {
        if ( num > = 0 ) Some( "A positive number!" )
         else None // A number less than 0?  Impossible!
      }
 
getAStringMaybe : (Int)Option[String]
 
def printResult(num : Int) = {
         getAStringMaybe(num) match {
           case Some(str) = > println(str)
          case None = > println( "No string!" )
        }
      }
printResult : (Int)Unit
 
printResult( 100 )
A positive number!
 
printResult(- 50 )
No string!

函数getAStringMaybe返回Option[String]。Option是一个抽象类,它有两个子类:Some和None。因此,初始化一个Option有两种方法。getAStringMaybe返回的只可能是Some[String]或None。Some和None又是Case Class,可以直接用来做模式匹配,很容易用来处理返回值。

类似地,Option[T]作为返回值,意味着调用者可能会收到Some[T]或None。

其实Option作为返回值,并没有比返回null好多少。代码里面充满了Option和模式匹配的代码,也不优雅,所以对待Option还是慎重。

Unit

Unit跟java的void一样,表示函数没有返回值。

?
 
def doThreeTimes(fn : (Int) = > Unit) = {
        fn( 1 ); fn( 2 ); fn( 3 );
     }
doThreeTimes : ((Int) = > Unit)Unit
 
doThreeTimes(println)
1
2
3
 
def specialPrint(num : Int) = {
         println( ">>>" + num + "<<<" )
     }
specialPrint : (Int)Unit
 
doThreeTimes(specialPrint)
>>> 1 <<<
>>> 2 <<<
>>> 3 <<<

这个不解释。

总的来看,Scala定义如此多的无,主要目的是为了保证它的类型系统的完整性,同时细化概念。到底好不好呢?下一个十年再下定论。

上述就是所有的Scala表征无的方式。如果你是Scala的簇拥,谈谈你的想法。

http://oldfashionedsoftware.com/2008/08/20/a-post-about-nothing/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值