月度归档:2013年08月

scala bug系列:2.10编译器把单例当作类型的bug

下面的内容也是那天在旺旺群里讨论的,当时对2.10下这种奇怪的行为很疑惑,以为是2.10因macro或什么其他原因允许编译时这么写呢,现在证实它是2.10编译器的一个bug,对那天在群里的错误言论澄清一下。

对于这段代码:

object A ;
A match { case a:A => println("ok") } 

为什么在2.10版本里能够编译通过?在运行时才报错。

scala> object A

scala> A match { case a:A => println(a) }
<console>:9: warning: fruitless type test: a value of type A.type cannot also be a A
          A match { case a:A => println(a) }
                           ^
<console>:9: warning: match may not be exhaustive.
It would fail on the following input: A
          A match { case a:A => println(a) }
          ^
scala.MatchError: A$@232b0a52 (of class A$)
    at .<init>(<console>:9)
    at .<clinit>(<console>)
    at .<init>(<console>:7)
    at .<clinit>(<console>)
    at $print(<console>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    ...

在2.92下编译会报错:

scala> object A 
scala> A  match { case a:A => println("OK") }
<console>:9: error: not found: type A
        A  match { case a:A => println("OK") }
                          ^

按理说 A 是单例,不是类型,要匹配应该写 case Acase a:A.type 才对。2.92编译报错应该是合理的。2.10下居然允许把单例对象当类型使用,很不合理啊?

scala> object A
defined module A

scala> val x: A = null  //在scala2.10.2下编译通过
x: A = null

在 2.11 下又验证了一下,MD原来确实是个bug,瞎猜测一堆其他的因素,实际验证是最靠谱的:

scala> object A
defined object A

scala> val x:A = null  // scala2.11.0-M4 版本下
<console>:7: error: not found: type A
    val x:A = null

新版本里已经修复了这个bug。

scala雾中风景(6): 内部类与模式匹配

这个问题是在旺旺群里讨论的,讨论的时候思路比较发散,过程中有些说法也是错误的,重新总结一下。

问题的由来是聚石在使用actor接受消息时发现模式匹配的奇怪问题,他把问题简化后,大致是这样的(忽略代码风格问题,仅分析为何是这样的匹配结果):

trait A {
    case class B(i: Int)
}

class C extends A {
    def !(a:Any) = a match {
        case B(0) => println("never be here")   // 注1
        case b: B => println("never be here2")  // 注2
        case B => println("never be here3")     // 注3
        case x => println(s"Oops, received $x")
    }
}

class D extends A {
    new C ! B(0)
}

new D

执行过程,不会匹配到case B(0)case b:B 以及 case B 这3行,只能到 case x 这一句。

如果不是因为把case类定义成了内部类的话,就没有这些诡异问题,这里的问题正是内部类、内部半生对象的限定问题。

第1个问题,在不同的类中用相同的参数创建的内部case类实例,是否equals为true?

编译器帮我们对case类实现了equals方法,关于case类参考这篇:话说模式匹配(6) case类的细节。编译器默认实现的equals在比较时,主要对比其构造参数是否相等:

scala> case class X(i:Int)

scala> new X(0) == new X(0) // 参数相同就相等
res0: Boolean = true

但对于内部case类,就不仅仅是参数了:

scala> trait A { case class X(i:Int) }

scala> class C extends A; class D extends A ;

scala> val x1 = new c.X(0)
x1: c.X = X(0)

scala> val x2 = new d.X(0)
x2: d.X = X(0)

scala> x1 == x2
res1: Boolean = false

scala> val x3 = new d.X(0)
x3: d.X = X(0)

scala> x2 == x3
res2: Boolean = true

之前我们已经提到过c.Xd.X是不同的类型(参考这里),两个内部case类实例要equals为true的话,必须类型相同,即得是同一个外部实例才行。

第2个问题,因为case类同时产生一个伴生对象,那么c实例中拥有的单例B与d实例中拥有的单例B是不是同一个单例对象?
scala> trait A { case class B(i:Int) }

scala>  class C extends A; class D extends A;

scala>  val b1 = (new C).B
b1: C#B.type = B

scala>  val b2 = (new D).B
b2: D#B.type = B

scala> b1 == b2
res0: Boolean = false

可看到不同的外部实例,其内部的case类伴生对象也是不同的。

回到最初的问题,正因为 B(0) 这个构造方法在不同子类里,含义是有所不同的,假如在同一个上下文里:

trait A { case class B(i: Int) } 

class C extends A {

    def foo(a:Any) = a match {
        case B(0) => println("never be here")
        case b: B => println("never be here2")
        case B => println("never be here3")
        case x => println("Oops, received ")
    }

    foo( B(0) ) // foo执行与foo定义在同一个上下文中,B实例类型是一致的
} 

new C

上面是能够匹配到 case B(0) 的。 如果把 case B(0)这行删掉,也是能匹配到case b:B的。

但当B是在另一个子类D中构造时,case B(0)case b:B 都匹配不到了,因为B的类型不同!回顾这篇文章,内部类B,是一个路径依赖类型,所以在D中创建的B实例,它的类型是 D.this.B 这与在C中的C.this.B 不是同一个类型,所以造成了匹配失败。 要类型匹配成功的话,需要改用类型投影:

case b : A#B => …  // ok 可以匹配

那第一句 case B(0) 呢?它一样也是类型不符合,让这一句匹配上需要修改在D里构造B的方式:

class D extends A {
    val c = new C
    val b = new c.B(0)   
    c  !  b
}

这样构造出来的 B实例b 与 C里面的 B 类型就一致了。

scala类型系统:4) 内部类,路径依赖类型&类型投影

