月度归档:2014年07月

『白夜行』

『白夜行』很难在看完『白夜行』之后忍住不说些什么,但是说些什么意味着剧透,这对没看过的人来说不公平,所以只能说些部分。

东野圭吾以多人视角,抽丝剥茧般慢慢揭开一桩横跨了20年的悬案,让人停不下来。人们当然不会想到,20年前的一个当铺老板在密室被杀,间接影响到20年后一家医药巨头企业的家族纷争,并有多人因此送命。这中间牵连了太多人,也改变了多人的命运。

故事最后,凶杀案的真相反而成了其次,人们更关注的是凶手的得失。唐泽雪穗冷血对待“共生”20年的同伴死在眼前,头也不回幽灵般离开,一定让无数人愤愤不已吧。我所理解的白夜行,是桐原亮司一直以一个黑暗骑士的身份,守护着唐泽雪穗,以此为其父赎罪,然而从唐泽雪穗的话中看来,桐原亮司一直以来还是作为工具身份存在的可能性更大一点。她说过,“我的天空里没有太阳,总是黑夜,因为有东西代替了太阳。虽然没有太阳那么明亮,但对我来说已经足够。我从来就没有太阳,所以不怕失去”。谁都会被桐原亮司的守护所感动,但是故事的结局并没有表明他的付出有所回报,所以这只能是一个阴暗的故事,外加一个悲伤的结局。

网络中能找到白夜行改编的电视和电影,我看过剧照,男女主角的气质与小说有差距,所以还是不看为妙。在我看来,唐泽雪穗扮演者有一个绝佳人选,桥本爱,可能是她在『告白』中演的阴冷女学生太过印象深刻吧。

--EOF--

erlang map?

Joe Armstrong在『Programming Erlang(2 Edition)』中介绍了R17的新增特性:map数据结构,这称得上是里程碑式的大功能了。map的语法还算简单,以下是其定义:

1
#{Key1 Op Val1, Key2 Op Val2, ... , KeyN Op ValN}

其中,Op是=>或:=两个操作符中的其中一个。前者表示更新Key对应的Value或者添加K-V对;后者表示将Key对应的Value进行更新,如果Key不存在,则返回异常。

书中还介绍了map在模式匹配中的应用。比如下面这个demo就是通过map实现的计算某个字符串里各个字符的出现次数:

1
2
3
4
5
6
7
8
9
count_characters(Str) ->
count_characters(Str, #{}).
 
count_characters([H|T], #{ H => N } = X) ->
    count_characters(T, X#{ H := N+1 });
count_characters([H|T], X) ->
    count_characters(T, X#{ H => 1});
count_characters([], X) ->
    X.

纸上得来终觉浅,在我把本地的R15替换成R17,准备尝试的时候问题出现了,这个demo连编译都过不了:

1
2
3
4
5
6
1> c(demo).
demo.erl:7: illegal pattern
demo.erl:8: variable 'N' is unbound
demo.erl:10: illegal use of variable 'H' in map
demo.erl:7: Warning: variable 'H' is unused
error

里里外外翻了R17的release note,发现R17里map的实现远不是书中介绍的这么回事。

以下是R17中能支持的4种类型map操作:

1
2
3
4
M0 = #{ a => 1, b => 2}, % create associations
M1 = M0#{ a := 10 }, % update values
M2 = M1#{ "hi" => "hello"}, % add new associations
#{ "hi" := V1, a := V2, b := V3} = M2. % match keys with values

下面的特性是不支持的:
-- No variable keys (key中不能含变量)
-- No single value access (不支持取单个value,即不支持#{Key}操作)
-- No map comprehensions (不支持map推导,例如 #{X => foggy || X <- [london,boston]}.) 好在R17已经提供了一个maps模块,封装好了map的基础操作。通过这些内置函数,不难实现上文中的count_characters/1函数。

1
2
3
4
5
6
7
8
9
count_characters(Str) ->
  count_characters(Str, maps:new()).
count_characters([], M) ->
  M;
count_characters([H | T], M) ->
  case maps:find(H, M) of
    {ok, Val} -> count_characters(T, maps:update(H, Val + 1, M));
    error -> count_characters(T, maps:put(H, 1, M))
  end.

map的完整蓝图在EEP-43上有定义,书中介绍的应该是说将来map能支持这些操作,事实上R17.0所实现的只是map定义中很小的一个子集。关于Erlang map数据结构的前世今生这里有描述。另外有些文章认为,存储大量的K-V时,当前版本的map效率比dict, gb_tree要差,使用时需慎重,不过有些场景下用来替换record还是绰绰有余的。

--EOF--

2014年世界杯时间轴

2014年巴西世界杯告一段落,以下只言片语来自新浪微博:

2014年6月14日 05:04
#世界杯##荷兰5:1西班牙#屌丝逆袭的典型呀,几名老将带领着一群90后草根,完成了一次华丽的复仇。这场比赛可能会让荷兰直通8强,但是要走得更远,还需要克服骨子里的悲观气质。加油吧,飞翔的荷兰人。哈哈哈哈哈。

2014年6月15日 08:09
#世界杯##英格兰1:2意大利#意大利依然稳健,慢热的特性使得他们依然是冠军的有利争夺者。英格兰经历大换血之后给人耳目一新的感觉,年轻人就是有拼劲。

2014年6月17日 01:56
#世界杯##德国4:0葡萄牙#肯定有人嚷嚷葡萄牙被裁判黑了,就葡萄牙这阵容,能进德国队首发的不超过3人,德国队替补里面,至少5人能进葡萄牙首发名单。一定要量化的话,我认为葡萄牙和德国队之间至少有100个中国队的差距。目前列强已悉数登场,如德国这般攻守平衡的,只有巴西。

2014年6月19日 01:57
#世界杯##澳大利亚2:3荷兰#澳大利亚人遗憾出局,光有16强的实力,却没有16强的命。荷兰人的晋级之路则是顺当多了。

2014年6月21日 02:02
#世界杯##意大利0:1哥斯达黎加#英格兰回家了。

2014年6月24日 01:58
#世界杯##荷兰2:0智利#范加尔重新定义了库伊特,让一个前锋去踢后卫,大赞。几次换人直接锁定胜局,牛逼。

2014年6月25日 01:02
#世界杯#刘建宏是我见识的唯一一个水平越来越差的解说。

2014年6月29日 00:04
#世界杯#巴西队唱国歌都这么有气势,像是要把智利吃了一样,看来智利队死定了。

2014年7月5日 02:07
#世界杯##法国0:1德国#法国踢得很好,德国踢得也好不到哪去,一场势均力敌得较量。除了开场10分钟和最后10分钟,其余时间看得人昏昏欲睡。难以相信,这支法国队居然比德国还要年轻,前途无量啊。

2014年7月6日 02:05
#世界杯##阿根廷1:0比利时#两支球队均有着突出的特点,阿根廷小灵快,坚持短传配合加超级明星的个人突破。比利时前后场都有大高个压阵,一路的长传冲吊,阿根廷对此倒也没辙。赞同黄健翔的说法,比利时离开世界赛场太久经验太少,他们需要的是大赛磨练。这也解释了为什么比利时一路走来跌跌撞撞。

2014年7月6日 07:10
#世界杯##荷兰4:3哥斯达黎加#荷兰人把多年攒下的点球RP用在了哥队身上,幸运的同时又有点遗憾:这要是用在阿根廷或决赛上该有多好。不管怎么说,最神的还是范加尓,换上一个神奇的点球门将,这个门将在俱乐部中扑过20粒点球,只扑出过2粒…

2014年7月9日 06:07
#世界杯##巴西1:7德国#没有席尔瓦,巴西不知道怎么防守了,没有内马尔,巴西不知道怎么进攻了,但是这些都不是输成这个尿样的借口。只能说巴西人中邪了,远在里约的基督光环照不到这里。

2014年7月10日 05:01
#世界杯##荷兰对战阿根廷#上半场略沉闷,配上刘建宏的解说尤其如此。

2014年7月10日 06:50
#世界杯##荷兰0:0阿根廷#运气在同哥斯达黎加的点球中用完。阿根廷机会比荷兰更多更好。

2014年7月14日 06:16
#世界杯##德国1:0阿根廷#阿根廷人浪费了多次单刀机会,这样的结局让我都不忍心再黑了。

2014年7月14日 12:38
阿根廷输球输世界杯,好像得到了世界上很多人的同情、鼓励和关爱,当年荷兰咋就没这待遇?是因为荷兰普遍长得比较挫吗?相比较而言,荷兰比阿根廷悲惨多了,这个世界真是充满了恶意啊。

2014年7月14日 12:57
多年之后,本届世界杯的大部分场景和比分将被遗忘,但是有些瞬间和片段将载入史册,所幸的是,我全程见证过:1. 舌尖上的苏亚雷斯。2.德国7:1巴西。3. 荷兰5:1西班牙。4. 荷兰人的12码梦靥依然继续着。5. 德国取代西班牙成为技术流足球的代表,并且会在较长一段时间内统治世界足坛。

--EOF--

FreeMarker Configuration的模板路径配置问题

FreeMarker的Configuration类可以指定模板文件的加载路径。比如最简单的:

1
2
Configuration cfg = new Configuration();
cfg.setDirectoryForTemplateLoading(new File(tmplDir));

tmplDir可以是通过Thread.currentThread().getContextClassLoader().getResource("")等方式拿到的目录,那么FreeMarker会从这个目录下寻找模板文件。大多情况下,这没什么问题。

但是,假如一个Web项目被打成了war包,或者模板文件在一个可执行jar包里,classpath不是以一个目录的形式存在,调用setDirectoryForTemplateLoading()方法时File对象new出来抛异常。这时候要用setClassForTemplateLoading()方法:

1
void setClassForTemplateLoading(Class cl, String prefix);

这个方法最底层其实就是封装了Class cl.getResource(prefix)方法,因此,prefix的格式就有些讲究,如果prefix以/开头,表示其为相对于classpath根目录为基准的绝对路径,这时候Class参数可以忽略了,任意填;如果不以/开头,表示所填路径是相对于Class文件的相对路径,需小心填写,一不小心就会得到一个FileNotFoundException:Template xxx not found.

跟了一下Configration.getTemplate()方法,它调用的是TemplateCache.getTemplate()方法:

1
2
3
4
5
6
7
8
public Template getTemplate(String name, Locale locale, String encoding,
        boolean parse) throws IOException {
    Template result = cache.getTemplate(name, locale, encoding, parse);
    if (result == null) {
        throw new FileNotFoundException("Template " + name + " not found.");
    }
    return result;
}

cache.getTemplate()中做的事情就是根据参数,判断缓存中是否已存在模板文件,如果已存在,则直接返回,否则调用findTemplateSource()方法查找模板文件:

1
newlyFoundSource = findTemplateSource(name, locale);

findTemplateSource()方法紧接着调用acquireTemplateSource()方法,再根据不同的TemplateLoader调用相应的findTemplateSource()方法,最终,ClassTemplateLoader的getURL()方法完成模板文件资源的定位:

1
2
3
4
protected URL getURL(String name)
{
    return loaderClass.getResource(path + name);
}

关于Class的getResource()方法doc上有如下说明:
The rules for searching resources associated with a given class are implemented by the defining class loader of the class. This method delegates to this object's class loader.

意思是它虽然是由Class实例发起的调用,但是可以看做是加载它的classloader的委托,classloader怎么搜寻类资源的,它就是怎么定位资源文件的。

关于FreeMarker定位模板文件的更多详情,参见官网说明:『Template loading』

--EOF--