在贵阳呆了一天
月度归档:2013年09月
布依族
这张照片是在贵州省博物馆里看到的,在镇宁的时候,大街上能看到很多带着头巾的少数民族,后来了解到这个民族是“布依族”,在大街上看到的穿着传统民族服饰的基本都是中老年妇女,不会像照片上女子这么年轻靓丽。并且在大街上看到的妇女们的头巾也与照片略有不同,两边没有散开,是一层一层缠起来的。
镇宁是安顺市下面的一个县,并且是“布依族苗族”自治县,后来了解到这个县的布依族人口很多。我原本对这个民族完全没有认识和了解,在博物馆看到她居然有270万人口,在贵州的少数民族里仅次于苗族。
黄果树马拉松2013
从上海坐飞机到达贵阳时3点左右,然后坐大巴到火车站,再乘坐火车到安顺。在安顺坐汽车到镇宁,去报到处领取了号码簿和芯片,这次是个小型赛事,居然把参赛者都印到了一本名册上。并在现场有两个医护人员测量了一下血压和心脏,我的高压有139,有点高,可能是最近休息的不好所致,平时并没有这么高,担心会不让参赛,解释说这两天睡眠较少受了影响,平时一直有跑步。她有仔细检查了一下心率,还好通过了。
周四上午比赛,真正参加半程比赛的人数不过几百人,女子半程就更少的可怜了。组织者为了充数,让当地的中小学生们也来凑热闹,显得人多一些,当然学生们只是跑个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
}
那么对max
和max2
都可以对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
之后再介绍。