先回顾一下java的内部类

class Outter{

    public class Inner {}

    public void foo(Inner c){
        System.out.println(c);
    }
}

public class Main {
    public static void main(String[] args)throws Exception{
        Outter o1 = new Outter();
        Outter o2 = new Outter();
        Outter.Inner i1 = o1.new Inner();
        Outter.Inner i2 = o2.new Inner();
        o1.foo(i2);
    }
}

上面在Outter类内部定义了Inner类,在后边main里创建了两个Inner实例,注意创建内部类的时候

Outter.Inner i1 = o1.new Inner();

在用new创建内部类时,前边必须限定外部对象(内部类实例必须要访问到外部对象引用):o1;如果是在 Outter类内部这个外部引用可以省略,它默认会用传递外部this引用。

class Outter {

    public class Inner{}

    public void test() {
        new Inner(); // 相当于this.new Inner(); 也可以写为Outter.this.new Inner();
    }
} 

同样的事情翻译为scala代码:

scala> class A { 
            class B; 
            def foo(b:B) = println(b) 
        }

scala> val a1 = new A
scala> val a2 = new A

scala> val b1 = new a1.B
scala> val b2 = new a2.B

在创建内部类的时候,语法上与java有所不同,不是 outter.new Inner() 而是 new outter.Inner(),看上去只是表象不同么?实际上,scala有很大差异,不同于java里 i1 和 i2 类型是相同的,否则 o1.foo(i2) 就不能执行了,scala里的 b1 和 b2 是不同的类型:

scala> a1.foo(b2)
<console>:12: error: type mismatch;
 found   : a2.B
 required: a1.B 

按照方法的提示: a1.foo方法接受的参数类型为:a1.B,而传入的b2 类型是 a2.B,两者不匹配。
验证一下:

scala> typeOf[a1.B] == typeOf[a2.B]
res2: Boolean = false

确实是不一样的类型,它跟外部的实例相关,那个foo方法参数类型B的写法是缩写,省略了路径:

def foo(b: B) // 相当于 this.B 或 A.this.B

这里要引入一个概念:路径依赖类型;比如上面的 A.this.B 就是一个路径依赖类型,B 前面的路径 A.this 随着不同的实例而不同,比如 a1 和 a2 就是两个不同的路径,所以a1.Ba2.B也是不同的类型。路径依赖类型的路径完整写法:

1) 内部类定义在object里面,路径:package.object.Inner

object Singleton {
    class Inner
}

val x = new p1.p2.p3.Singleton.Inner

2) 内部类定义在class/trait 里

//2.1) 直接在外部类中使用内部类型,路径:this 或 Outter.this 

