月度归档:2014年01月

2013年的环台湾(5): 嘉义-高雄

早晨离开旅社在7-11吃早餐,边上有位在看报纸的老伯,聊了几句,他说你应该会经过白河,那里盛产荷花,前些天正盛放的时候他去拍过照。当我到了经过白河镇的时候,确实看到很多荷花,莲叶还在,但花已枯萎。

台湾南部的乡间。

这一天的的目的地是高雄,距离不算太远,骑的比较放松。中午的时候到达台南县境内,在一个小镇的路边吃了碗面还有烤肠,看了一下桌子上放的自由时报,上面的一些政治观点让我有了新的认识,绿营们对待李登辉的态度也是不尽相同的。

在经过台南市区的时候在一个水果摊前要了杯现榨果汁,和老板聊了起来,他是台湾本省人,老婆是福建人,已经来台快20年。对于大陆他还是有些看不起的态度,比如不如台湾发达,不讲卫生,不遵守秩序等等。我问他上次去大陆是什么时候,他说是15年前回过泉州的丈母娘家,我告诉他大陆现在的一线城市已经超过台北的繁华程度了。

我遇到的绝大部分台湾人都是来过大陆的(但大陆人却很少去过台湾)。近些年到过大陆的台湾人基本都很惊讶大陆的基础设施的变化,在政治态度上,遇到的大部分台湾人也没有我想的那么反感大陆的政治,对于民主化进程台湾走在前边,但他们也存在很多的问题。与台湾的多数人聊天,在相互尊重的前提下,还是非常愉快的。

在台南地区我印象深刻的是路边有很多很大很大的榕树,榕树下有槟榔铺,人们喝茶下棋,这种景象非常符合我对南国的想象。很遗憾当时骑车经过时没有停下来拍一张榕树下的照片。

出台南市区的时候,我遇到两个带着头盔骑车的人,我跟上去和他们打招呼,问他们是否也是去高雄,一同骑一段。

2013年的环台湾(4): 竹南-鹿港-嘉义

经过第一天的适应,对环境和自身的状态有了些把握,第二天的路线安排的稍微远了一些,计划在中午的时候到达鹿港小镇,晚上到达嘉义。这段距离有170多公里(后来我被gps带错路在田间绕了不少路,这一天的实际行程估计有180公里)。

之所以想去鹿港,是因为罗大佑的歌曲曾对我们影响很深,小镇也正好在经过的路线上。

海边的稻田。

上午的一段路程骑得非常兴奋,身体渐渐进入状态,虽然有一些起伏的山坡但因为顺风,速度很快。途中遇到一个徒步旅行的小伙,台湾本地人,从台东出发,要顺时针绕岛一周,已经是第32天,方向跟我相反,简短寒暄了几句。

中午非常炎热,脖子和鼻尖已经有些难受,经过彰化时我在一家超市里买了防晒霜。比预计晚了2个多小时到达鹿港,发现这个小镇的游客很多,早已不那么宁静。那个妈祖庙我也没有进去,只在外边拍了照片。在鹿港,碰到了两个来自福州的骑行者,从福建平潭坐船到的台中,自行车也是自己带过来的,我们聊了聊环岛的计划,也相互加了微信。

鹿港到嘉义还有很远的距离,加上中间有一段路被gps带错路(正好赶上修路),我在乡间的小路上绕了很久。乡间有些小狗会过来咬人,被两只小狗咬到鞋子。

当晚一直到10点多才到嘉义,很疲惫,找到旅行社之后,洗漱了一下去外边吃了点东西。

2013年的环台湾(3): 台北到竹南

我在西宁南路与开封街二段交叉的附近找到了一家旅馆,台币1200元,可以把自行车放到房间里(忘了说租车的时候自行车没有锁),只是电梯有点小,需要把自行车竖起来。

一大早起来,下去退房的时候老板说不需要检查房间。在附近有家早点铺,要了一碗线面糊,看起来不怎么样,吃起来还不错。我忘记了价格多少,印象中日常的消费与大陆相差不大。

沿着淡水河,到达八里,这片海是台湾海峡

第一天的行程并没有明确的终点,预估过大概会到新竹(一百公里左右)。当时并不确定当地的天气、路况下体力会如何,所幸选择的逆时针方向对开头是比较有利的,在台西线沿海骑行的时候正是顺风,加上台湾的马路沥青铺得很细,租的单车也比山地车轻一些,带的东西也不像上次去海南时那么多,骑起来还比较轻松。

中午在7-11解决的,台湾的7-11有上万家,里面有wifi提供,有一些简单的便当和咖啡,很方便。这一天的大部分时间是沿着西部滨海公路,路上先后遇到两个来自广州的也是独自骑车的家伙,一同骑了一段,但因节奏不同(他们骑得稍微快一些,他们的目的地是苗栗,比我要计划的要远一些),后边我没有再跟着他们。

