月度归档:2013年09月

布依族

这张照片是在贵州省博物馆里看到的,在镇宁的时候,大街上能看到很多带着头巾的少数民族,后来了解到这个民族是“布依族”,在大街上看到的穿着传统民族服饰的基本都是中老年妇女,不会像照片上女子这么年轻靓丽。并且在大街上看到的妇女们的头巾也与照片略有不同,两边没有散开,是一层一层缠起来的。

镇宁是安顺市下面的一个县,并且是“布依族苗族”自治县,后来了解到这个县的布依族人口很多。我原本对这个民族完全没有认识和了解,在博物馆看到她居然有270万人口,在贵州的少数民族里仅次于苗族。

黄果树马拉松2013

从上海坐飞机到达贵阳时3点左右,然后坐大巴到火车站,再乘坐火车到安顺。在安顺坐汽车到镇宁,去报到处领取了号码簿和芯片,这次是个小型赛事,居然把参赛者都印到了一本名册上。并在现场有两个医护人员测量了一下血压和心脏,我的高压有139,有点高,可能是最近休息的不好所致,平时并没有这么高,担心会不让参赛,解释说这两天睡眠较少' ;&,"vIE' ;&,"跑步。她有仔细检查了一下心率,还好通过了。

周四上午比赛,真正参加半程比赛的人数不过几百人,女子半程就更少的可怜了。组织者为了充数,让当地的中小学生们也来凑热闹,显得人多一些,当然学生们只是跑个3-5公里。这条赛程的上坡下坡较多,前面的14公里,一直保持在6分钟左右每公里,到后边的时候依然有一些上坡,有两个上坡是走着的。最终的成绩是2小时18分钟。差强人意。

在比赛快结束时的一个上坡,碰到一个女孩儿,捂着肚子在走,问她是第几次参加马拉松,她说是第二次,上一次也是这里。在贵阳警官学院读书,本来这次想冲着前10名,却半路阑尾发作。不过仍坚持着完成了比赛。

最后在退芯片排队的时候,也碰到几个其他地方的一些跑友,一个老人接近60岁的样子,说从50岁开始跑马拉松,迄今已经参加过18次马拉松,6次全程。他说第一次跑全程的时候,家人极力反对。

第二天去看了黄果树瀑布,原来可以凭马拉松成绩单买景区门票半价,之前不知道有这项优惠,在结束的时候退芯片排队时间很长,没有去打印成绩。黄果树瀑布值得一看,但并没有让我感到“震撼”,不确定它是不是世界第二大瀑布,期待以后能看到让人震撼的大瀑布,就像电影《普罗米修斯》的开头,那样震撼的场景。

scala类型系统:16) 函数类型

函数类型在写法上

(T1,T2…) => R

小括号里的是入参类型(最多可以有22个,最少为0),右箭头右边的是返回结果类型。

函数类型里可以使用通配符“_”,表示任意类型:

scala> val x: String => _ = null
x: Function1[String, _] = null

右箭头形式的背后是转换成FunctionN[T1,T2…, R] (N对应0~22),有点要注意的是,FunctionN在对其类型参数的定义,入参类型都是逆变的,而结果类型是协变的。它反映了函数类型也是具有多态特性的,参考这篇:scala中函数类型的多态,这里再补充一下温悦在ATA上对这篇blog的评论,他的这张图很好:

有2个函数: f1, f2; 我们若能说f2是f1的子类,当且仅当:f2的定义域类型x2 “大于” f1的定义域类型x1,且f2的值域y2 “小于” f1的值域y1; 其中“大于”指“是父类”;“小于”指“是子类”;

进一步讲,f1(父类函数)能接受的任意参数,f2也能接受,且经由f2映射而得出的结果的类型,也一定在经由f1映射所得结果的类型的“范围内”(是其子类)

再进一步讲:当代码里有对f1的调用“f1(x)”时,你可以尽管将这里的f1换成f2而不会出现类型错误,这也就是关乎“函数类型”的“里氏替换原则(LSP)”

scala类型系统:15) 协变与逆变

对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合 List[A]的子类型,那么就称为covariance(协变),如果 List[A]List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)

协变:

 _____               _____________ 
|     |             |             |
|  A  |             |  List[ A ]  |
|_____|             |_____________|
   ^                       ^ 
   |                       | 
 _____               _____________ 
|     |             |             |
|  B  |             |  List[ B ]  |
|_____|             |_____________|  

逆变:

 _____               _____________ 