class A {
    class B
    val b = new B // 相当于 A.this.B
}   

//2.2) 在子类中使用父类的内部类型,路径:super 或 Child.super

class A  { class B }
class C extends A { val x = new super.B } // 相当于 C.super.B

//2.3) 在其他类中使用,路径:outter(外部类实例)

class A  { class B }
class C { 
    val a = new A
    val x = new a.B  
}

那现在的问题来了,怎么让 a1.foo 方法可以接收 b2 参数 ?

class A { 
    class B; 
    def foo(b:B)  // 接收所有的B类型实例,而不只是foo的调用者实例(a1)路径下B类型的对象
         println(b) 
}

这又引出一个概念:类型投影(type projection)

在scala里,内部类型(排除定义在object内部的),想要表达所有的外部类A实例路径下的B类型,即对 a1.Ba2.B及所有的 an.B类型找一个共同的父类型,这就是类型投影,用 A#B的形式表示。

        A#B
        / \
       /   \
     a1.B  a2.B

这样,我们只要修改一下 foo 方法里的参数类型

def foo(b: A#B)

就可以调用 a1.foo(b2) 了。

我们回头来对比一下scala里的类型投影与java里的内部类型的概念,java里的内部类型在写法上是 Outter.Inner 它其实等同于scala里的投影类型 Outter#Inner,java里没有路径依赖类型的概念,比较简化。

scala类型系统:3) 单例类型与this.type

对于单例类型(singleton type),在《scala impatient》这本书(中文版:快学scala)里有提到过,当时读的时候,扫了一下,以为就是指scala里用object定义的这种单例对象呢,没有仔细看,最近才发现其实singleton type 是所有实例都可以有

scala> object A

scala> A.getClass
res2: Class[_ <: A.type] = class A$

scala> typeOf[A.type]
res0: reflect.runtime.universe.Type = A.type

对于这种单例,它的类型与它的类不同,要用 A.type 来表示。
这有点怪,通常我们不会用它,比如下面的方式都多此一举:

scala> val a : A.type = A

scala> def foo() : A.type = A

一方面因为scala有类型推导的功能,另一方面,因为单例是唯一的,A.type类型只有唯一的实例A(排除null),我需要的话直接用A就好了。

不过我们讨论的话题重点是 singleton type,想象一下A是一个对象实例,是否对任何实例x都存在一个x.type这样的类型呢?

scala> class A

scala> val a = new A

scala> typeOf[a.type]
res0: reflect.runtime.universe.Type = a.type

wow,真的存在。再用这个类型声明一个变量看看:

scala> val x:a.type = a
x: a.type = A@6738694b

灵的,如果赋一个非a的实例呢?

scala> val x:a.type = a2
<console>:13: error: type mismatch;
     found   : a2.type (with underlying type A)
     required: a.type

scala> typeOf[a.type] == typeOf[A]  // a.type 与 A 不是同一个类型
res2: Boolean = false

scala> typeOf[a.type] == typeOf[a2.type] // a.type 与 a2.type 也不同
res1: Boolean = false

scala> typeOf[a.type] <:< typeOf[A] // a.type 是 A 类型的子类型
res5: Boolean = true

看到了,a.typea2.type 是不同的类型!a.type也是单例类型,它也只有唯一的实例: a (排除null)

所有的对象实例都有一个x.type的单例类型,它只对应当前对象实例。这么做有什么意义呢?

这里看到一种情况,在“链式”调用风格下,有适用的场景:

class A {def method1: A = this }
class B extends A {def method2: B = this}

val b = new B
b.method2.method1  // 可以工作
b.method1.method2  // 不行,提示:error: value method2 is not a member of A

有些人很喜欢用 x.foo.bar 这样的方式连续的去操作,这种风格也成为”链式调用”风格,它要求方法返回的必须是当前对象类型,以便连贯的调用方法。不过上面,因为父类中声明的method1方法返回类型限制死了就是A类型(不写返回值类型,用类型推导也一样),导致子类对象调用完method1之后,类型已经变成了父类型,无法再调用子类型中的方法了。解决方法是:

class A { def method1: this.type = this } 
class B extends A { def method2 : this.type = this } 