到达新竹的时候,还没有到傍晚,我继续骑了一段直到太阳落下,在一个叫“竹南”的小镇落脚,这个小镇确实很小,之前计划中没有注意到过这个地方,不期而遇的一个小镇。经过一个妈祖庙,里面有一个很大的妈祖神像,后来才了解到这时台湾最大的妈祖像。

我在小镇找旅馆花了很长时间,看不到在大陆很容易找的酒店或快捷旅馆,后来问人,才知道这边的旅馆叫“旅行社”,小镇的旅行社大都很简陋,很有八十年代的味道,但能洗澡,有空调和电视,价格很便宜(600-800台币)。

晚上我在小镇转了一圈,吃了点东西,也逛了一下小镇上的书店。

2013年的环台湾(2): 到达台北

从上海出发,经由厦门转机,在晚上7点多的时候到达松山机场,在机场办理了一张当地的中华电信卡,然后在ATM上换了一些台币,台湾的大部分银行ATM上都支持银联,非常方便。出了机场之后,一眼就看到了夜色中的101大楼,空气有一点潮热,但非常通透,明显要比大陆的城市好很多,市区的霓虹灯、广告牌上的字多是竖排,这是我对台北的第一印象(后来我在书店,以及地铁上看到学生们的教科书,发现排版上也是竖排的)。

叫了出租车去往民生西路,靠近大稻埕码头的地方,出租车师傅年级较大,跟我聊起淡水河,大稻埕码头过去运粮,曾是很繁华的地方,现在有些没落。我后来发现那片区域用了很多西北地区的地名来命名街道(直到返回前买了龙应台的《大江大海一九四九》那本书才明白台北这座城市的命名规则)。

到了那家吉安特店,伙计现组装一台环岛的单车,这种车的轮胎是介于公路车与山地车之间的一种,后来证明台湾的马路铺的还是比较好的,这种轮胎在台湾的马路上骑正合适,比山地车轻很多,减少很多阻力。随车带的还有两条备用轮胎和一个打气筒,车载灯。本来也提供一个驮包,不过我自己有带过去,就没用他的。我把驾照压在他这里,回来后再付给他钱,十天左右的费用折合成人民币约五六百元。

店里时不时有人进来,有两个老年人了解到我从大陆过来要环岛,跟我交流了一下他们的经验,非常热心。车子组装好了之后,我问了伙计哪儿有快捷旅馆,他告诉我顺着西宁路下去,到西门町,说那片超多。

我骑车先在附近转了一下,试试车子怎么样,遇到问题好再让他调整一下。在大稻埕码头有个小广场,有一个两人的小乐队在演唱,一个演奏口风琴,一个唱歌。稍微有点小雨,我把车停在广场的亭子下,一会儿来了一群带着头盔,穿着骑行服骑车的人,也来亭子下歇息。我便与他们交流起来,他们的队伍中基本是中老年人,是平时有空就一起组织骑行。他们很客气的称呼我“同胞”,并递给我一罐啤酒和凤爪。得知我是一个人环岛,跟我说了很多安全事项,以及遇到问题时该怎么处理。其中的一位老先生还留了电话号码给我,让我非常意外他们的热心以及对陌生人的信任度。

除了单车也聊了聊其他有关政治、旅行等事情,比如他们自称虽然属于蓝营,但前两天也去参与了游行让马英九下台,因为他们觉得小马哥这几年做的不好。后来雨停了,我说要去西门町那边找旅馆跟他们道别,现在只记得他们自行车队的名字是“野猪队”。

2013年的环台湾(1)

2013年的环台湾
几年前台湾自由行刚刚开通的时候,在某个论坛里看到一个哥们自己备了辆可折叠的变速自行车,到台湾骑车环岛,当然他不是完全自行车环岛,有些路线还是坐的火车;看完后心里很痒痒,去台湾旅行的想法很久就有,如果能骑自行车环岛一周,更是一种独特的感受了。

那时我还没怎么骑过长距离的,最远的就是和几个小伙伴从杭州骑到苏州,第二天再返回,往返距离也就360多公里。那次骑行的过程也很美妙,我记得那天是2011.12.10号,因为晚上正好有月全食,我们当晚11点多才到苏州市区,在吴江到苏州的途中,看着月亮一点一点的变小,非常有趣。除了对月食的记忆,我们途中还经过一片树林和幽静的江南小镇,以及第一次到苏州,看到老城区依然保持的黑瓦白墙的民居。

后来在2012年的春节,我尝试了一下骑自行车环海南岛,本来有愿意一同前往的小伙伴,后来有事不能去了,我便自己一个人出发,用了6天时间完成了海南的骑行。当时的一些记录见这里