|     |             |             |
|  A  |             |  List[ B ]  |
|_____|             |_____________|
   ^                       ^ 
   |                       | 
 _____               _____________ 
|     |             |             |
|  B  |             |  List[ A ]  |
|_____|             |_____________|  

如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariant(不可变的)

在Java里,泛型类型都是invariant,比如 List<String> 并不是 List<Object> 的子类型。Java并不支持声明点变型(declaration-site variance,即在定义一个类型时声明它为可变型,也称definition-site),而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如:

trait List[+T] // 在类型定义时(declaration-site)声明为协变 

这样会把List[String]作为List[Any]的子类型。

不过Java支持使用点变型(use-site variance),所谓“使用点“,也就是在声明变量时:

List<? extends Object> list = new ArrayList<String>();

scala为了兼容java泛型通配符的形式,引入存在类型(existential type,后边再讲)时,也支持了使用点变型(use-site variance)

scala> val a : List[_ <: Any] = List[String]("A")
a: List[_] = List(A)    

要注意variance并不会被继承,父类声明为variance,子类如果想要保持,仍需要声明:

scala> trait A[+T]

scala> class C[T] extends A[T]  // C是invariant的

scala> class X; class Y extends X;

scala> val t:C[X] = new C[Y]
<console>:11: error: type mismatch; 
 found   : C[Y]
 required: C[X]
Note: Y <: X, but class C is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)

必须也对C声明为协变的才行:

scala> class C[+T] extends A[T]

scala> val t:C[X] = new C[Y]
t: C[X] = C@6a079142

对于协变与逆变的使用,参考《Effective Java》的PECS原则。这里有篇旧文也可供参考。

scala类型系统:14) multiple bounds

在之前的:scala类型系统:11) upper bounds & lower bounds 结尾提到过在multiple bounds(多重界定)情况下java与scala的一些差异,这篇详细说一下scala里的multiple bounds。

1 不能同时有多个upper bounds 或 lower bounds,变通的方式是使用复合类型
T <: A with B

T >: A with B
2 可以同时有upper bounds 和 lower bounds,如
T >: A <: B

这种情况 lower bounds 必须写在前边,upper bounds写在后边,位置不能反。同时A要符合B的子类型,A与B不能是两个无关的类型。

3 可以同时有多个view bounds
T <% A <% B

这种情况要求必须同时存在 T=>A的隐式转换,和T=>B的隐式转换。

scala> implicit def string2A(s:String) = new A
scala> implicit def string2B(s:String) = new B

scala> def foo[ T <% A <% B](x:T)  = println("OK")

scala> foo("test")
OK
4 可以同时有多个context bounds
T : A : B

这种情况要求必须同时存在A[T]类型的隐式值,和B[T]类型的隐式值。

scala> class A[T];
scala> class B[T];

scala> implicit val a = new A[Int]
scala> implicit val b = new B[Int]

scala> def foo[ T : A : B ](i:T) = println("OK")

scala> foo(2)
OK

scala类型系统:13) context bounds

view bounds一样context bounds(上下文界定)也是隐式参数的语法糖。context bounds的出现是scala2.8版本才增加的,起因是2.8版本修正了数组类型的设计(关于这个话题后续单独讨论),同时为语法上的方便,引入了”上下文界定”这个概念。

看一个简单的例子,对于比较两个数的大小,采用隐式参数的做法:

scala> def max[T](a:T, b:T) (implicit cp : Comparator[T]) = { 
            if (cp.compareTo(b) > 0) a else b 
        }

可以简化为:

scala> def max[T : Comparator] (a:T, b:T) = { … }

上面的类型参数声明 T : Comparator 就表示存在一个 Comparator[T]类型的隐式值。

等等,原max方法里是要用到 cp 这个隐式参数的,省略后怎么用这个参数呢?有两个途径:

第一种是,在内部定义函数并声明隐式参数

scala> def max[T : Comparator] (a:T, b:T) = { 

            def inner(implicit c:Comparator[T]) = c.compare(a,b);  

            if(inner>0) a else b 
        }

这种做法只是把外部方法的隐式参数隐藏了,放到内部嵌套函数上;还有一个推荐的做法,使用implicitly方法:

scala> def max2[T : Comparator] (a:T, b:T) = { 

            val cp = implicitly[Comparator[T]]

            if( cp.compare(a,b)>0) a else b 
        }

implicitly是在Predef.scala里定义的,它是一个特殊的方法,编译器会记录当前上下文里的隐式值,而这个方法则可以获得某种类型的隐式值。