val b = new B
b.method1.method2  // ok

把返回类型都改为了 this.type 单例类型,就灵了。它利用了this关键字的动态特性来实现的,在执行b.method1 的时候,method1返回值类型this.type 被翻译成了B.this.type

scala> b.method1
res0: b.type = B@ca5bdb6

这样不同的对象实例在执行该方法的时候,返回的类型也是不同的(都是当前实例的单例类型)。

小结,单例类型是个特殊的类型,单例类型绑定(依赖)在某个对象实例上,每个对象实例都有它的单例类型。不过它的场景并不多见。

scala类型系统:2) classOf与getClass方法的差异

前一篇在提到获取Class时的两个方法:classOf 和 getClass

scala> class  A
scala> val a = new A

scala> a.getClass
res2: Class[_ <: A] = class A

scala> classOf[A]
res3: Class[A] = class A

上面显示了两者的不同,getClass 方法得到的是 Class[A]的某个子类,而 classOf[A] 得到是正确的 Class[A],但是去比较的话,这两个类型是equals为true的

scala> a.getClass  == classOf[A]
res13: Boolean = true

这种细微的差别,体现在类型赋值时,因为java里的 Class[T]是不支持协变的,所以无法把一个 Class[_ < : A] 赋值给一个 Class[A]

scala> val c:Class[A] = a.getClass
<console>:9: error: type mismatch;

scala类型系统:1) 类型与类

在Java里,一直到jdk1.5之前,我们说一个对象的类型(type),都与它的class是一一映射的,通过获取它们的class对象,比如 String.class, int.class, obj.getClass() 等,就可以判断它们的类型(type)是不是一致的。

而到了jdk1.5之后,因为引入了泛型的概念,类型系统变得复杂了,并且因为jvm选择了在运行时采用类型擦拭的做法(兼容性考虑),类型已经不能单纯的用class来区分了,比如 List<String>List<Integer>class 都是 Class<List>,然而两者类型(type)却是不同的。泛型类型的信息要通过反射的技巧来获取,同时java里增加了Type接口来表达更泛的类型,这样对于 List<String>这样由类型构造器类型参数组成的类型,可以通过 Type 来描述;它和 List<Integer> 类型的对应的Type对象是完全不同的。

在Scala里,类型系统又比java复杂很多,泛型从一开始就存在,还支持高阶的概念(后续会讲述)。所以它没有直接用Java里的Type接口,而是自己提供了一个scala.reflect.runtime.universe.Type (2.10后)

在scala里获取类型信息是比较便捷的:

scala> import scala.reflect.runtime.universe._

scala> class A

scala> typeOf[A]
res44: reflect.runtime.universe.Type = A

同样scala里获取类(Class)信息也很便捷,类似:

scala> classOf[A]
res52: Class[A] = class A

另外,因为java的Object里提供了getClass方法,对于对象来说,可以直接调用这个方法

scala> val a = new A

scala> a.getClass
res53: Class[_ <: A] = class A


scala> trait T

scala> classOf[T]
res50: Class[T] = interface T

scala> typeOf[T]
res51: reflect.runtime.universe.Type = T

注意,typeOfclassOf 方法接收的都是类型符号(symbol),并不是对象实例

scala> object O

scala> classOf[O] // 这里O是一个单例对象
<console>:14: error: not found: type O

对于实例,要获取他的 Class 信息,只有通过 getClass 方法

scala> O.getClass
res60: Class[_ <: O.type] = class O$

注意到了,上面的 单例对象O 对应的class是 O$ 而不是 O,你通过 :javap O 也能看到这个单例反编译后是一个名为O$的java class

而这个单例的类型更有趣了:O.type 看上去像是这个单例内部的一个成员,用这个成员的值表示其类型;实际上.type之前的都可以看做是一种类型路径,这种特殊的类型也叫单例类型,它是面向对象实例的,每个实例都可以通过.type方式表达它的单例类型,这个后续我们再说。

再举一个例子:

scala> class A { class B }  // 嵌套类

scala> val a1 = new A
scala> val a2 = new A

scala> val b1 = new a1.B
scala> val b2 = new a2.B

对于内部类B的实例,它们的class都是相同的: A$B