完成海南的环岛之后,对长距离的骑行有了些信心,考虑在2013年的春节再实现骑车环台湾岛,不过在时间上正好遇到一些变数,春节期间没有成行,直到十一假期才有时间,排除了很多的阻拦因素,不能把这个计划再拖延下去。

不过这次环岛我几乎没有做任何准备,从海南环岛结束之后,我基本没有骑过长距离,只在2013.6月份端午节的时候恢复了一下只骑了100公里,再加上工作较忙没有时间准备一个详细的计划,只是email联系好了台北一家出租自行车的吉安特店,约好我在哪天过去取车;其余住宿之类都没有预定。台湾自由行的签证时间是15天,我本来的计划是在台湾呆12-13天,计划在路上10天左右,只订了飞往台湾的单程机票,想着等环岛快要结束的时候再让老婆帮忙预定回程的票,谁知这是一个严重的失误。

一是我没有注意到个人自由行的签证要求入境时必须有返程机票(不得不在机场花四百元买了一张“假“的电子票,因为全票太贵,厦门机场估计也遇到了很多这样的事情,于是他们提供了花几百元帮你办理一张机票,仅供你入境使用,等你入境后,他们会把机票改为无效)。二是当我环岛快结束的时候,已经是返回的高峰期,机票很难预定了。

scala雾中风景(11): isInstanceOf与类型擦拭

scala中用isInstanceOf判断一个对象实例是否是某种类型时,如果类型包含参数类型,会被擦拭掉(jvm的做法)。所以会导致例如下面的问题:

scala> (1,2).isInstanceOf[Tuple2[String,String]]

<console>:11: warning: fruitless type test: a value of type (Int, Int) cannot also be a     (String, String) (but still might match its erasure)
          (1,2).isInstanceOf[Tuple2[String,String]]
                            ^
res33: Boolean = true

在给出了一段警告之后,结果返回true,如果数据对象支持模式匹配且元素较少,可以用模式匹配来精确判断:

scala> (1,2) match{ case(_:Int, _:Int) => println("ok"); case _ => "no"}
ok

但元素比较多的话,或数据不支持模式匹配,要判断参数类型,就需要让编译器在运行时保持参数类型的信息了,在scala里是通过TypeTag来实现的,参考scala类型系统:19) Manifest与TypeTag

scala> def checkType[A: TypeTag](a: A, t: Type) =  typeOf[A] <:< t

scala> checkType((1,2), typeOf[Tuple2[String,String]])
res37: Boolean = false

scala> checkType((1,2), typeOf[Tuple2[Int,Int]])
res38: Boolean = true

注意恶意攻击

博客最近的访问记录里总能看到一些攻击者,想要访问wp的登录页;因为我的登陆页设置了不太好猜的参数,参数错误的话会被跳转到google,所以起了一道保护。这个经验也是我之前因为被攻击而学会的,曾遇到过一次ip显示来自东欧地区的攻击,大量请求在login页面,视图猜测我的密码,导致cpu被占满,博客无法访问。

最近又发现一些恶意攻击,视图猜测我设置的参数,类似下面的:

   13:45:20 ->/wp-login.php?action=register
   13:45:22 ->/wp-login.php?registration=disabled 

或许是年底了,cracker们也忙碌起来了?可这个技术博客有啥好图的呢。

我所理解的monad(7):把monad看做行为的组合子

先从最简单的情况开始,我们用monad封装一段执行逻辑,然后提供一个map组合子,可以组合后续行为:

// 一个不完善的monad
class M[A](inner: => A) {

    // 执行逻辑
    def apply() = inner

    // 组合当前行为与后续行为,返回一个新的monad
    def map[B](next: A=>B): M[B] = new M(next(inner))
}

用上面的例子模拟几个行为的组合:

scala> val first = new M({println("first"); "111"})
first: M[String] = M@745b171c

scala> val second = first.map(x => {println("second"); x.toInt})
second: M[Int] = M@155f28dc

scala> val third = second.map(x => {println("third"); x + 200})
third: M[Int] = M@b345419

scala> third()
first
second
third
res0: Int = 311

看上去对行为的组合也不过如此,其实在Function1这个类里已经提供了对只有一个参数的函数的组合:

scala> class A; class B; class C

scala> val f1: A=>B = (a:A) => new B

scala> val f2: B=>C = (b:B) => new C

scala> val f3 = f1 andThen f2
f3: A => C = <function1>

Function1里的andThen方法可以把前面函数的结果交给后边的函数处理,A=>BB=>C 组成函数 A=>C