如果上下文中存在一个Comparator[Int]类型的隐式值:

scala> implicit val c = new Comparator[Int]{ 
                override def compare(a:Int, b:Int) = a - b 
        }

那么对maxmax2都可以对Int型的参数正确执行:

scala> max(1,2)
res4: Int = 2

scala> max2(3,2)
res5: Int = 3

scala类型系统:12) view bounds

很早前在来往的扎堆里发过一段模拟C#的using代码:

def using[ C <: { def close(): Unit }, T ] (resource: C) (handle: C => T): T = {
    try {
        handle(resource)
    } finally {
      closeQuietly(resource)
    }
}

Eric(药师)看到后,说 <% 更好

<%的意思是“view bounds”(视界),它比<:适用的范围更广,除了所有的子类型,还允许隐式转换过去的类型

def method [A <% B](arglist): R = ...

等价于:

def method [A](arglist)(implicit viewAB: A => B): R = ...

或等价于:

implicit def conver(a:A): B = …

def method [A](arglist): R = ...

<% 除了方法使用之外,class声明类型参数时也可使用:

scala> class A[T <% Int]
defined class A

但无法对trait的类型参数使用 <%

scala> trait A[T <% Int]
<console>:1: error: traits cannot have type parameters with context bounds `: ...' nor view bounds `<% ...'

补充,注意上面 using 方法中声明的结构类型 {def close():Unit},它的实现是通过反射做到的,所以有一定性能问题,在需要考虑性能的场景下,这里明确的用 java.io.Closeable 接口更合适,灵活性与性能你总要做一个选择。

scala类型系统:11) upper bounds & lower bounds

Upper Bounds

在Java泛型里表示某个类型是Test类型的子类型,使用extends关键字:

<T extends Test>

//或用通配符的形式:
<? extends Test>

这种形式也叫upper bounds(中文为上限或上界),同样的意思在scala的写法为:

[T <: Test]

//或用通配符:
[_ <: Test]

upper bounds适用于把泛型对象当作数据的提供者(生产者)的场景下:

scala>  def pr(list : List[_ <: Any]) { 
            list.foreach(print) 
        }

Lower Bounds

在Java泛型里表示某个类型是Test类型的父类型,使用super关键字:

<T super Test>

//或用通配符的形式:
<? super Test>

这种形式也叫lower bounds(中文为下限或下界),同样的意思在scala的写法为:

[T >: Test]

//或用通配符:
[_ >: Test]

lower bound适用于把泛型对象当作数据的消费者的场景下:

scala>  def append[T >: String] (buf : ListBuffer[T])  = {  
                buf.append( "hi")
        }

基本上与java一致,不过在复杂一点的情况下,对多重界定,有一些差异:

Java里,T 同时是 A 和 B 的子类型,称为multiple bounds

<T extends A & B>

Scala里对上界和下界不能有多个,不过变通的做法是使用复合类型(compund type):

[T <: A with B]

而对于lower bounds,在java里则不支持multiple bounds的形式:

<T super A & B> //java不支持

Scala里对复合类型同样可以使用lower bound

[T >: A with B]

因为Scala里对于 A with B相当于 (A with B),仍看成一个类型,参考复合类型

scala类型系统:10) 交集类型与联合类型

《快学scala》这本书(即《Scala for the Impatient》中文版)在介绍复合类型时提到它们也被成为“交集类型”,跟老高确认了一下,这块的英文原文是:

In order to belong to the compound type, a value must belong to all of the individual types. Therefore, such a type is also called an intersection type.

我之前以为是union type还觉得他翻译的别扭,是我理解错了。他翻译的是合适的,intersection type 交集类型:

X with Y with Z

scala是通过with关键字来支持这种形式的。

union type“或”的意思

X or Y or Z

在scala里并没有在语言级别支持 union type,但可以通过一些技巧实现。在stackoverflow上看到有2种实现技巧。

第一种方法,通过隐式转换(上下文界定):

class StringOrInt[T]
object StringOrInt {
  implicit object IntWitness extends StringOrInt[Int]
  implicit object StringWitness extends StringOrInt[String]
}


object Bar {
  def foo[T: StringOrInt](x: T) = x match {
    case _: String => println("str")
    case _: Int => println("int")
  }
}

第二种方式:Curry-Howard isomorphism(柯里-霍华德同构),这个有点复杂,等type lambda之后再介绍。