scala> b1.getClass
res8: Class[_ <: a1.B] = class A$B

scala> b1.getClass == b2.getClass
res7: Boolean = true

而它们的类型却是不同的:

scala> typeOf[a1.B] == typeOf[a2.B]
res10: Boolean = false  

这是因为内部类型依赖外部实例(路径依赖类型),外部实例不同,它们也不同。但还可以对这种类型再抽象

scala> typeOf[a1.B] <:< typeOf[A#B]
res11: Boolean = true

scala> typeOf[a2.B] <:< typeOf[A#B]
res12: Boolean = true

这里A#B涉及到类型投影的概念,以后再讲。

简单的说,类(class)与类型(type)是两个不一样的概念(在java里因为早期一直使用class表达type,并且现在也延续这样的习惯);类型(type)比类(class)更”具体”,任何数据都有类型。类是面向对象系统里对同一类数据的抽象,在没有泛型之前,类型系统不存在高阶概念,直接与类一一映射,而泛型出现之后,就不在一一映射了。比如定义class List[T] {}, 可以有List[Int]List[String]等具体类型,它们的类是同一个List,但类型则根据不同的构造参数类型而不同。

类型一致的对象它们的类也是一致的,反过来,类一致的,其类型不一定一致。

scala> classOf[List[Int]] == classOf[List[String]]
res16: Boolean = true

scala> typeOf[List[Int]] == typeOf[List[String]]
res17: Boolean = false

在jvm里,类的实例数据都是引用形式,而类型没有这个约束,基础类型int,byte,char等就是非引用的。(虽然可以通过int.class来获取Class对象,但并不能找到有定义class int的地方,这只是早期java为了统一用class来承载其类型信息的方式)

小结,类型是所有编程语言都有的概念,一切数据都有类型。类更多存在于面向对象语言,非面向对象语言也有“结构体”等与之相似的概念;类是对数据的抽象,而类型则是对数据的”分类”,类型比类更“具体”,更“细”一些。

命令输入完,发现需要先执行另一条命令

比如, cd /tmp/dd,刚输入完,发现需要先创建这个目录。以往的做法是 Ctrl+a 移到这一行开头,
然后输入 mkdir /tmp/dd && cd /tmp/dd

还有个简单的方式是,Ctrl+u 删除此行命令,然后执行其他命令。之后,再 Ctrl+y 粘贴之前的命令

这里Ctrl+u 相当于 Ctrl+a , Ctrl+k

在zsh下,还可以省一步,用 alt+q 可以替代上面的两步 ctrl+u , ctrl+y

注: Esc-qAlt-q 清除当前命令,执行另一个命令结束后,再插入此命令。

搜索历史命令

很久之前,我是这样对历史搜索增强的,参考我的.zshrc文件

bash 中绑定 up 和 down 在匹配的条件中选择

bind '"\e[A": history-search-backward'
bind '"\e[B": history-search-forward'

zsh中的绑定:

bindkey "^[[A" history-search-backward
bindkey "^[[B" history-search-forward

这样 ctrl-r 后搜索 mvn 然后可以用 up/down 选择所有mvn的历史命令。
不过这样如果你刚好使用过“history | grep mvn” 这样的命令时,再通过ctrl-r搜索mvn会把这条也匹配到,并且再继续用方向键时,列出的都是“history | grep”开头的命令;这点有些不爽。

还好有一种稍微好点的方式:敲入一个命令时,通过快捷键列出所有这个命令开头的,并给出序号来选择。

把下面几行放到 .zshrc文件里:

autoload -Uz history-beginning-search-menu
zle -N history-beginning-search-menu
bindkey '^X^X' history-beginning-search-menu

然后在终端,当我敲入 mvn 后,按ctrl-x两次会列出所有mvn开头的命令,然后可以通过输入序号来执行那一次的命令了。

Enter digits:
01 mvn                                                     13 mvn dependency:sources
02 mvn assembly                                            14 mvn dependency:soureces
03 mvn assembly:assembly                                   15 mvn dependency:tree | tee /data/tmp/hsf-dep-tree
04 mvn assembly:assembly -Dmaven.test.skip                 16 mvn eclipse:eclipse
05 mvn clean                                               17 mvn hi /data
06 mvn clean compile                                       18 mvn install
07 mvn clean install -Dmaven.test.skip                     19 mvn package
08 mvn clean package assembly:assembly -Dmaven.test.skip   20 mvn package assembly:assembly
09 mvn compile                                             21 mvn package assembly:assembly -Dmaven.test.skip
10 mvn compile:compile                                     22 mvn3
11 mvn copile                                              23 mvn3 compile
12 mvn dependency:sources                                  24 mvn3 eclipse:eclipse

不过这招仍有些问题,当历史命令里带有管道时,输入序号不起作用了,比如:

hongjiang@whj-mbp ~ % ps #两次ctrl-x
Enter digits:
01 ps -aux | head -2                           12 ps -ef | grep mplayer 
02 ps -aux | head 2                            13 ps -ef | grep fmd 
03 ps -ef                                      14 ps -ef | grep java                            

这时候输入12并不能自动补齐这条历史命令。先配合着与ctr-r一同来用吧。

wordpress的 wp-login.php 页面被频繁攻击

某晚上收到阿里云vps短信报警,博客http服务不正常,第一反应以为还是mysql的问题,上次已经解决过,难道没效果?

结果上去看,mysql,nginx,php等进程全都存在,页面也能访问,不过特别慢。以为是哪里发生了阻塞,先尝试重启了mysql,没用还是慢。然后重启nginx,一样,再重启php5-fpm 还是慢。这就怪了,用top看主要是几个php-fpm进程把cpu都占满了。

去看日志,一下子明白了,有大量的请求过来,都是在访问 wp-login.php,原来是黑客又在猜我的密码,这些ip大多是来自东欧的。

网上看了一下解决办法,有2种

1)修改 wp-login.php 在请求后边设置自定义参数,参数不正确重定向到其他页面或网站。
2)限制访问 wp-login.php 的 ip地址。