这样看上去我们当前实现的monad和Function1有些相似,封装了一段行为,提供方法将下一个行为组合成新的行为(可以不断的组合下去),在java里我们可以对比Command模式和Composite模式。

真实世界的”行为”(Action),普通函数的问题

1) 结果的不确定性,行为链的校验问题

这个不完善的monad已经可以做一些事情,但有个很显著的问题,只能组合”普通函数(plain)”,所谓普通函数是指结果类型就是我们想要的数据类型。

比如f: A=>B 这个函数可以表示一个行为,这个行为最终得到的数据类型是B,但如果这个行为遇到异常,或者返回为null,这时我们的组合过程会如何?

这意味着我们必须在组合过程中判断每个函数的结果是否合法,只有合法的情况下,才能与下一步组合。你可能会想把这些判断放在组合逻辑里不就得了吗?

def map[B](next: A=>B): M[B] = { 
        val result = inner  // 执行了当前行为
        if(result != null) {
            new M(next(result))
        }else {
            null.asInstanceOf[M[B]]
        }
    }

上面的情况不是我们希望的,因为要判断当前行为(inner)的结果是否符合传递给下一个行为,只有执行当前行为才能拿到结果。而我们希望组合子做的是先把所有行为组合起来,最后再一并执行。

看来只能要求next函数在实现的时候做了异常检测,但即使next函数做了判断,也不一定完美;因为行为已经先被组合好了,执行的时候,链上的每个函数仍会被调用。假设组合了多个函数,执行时中间的一个函数即使为null,仍会传递给下一个执行(下一个也必须也对参数检测),其实这个时候已经没有必要传递下去了

在scala里对这种结果不确定的情况,提供了一个专门的Option,它有两个子类:SomeNone,其中Some表示结果是一个正常值,可以通过模式匹配来获取到这个值,而None则表示为空的情况。

如果我们Option来表示我们前面的行为,可以写为:f: A=>Option[B],即结果被一个富类型给包装起来,它表示结果可能有值,也可能为空。

2) 副作用的问题

另外一个真实世界中无法用函数式完美解决的问题是IO操作,因为IO无论如何总要伴随状态的产生或变化(也就是副作用)

一段常见的举例片段:

def read(state: State) = {
    // 返回下一状态和读取到的字符串
    (state.nextState, readLine)
}

def write(state: State, str: String) = {
    //返回下一状态,和字符串处理的结果
    //这里是print返回为Unit类型,所以最终返回一个State和Unit的二元组
    (state.nextState, println(str))
}

这两个函数类型为State => (State, String)(State,String) => (State, Unit) 在入参和出参里都伴随State,是一种”不纯粹”的函数,因为每次都执行状态都不一样,即使两次的字符串是一样的,状态也是不同的。这是一种典型的“非引用透明”问题,这样的函数无法满足结合率,函数的组合性无法保障。

要实现组合特性,需要把函数转换为符合“引用透明”的特性,我们可以通过柯里化来把两个函数转化为高阶函数:

def read = 
    (state: State) => (state.nextState, readLine)

def write(str: String) = 
    (state: State) => (state.nextState, println(str))

现在这两个函数相当于只是构造了行为,要执行它们需要后续传入“状态”才行;等于把执行推迟了;同时这两个函数现在符合“引用透明”特性了。

再进一步,为了把State在构造行为的时候给隐藏起来,我们继续重构:

class IOAction[A](expression: =>A) extends Function1[State, (State,A)] { 

    def apply(s:State) = (state.next, expression) 
}

def read = new IOAction[String](readLine)

def write(str: String) = new IOAction[Unit](println(str))

现在我们可以把read看做是一个 () => IOAction[String] 的函数,write则是:String => IOAction[Unit]的函数。

用一个”富类型”表示行为的结果

我们看到现实世界的行为,除了可以用A=>B这样的plain function描述,还有A=>Option[B]A=>IOAction[B] 这种结果是一个富类型的函数来描述。我们把后两种统一描述为:

A => Result[B]

当我们要组合一个这种形式的行为时,不能再使用map,而是flatMap组合子。

实际上,我们一开始就提到过map并不是必须的方法,flatMap才是,可以把map看做只是一种特例。把行为都用统一形式A => Result[B] 来描述,对于普通的A=>B也可以转为A=>Result[B]

现在我们看几个flatMap的例子,先看一个Option的实际例子:

scala> Some({println("111"); new A}).
        flatMap(a => {println("222");Some(new B)}).
        flatMap(b => {println("333"); None}).
        flatMap(c => {println("444"); Some(new D)})
111
222
333
res14: Option[D] = None

上面的例子看到,在组合第三步的时候,得到None,后边的第四步c => {println("444"); Some(new D)}没有被执行。这是因为None与后续的行为结合时,会忽略后续的行为。