我采用了第一种方案。

阿里云vps上mysql挂掉的解决办法

用阿里云的vps用作blog服务器,系统很稳定,已经100多天一直运行正常,大概从上个月开始发现blog的mysql会有时挂掉,会收到短信通知。之前没太追究,重新启动了mysql解决的。今天上午又收到短信,已经第三次了。

查了一下日志,三次基本都是一样的:

130728  6:50:14 [Note] Plugin 'FEDERATED' is disabled.
130728  6:50:14 InnoDB: The InnoDB memory heap is disabled
130728  6:50:14 InnoDB: Mutexes and rw_locks use GCC atomic builtins
130728  6:50:14 InnoDB: Compressed tables use zlib 1.2.3.4
130728  6:50:14 InnoDB: Initializing buffer pool, size = 128.0M
InnoDB: mmap(137363456 bytes) failed; errno 12
130728  6:50:14 InnoDB: Completed initialization of buffer pool
130728  6:50:14 InnoDB: Fatal error: cannot allocate memory for the buffer pool
130728  6:50:14 [ERROR] Plugin 'InnoDB' init function returned error.
130728  6:50:14 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
130728  6:50:14 [ERROR] Unknown/unsupported storage engine: InnoDB
130728  6:50:14 [ERROR] Aborting
130728  6:50:14 [Note] /usr/sbin/mysqld: Shutdown complete

解决方法:
1) 在 /etc/mysql/my.cnf 的 mysqld 下增加下面一句:

innodb_buffer_pool_size = 64M

还要设置一下swap分区,因为我的vps是没有swap分区的,通过fdisk -l1mount 看不到swap的信息,需要手动添加一下。

2) 添加swap分区的步骤:

2.1) dd if=/dev/zero of=/swapfile bs=1M count=1024
2.2) mkswap /swapfile
2.3) swapon /swapfile
2.4) 添加这行: /swapfile swap swap defaults 0 0 到 /etc/fstab

目前已经设置了swap分区,并重启了mysql,后续观察一下看看还会不会出现吧。

参考:http://stackoverflow.com/questions/10284532/amazon-ec2-mysql-aborting-start-because-innodb-mmap-x-bytes-failed-errno-12

补充,经过近2个月观察,没再发生down掉的情况。