《创变》

刚到酒店,最近在补酒店行业和OTA领域的业务知识。花了两晚读完《创变》,收获很多。关于7天连锁,记得是研究生时候读过《从不竞争》,讲7天如何在短短四年从诞生到上市野蛮生长。印象深刻的是组建团队的过程,之后如何管理团队,如何压缩成本,因为看了《从》,有次订酒店还特意定了7天,想看看公共吹风机是咋回事。

读《创变》才知道,原来这几年下来,7天经历了这么多。期间郑南雁还短暂离职出去创业,然后回归,带领7天进行大转型,蜕变成现在的铂涛集团。书写的很好,转型思路和转型过程都讲解的很清楚,看得出来作者很专业。单是书中的第25章(来自郑南雁的一趟经济走势课)就值了整本书的价钱。

书中记录了很多郑南雁出人意料——对于我这个外门汉来说——的观点,比如郑认为:酒店将不再是人们出行中的附属选择,而会成为时尚和休闲消费目的地;酒店企业真正的竞争对手是OTA;中端酒店市场将会呈现出和经济连锁酒店市场完全不同的形态,绝对不可能几家独大,一定是百花齐放;转换思路不如换人来得快;微乱是企业最好的状态。除了郑南雁搞品牌众筹的主线思路,整本书让我印象最深的是他对于经济驱动力转移的观点:

那么,为什么产业竞争时代会向以用户需求为核心的时代过渡?郑南雁的理解是,所有产业的生产能力都很容易出现过剩情况,这与前三个阶段经济核心驱动力的转移原因是一致的:发现资本可以买到资源时,资源的重要性就排在后面了;发现资本追着产业跑时,资本的重要性就往后排了;当各个产业的生产能力都出现严重过剩时,用户就变成了最有发言权的人。郑南雁认为,酒店企业与OTA之间绝不仅仅是产品与渠道的关系,而是最后将饭碗给了谁的问题。

按照这个观点,商业发展的终极,都是以服务好用户为核心。

对于如何跟OTA抗衡,郑南雁的思路也很清晰,他认为“强势的高端品牌”具备去OTA化的能力,因为OTA的弱点就是“无法让酒店品牌与消费者产生更高的情感沟通和黏性,无法实现品牌增值”。他将OTA类比为空军,酒店运营管理类比为陆军,酒店品牌类比为防空导弹。由此导出铂涛集团的的竞争策略:以品牌构建能力为先锋,以运营管理能力为阵地,以会员平台为核心能力培育。即三个要素:品牌,运营,会员。

书中好几章讲述了从7天向铂涛转型详细过程,如何调整供应链体系、会员体系、IT及业务流程体系、市场开发体系、门店运营体系,以及这个过程中的各种阻力、不理解,但最终事情做成了,说明郑南雁这个人真是有强大的内心和事业激情。前些日子和一个同学闲聊,都认为能鼓动人跟随、具有强大个人魅力或者说煽动能力的人,本质上需要这个人自己“坚信”这个事情,否则也没法让他人跟着他坚持这个事情,就像书中说的,“当一个人有了一个愿望就会形成一种气场。”

除了郑南雁的观点,书中也记录不少其他高管的一些思考,都挺有意思。比如波涛互联网公司CEO孟令航的观点,他将互联网行业分为三种模式:一是垂直模式,做的是用户体验、价格、促销、产品;二是平台模式,定规则;三是生态模式,筑环境。

书中从酒店行业的视角看问题跟纯互联网思维看问题有挺大不同。比如他们认为,酒店企业需要做三件事:客源、品牌、运营;OTA只需要做一件事:客源。因为要做的事情多,借助OTA的客源和数据优势,可以帮助酒店更好的做运营,进而提升品牌。通过对接OTA的数据,可以分析酒店的经营情况,分析哪些地区生意更好,哪些地方缺乏酒店,对酒店开发和选址都有帮助。

最后摘一小段某位铂涛老员工的感受,“感觉铂涛是分久必合、合久必分,一件事不会长久的做下去,每隔一段时间就要调整,打破官僚机制,让员工始终处于不确定、不安全的状态,一定要把员工的潜力都挖出来。”可想而知,这种状态下很多人会不舒服甚至很痛苦。我最近时常也在想组织和个体的关系,我发现很奇怪,似乎现在这个所有企业都不约而同强调“个体崛起”的社会,处于组织中个体却并得到没有所谓的独立性甚至保留自我,每一个人,不论层级,都被迫的随着组织这个器官的移动而移动,一刻也无法停止。所谓个体崛起,更像是假释出来的囚犯,在尽情补偿自己被剥夺的生活。无奈的是,连这位老员工也认为,这种不断的变化也许才是组织活力的源泉。

Haskell的副作用究竟是如何隔离的

大家都知道Haskell是纯函数式语言,因为Haskell将side-effects隔离出来了,但是“隔离”是怎么体现出来的,一直很难讲清楚,举个例子试着分析一下。

隔离副作用的表现就是,对于一个纯函数,永远无法引入副作用,换句话说,对于相同的输入,输出永远相同。在Haskell里,你永远无法定义出一个类型为String -> String的函数,对于相同的输入,输出的String值不同。

我们举一个John Hughes的Paper《Programming with Arrows》中的例子来说明。

假设我们想定义一个函数,计算某个字符串中某个单词的计数,最简洁的方式是使用Function Composition:

count w =
    length . filter (==w) . words

可以看出来,这个函数的类型是:String -> Int。这是一个无副作用的函数,对于同样的字符串,输出的结果永远相同。

现在我们改变一下这个函数的功能,不从字符串里面统计,而是读一个文件,然后输出向终端输出最后的结果,类似unixwc命令。我们可能很自然的会想将函数改造成这个样子:

count w =
    print . length . filter (==w) . words . readFile

代码虽然很直观,但是这个函数是编译不了的,因为类型不对,readFile的类型是FilePath -> IO String,返回值是IO String,是一个IO Monad,也就是说是有副作用的。

IO Monad的内部状态是无法escape的,所以你无法使用readFile函数,在IO Monad之外,写出一个类型为FilePath -> String函数来读取文件内容并返回。通过这种类型上的约束,Haskell就可以避免副作用的引入。

如果想实现这个功能,函数类型就只能是FilePath -> IO ()

count w =
    (>>=print) . liftM (length . filter (==w) . words) . readFile

我们从后向前来解读一下。

readFile的类型是FilePath -> IO String,表示整个函数的参数类型是FilePath,其实就是String

length . filter (==w) . words的类型是String -> Int,若想将这个函数应用到IO Monad里,为了类型兼容,必须要先lift这个函数,即将a -> b的函数lift成m a -> m b。在我们的环境下,就是将 String -> Int提升成IO String -> IO Int

这两部分组合后,即liftM (length . filter (==w) . words) . readFile,大家应该可以看出来,返回值类型是IO Int,但是print的类型为a -> IO (),在这个上下文中,即为Int -> IO (),无法直接使用。如果想使用,就需要一个类型为IO Int -> (Int -> IO ()) -> IO ()的函数,去掉具体类型的话,其实就是IO a -> (a -> IO b) -> IO b,这不就是Monad的bind运算符(>>=)吗?

最后理一下:

  • liftM (length . filter (==w) . words) . readFile的类型是FilePath -> IO Int
  • >>=的类型是IO Int -> (a -> IO ()) -> IO ()
  • print的类型是Int -> IO ()

将这三个函数放到一起,就是:

count w filePath=
    (liftM (length . filter (==w) . words) . readFile) filePath >>= print

写成point-free的形式,就是:

count w =
    (>>=print) . liftM (length . filter (==w) . words) . readFile

解释器的效率

上学时候似乎一直在词法分析和语法分析阶段徘徊,好像编译器的大头是Parsing似的,其实无关紧要,大多数时候手写递归下降就够了。AST之后的部分才是核心,代码生成还算了解,代码优化就没怎么看过了。前几天重看了一遍wingolog的博客,又看了一些解释器优化的资料,才后知后觉的发现一些其实比较浅显的东西。

解释器效率低下的主要原因是,除了执行指令本身的“有效工作”以外,有很多不能省去的“额外开销”。比如解释“result=op1+op2”,除了加载操作数,执行加法以外,其余都是额外开销。如果是直接解释AST,额外开销就是visit各种节点。如block节点,仅表示一个作用域,并不做任何实际工作,另外,遍历语法树时虚函数调用的开销也很大。如果是生成IR,那么执行IR的loop就必须非常高效。这个loop主要工作就是维护一个PC(program counter),执行指令,然后将PC指向下一条执行,跳转并执行。看似直接的过程,因为这是整个解释器的核心,“额外开销”在这个loop占的比例将极大的影响解释器的执行效率,所以需要(a)保证这个主循环所有关键变量都使用寄存器,需要(b)避免pc修改后,执行indirect branches,因为CPU执行这类jmp开销很大,很难进行分支预测,另外(c)主循环的代码要能放得进CPU的指令CacheLine中(似乎是64k吧)。如果指令集很大,通常会实现成一个大的switch语句,编译器并不善于优化这类代码,通过gcc的Labels-As-Values扩展可以避免(b),但是对于(a)(c)处理并不好,所以很多编译器如LuaJIT2就是手写汇编,缺点是每个平台都得写一遍。

上面说的是解释器,效率的核心点是降低“额外开销占比”,优化手段是充分利用寄存器、CPU的分支预测和指令Cache。要想彻底消除这些“额外开销”,就需要进行JIT了。当然中间还有一步是代码优化,还在看,先不写了。

开始写点东西

好久好久没写一个字了。

辗转一年,有许多进步,也多了许多焦虑,自省更深了:擅长的不擅长的、骨子里真心不喜欢的、禀性难移的都摸清楚不少,但是人真是不能低估自己改变自己的能力,起码不能低估改变自己表象的能力。

转眼工作三年半,搞了两年多分布式存储,搞了一年多推荐系统,接下来,搞搞业务架构。似乎一直没在舒适区待过,刚刚觉得透彻了些就换了。分布式系统、规划、带团队、扯业务、算法、架构,都有不少心得。应该不是坏事,起码按外人的眼光看没有浪费时间。

生活上变化挺大,一切十分传统。接下来尽量抽一些时间,继续记录一些读书心得和技术思考。技术主要是非工作涉及的部分。第一个计划是关注下高性能解释器实现,把之前的解释器优化下。这半年Haskell的进步挺大,已经基本可以随意写代码了。最近尝试写了一点Rust,模仿seastar的fpc,发现原本直接的东西套上Rust的ownership限制着实令人抓狂。算法上,除了组里大张旗鼓的搞DeepLearning,别组同事也在自己玩,我却一头扎进数学基础迟迟未上道,也是计划的一部分。

这半年读了很多书,主要是名著、经济学和一堆数学书,但是一个字的心得也没留下,希望搬进新房后能有空闲和心情记录。

不管怎样,还是想开始写点东西。

Datomic架构的一点理解

一年多以前Rich Hickey刚刚发布Datomic的时候就看过它的架构图,当时印象深刻的是以Datalog作为查询语言,架构却理解不深。这一年多对分布式系统有不少心得,晚上无意间发现一篇《unofficial-guide-to-datomic-internals》,读完发现理解起来并不费劲,然后又找出Datomic的文档读了一通,做了一点简单记录,又翻看了Hickey的视频《Using Datomic with Riak》。夜深人静,写点粗浅理解。

应该说函数式数据库从架构上非常不同于我们平时会接触到的分布式系统,主流的分布式NoSQL(Cassandra, Mongodb, Riak, Aerospike, Couchbase)也好,分布式MQ(Kafka,ActiveMQ)也好,架构设计上除了考虑如何处理Replication和Partitioning,再就是考虑一致性和可用性之间的TradeOff,对于数据引擎,就考虑是读优化还是写优化,基本上思维都围绕这些打转。所以遇到像datomic这样的东西,架构上分成Peer、Transactor和Datastore,然后交互访问上又从来不提我们熟知的这些概念,就会觉得很奇怪。我们会想这个Peer是指什么,看起来又不是Gossip协议中的Peer,更奇怪的是它是一个CP系统,却构建在Riak,Cassandra这类Dynamo系的AP系统之上。这是怎么回事。

Datomic架构图

传统上,我们都将DB作为一个全局状态来看待,应用层进行更新、读取,整个系统行为上与一个有共享状态的多线程程序无异,要考虑复杂的数据竞争问题。而对于纯函数式环境,所有东西都是immutable的,修改即创建,不再需要考虑竞争问题了。函数式DB也同样,对数据库的修改,实际是增加新的内容,而旧的数据仍然存在,每条数据都有关联的Version和时间戳,这样有一个好处是,我们可以查看此前任意时间的Snapshot,这对做分析或者审计都非常有用,也可用来恢复由于程序bug引起的事故。

Datomic架构上还是比较简单的,数据存储使用第三方的成熟方案,冗余和分片就不需要操心了。Transactor作为独立的协调者用于处理一般写入和事务,Peer作为客户端的Lib处理读请求。可以看到,写和读是两条独立的路径,这与《turning-the-database-inside-out》的思路很相似,只不过datomic需要处理事务,要满足ACID。这个保证逻辑并不复杂(我们暂时忽略处理事务本身的细节,只关心整体流程):每个Transaction都会经由单点的Transactor来执行,Transactor将请求发给Backend如Riak,当写入都成功后,Transactor主动向所有连接着的Peer推送更新通知,Peer这方更新本地索引,新数据可见,Transactor返回给客户端。又由于Transactor是个单点,并且事务处理不并发,所以Isolation Level是Serializable,一致性是满足Linearizability的。如果理解没有错误,Datomic里面的索引(Covering Index)实际已经包含了数据,按理说更新索引也就更新了数据,不过考虑到如果这样做,Transactor可能Fan-Out太大,网卡未必受得了,所以可能仅仅通知的是Segment的UUID。系统配置上,需要配置保证写入的Factor,如Riak,N=3,W=2,这里R=1就足够了,因为不需要做Read-Repair,依靠自身的最终一致就好了。对于Peer来说,最近更新的数据已经记录在其内存索引里(细节不清楚,猜测记录的是UUID,Segment需要重新从Backend读取),如果读后端Miss,就表明数据同步尚未结束,选择其他节点或者重试就好,牺牲可用性保证一致性。这个逻辑很有意思,语义上是强一致的,但是实际的写入是最终一致。这其实就是Datomic在AP系统上实现CP的奥秘。

Datomic还有一个优势就是Caching,一般我们使用MySQL+Cache模式时,如果DB做了修改,需要失效Cache。这个过程是需要业务来实现,如果涉及到从库,从库前面也有一层Cache,这其中就有一个一致性问题。比如说,更新了主库,然后失效主库前的Cache,再失效从库的Cache,但是如果Cache失效发生于数据同步到从库之前,就很可能造成Cache又重新回填了旧数据,造成从库这边Cache出现脏数据。一种比较好的方法是搜集从库的Binlog来删Cache,但是这可能需要修改协议和MySQL实现;还有一种方法是做延迟删除,等几秒再进行一次Cache删除。这是我们遇到的实际问题,但是Datomic根本没有这类问题。数据是Immutable的,只要索引正确,数据就是正确的,而且由于是Immutable的,我们也就不需要担心数据在使用过程中被篡改,所以就可以尽可能的进行Caching,而让大部分Query都在客户端进行,避免了网络交互。客户端查询有很多优势,比如业务需要查询一系列连续的数据,如果使用MySQL,写业务时不太可能会Cache一个Range的数据,于是就需要多次Round-Trip,而对于Datomic,这些数据(Datom)很可能在同一个Segment里。

另外,Datomic的缺点似乎也很明显:Transactor是个单点,即便官方Pro版提供HA,应该也仅仅是提供个主备,实际工作的只有一个Primary,所有的写入都走这一个点,必然容易出现瓶颈。还有就是索引,Transactor为了写入效率,应该会维护一个完整的索引,假如这样,单机容量也是瓶颈(好像最初GFS那个单点Master)。不过,这个问题可能并不严重,对于一个业务系统,有ACID要求的数据一般并不太多,Datomic的一个特点就是它是工作在其他成熟的分布式NoSQL之上的,所以用户大可以直接使用下层的NoSQL来存储对一致性要求不高的数据,而使用Datomic存储重要或需要复杂查询支持的数据。Transactor虽说是个单点,但是它只处理写入,听Hickey描述,写入逻辑跟LevelDB差不多,基本都是顺序写,定期建Index,总体的性能应该还OK。另外一点担心是,Transactor到Peer是个广播,如果Peer节点特别多,假如出现慢节点,很可能拖垮整个Transactor,导致写入不可用。这些关键的细节问题Datomic如何处理就不得而知了。

除了上面提到的这些点,还有诸如Datomic五种类型索引的设计、索引根信息的维护(需要强一致存储)、Datalog查询优化等等有趣但与架构关系不大的内容,没太仔细考虑,有时间再仔细玩味。

他人的“三十而立”

看了一会0xlab成员walkingice博客,很久没耐心读一些博客了,刚才读到《三十而立》这篇,一种久违的平静之感油然而生,就如同Rock听得太久,突然听到轻音乐一样。0xlab的几位成员从实验室成立之初就一直有关注,我个人的技术之路受Jserv影响颇深,对台湾黑客天然有一种亲切感。

工作两年,想来可悲,似乎一切都与在校时的期望不尽相同。我一直认为,软件开发是与艺术家和诗人最为接近的职业,我们的技艺里有美的东西存在,美的设计、美的结构、美的细节。影响我最深的几位黑客都是搞嵌入式的,直观上嵌入式更加生硬一些,离UI那些表象的东西更遥远,但恰恰在这个人群中,总是可以发现内心清澈、有纯净技术理想的人。而互联网技术圈,到处是浮夸的炫技、秀思维,互相吹捧、互相贬低成风,大家都自封屌丝,没有人真的会想用技术来改变什么,所谓技术理想不过是做一个如何等样的系统,收获吹捧,将来跳槽能多拿几块钱而已。

我想,这是两个技术领域的区别,也是两个社会的区别。嵌入式领域有深厚底蕴,互联网技术日新月异,低层次的人不需要太多积累即可胜任,高层次的人又凤毛麟角,很像武侠小说的世界。领域的差别还在其次,想来我自己所处的层次是主因。再者,大陆与台湾这两个社会现阶段也相当不同,台湾近些年出生率低,社会福利好,工作压力相对小,人们惯于舒缓的生活,这种社会更容易孕育出文化的东西,而非商业的东西,人们对于幸福和成功的理解也不同。大陆尚处在原始积累阶段,贫富分化严重,大部分人还在为生计奔波,经济独立是人格独立的基础,贫穷的理想主义又得不到社会认同,自然与财富无关的理想都会被认为是矫情,所谓理想不过是企业家和房地产商们的理想,都是财富理想。

结尾摘一段Blog:

自己最近給自己的答案,則是分成對內與對外兩個面向。

對內,則是尋求自我的實現。 而自我實現無法以職業、收入來滿足。當然並不是說我不喜歡錢,或不希望給家人好的生活。而是在維持家庭正常的生活水平前提下,追求我認同的價值,在這個過程之中我才能感受到自己的生命被充實。

對外,一個微小改進的價值,以一年、十年甚至百年的幅度來看,可能會有不同的結論。世上的英雄少之又少,多數的人是在時間的長河中互相推擠著緩緩向前。我知道個人無力指揮流向,但我也知道流向的改變總是各種作用力加成的結果。

對內的因素我無法超越,對外的因素又是我的信仰,加總起來就是我現在的選擇。我想這就是我如此快樂的原因。

话说回来,是否有一天,我也能遇到一小群人,成立0xlab一样的小组织,以一种不虚伪、无私心的理想主义情怀为这个社会做一些事情。

CAP

发现自己貌似理解了CAP这么久,其实不太准确。重新理理思路记录一下。

首先是CAP的确切概念。以cap-faq的定义为例:

Availability: will a request made to the data store always eventually complete?
Consistency: will all executions of reads and writes seen by all nodes be atomic or linearizably consistent?
Partition tolerance: the network is allowed to drop any messages.

有点咬文嚼字,再看foundationdb的定义

Availability: Reads and writes always succeed.
Consistency: A read sees all previously completed writes.
Partition tolerance: Guaranteed properties are maintained even when network failures prevent some machines from communicating with others.

CAP理论中的Availability与我们服务承诺的SLA中的可用性不是一回事,可以说CAP中的A是指“强可用性”,部分可用或写不可用读可用在CAP的概念里都不能称为可用,但通常SLA保证的可用性,只要不是完全挂掉,都算是可用。

一致性在CAP里同样指强一致,也即linearizably consistent或linearizability,如cap-faq中的例子,set(10),set(4),get()->10是最终一致,而非强一致。

网络划分好理解。

平时说CAP三选二,其实CA系统现实中并不存在,因为没有绝对可靠的网络,网络划分一定会出现,当出现P无法满足时,C和A必须二选一,也就是要么是CP系统,要么是AP系统。单主架构必定是CP系统(当然可能CP都不是),如MongoDB,多主架构或All-Peers(其实也是多主)架构一定是AP系统,如Cassandra。

所以有时我们争论说“该场景是个CAP场景,我们满足不了”,很可能是一种错误,因为业务方很可能可以接受一般意义上的“可用性”,也可能事实上可以接受最终一致。

CAP放松一些限制,就可以形成一个很实用的系统,如我们的Redis集群,简单的将强一致放松为最终一致,就可以实现网络划分的情况下读可用。虽然此时严格上讲,这个系统AP和CP都算不上。更多情况下,一个分布式系统会融合CP和AP,CP的部分负责维护一些关键的共享数据,比如用ZooKeeper维护集群状态,AP部分保证系统的可用性。

曾先生的碎碎念

半年一个字也没写了,非技术类书读得也不多,代码倒是写了不少。最近读完《曾国藩从政为官方略》文字版,两个月前读蔡锷的《曾胡治兵语录》,再两个月前读梁启超的《曾文正公嘉言钞》,再之前读《曾国藩家书》原著,最早是听郦波先生讲“曾国藩家训”。一路下来,对曾国藩也算熟了,了解越深越喜欢、越佩服。最近打算读一读李鸿章。

读曾国藩还真不是想读出点职场政治心得,也不是想学习怎么当领导玩弄权术搞厚黑,品行上自认为跟彭玉麟更贴近一些,“平生最薄封侯愿,愿与梅花过一生”。大四毕业回家火车上,路上跟一个姑娘聊文学(当年真是够文艺的),她极力推崇曾国藩,她推崇的理由是,曾国藩对弟弟和子侄教育的很好,是个大教育家。当时心想立一套不华而不实的家规,搞一套自己践行起来都吃力的准则来要求家人,定然很无趣。

中国写家训远不止曾国藩一位,但是立功、立德、立言三不朽的似乎只有他一个。宦海沉浮,戎马半生,整个人生完整真实,不虚伪不做作,在那个黑色的年代,能出现这样一个人,怎能不让人感慨。每次想到攻克南京之后拒绝造反,我都非常遗憾。毛主席说“予于近人,独服曾文正”,据说蒋介石枕边也是曾国藩家书。

要讲曾国藩,一讲一本书,我感触最深的有两点。

第一点是他做人非常透彻,看自己透彻,看别人透彻,看事情透彻。这三点每一点都不容易,但是要成不朽功业,我认为这是必须的。想真正看透事情,看透形势,要能看透人,看透人得能看透自己,否则就会很片面。观察周围的人,能看透自己的很少。当然成事与否与看透事情没有必然因果关系,但是要想一直成事,这就是必须的。曾国藩能看透,看透了未必容易做到,但是他有“定见”,有耐心,有毅力,看透了就能按正确的方向去做,他能把握自身的“战略”部分,不会让一些小问题影响自己对于自己的把控。他看透成大事要引用一帮“正人”,待下属要诚,人才“忠义血性”为上,懂得共患难更要能共富贵。他懂得,乡绅不要去干涉地方官的事,无权的时候一定要争,权大的时候一定要放。观其一生,这是让其利于不败之地的根本原因,他懂人更懂自己。

第二点是他真实,有时候看到曾国藩懦弱、自责的时候心里会窃喜,原来他也会这样。靖港大败之后,曾国藩立下遗书跳水自尽,所幸被下属及时救起。年轻时候戒烟屡次失败。有时候心情不好也会跟人吵架怄气。他有种种缺点,但他了解自己,明辨是非,从不自欺欺人,”其一生得力在立志,自拔于流俗,而困而知,而勉而行,历百千艰阻而不挫屈;不求近效,铢积寸累,受之以虚,将之以勤,植之以刚,贞之以恒,帅之以诚,勇猛精进,坚苦卓绝。”像曾国藩这样毫不虚伪的大圣大贤,回溯中国古代,恐怕只有王阳明能与之相提并论。

《中国哲学简史》

记得不太大的时候就听说冯友兰是中国近代最大的哲学家,那时候对哲学连起码的感性认识都还没有。要说对哲学渐渐有印象,最初还是大学时候读罗素的《西方哲学史》,当时还细细做了笔记。此后是庞思奋的《哲学之树》等,基本都是综述类的书。以前以为中国并不存在西方意义上的哲学,直到后来看了《明朝那些事儿》和《阳明学的形成与发展》才知道,原来中国的哲学之树也是这样枝繁叶茂,硕果累累。那段时间,每次想到王守仁的话都觉得热血沸腾,也的确知行合一实践了一段时间,只可惜好景不长啊。

“依此良知,忍耐做去,不管人非笑,不管人毁谤,不管人荣辱,任他功夫有进有退,我只是这致良知的主宰不息,久久自然有得力处,一切外事亦自不能动。”

《中国哲学简史》这书是冯友兰先生的学生由英文翻译过来的,但是读起来非常顺畅。该书从先秦哲学一直讲到近代西方哲学侵入,书本身不是简单的人名书名陈列,而是仿佛一座宏大的教堂,将中国哲学的全貌画于其中,让每个人都对整体脉络有了清晰的认知。可能由于这本书最初是为老外写的吧,内容非常容易理解,按照哲学的发展过程,一步一步娓娓道来,评论鞭辟入里。最初还想记些笔记,后来发现,如果记,那需要记得的东西就太多了。有时间重读吧。

书中有一些内容印象非常深刻,也是这几年读历史的时候有感觉,但是没法表达的一些东西。比如书中解释为什么中国没有宗教,而只有哲学,冯友兰先生的解释是,哲学是高于宗教的一种表现形式,宗教是哲学加上迷信和仪式等,人可以没有宗教,但不能没有哲学。这其实解释了为什么人们批评中国人没有信仰,但我们并没有活得野蛮。

纵观本书,可以感受到,中国的哲学对整个社会文化的影响不出意外的主要集中在儒家和道家上。儒家讲究入世的哲学,道家讲究出世的哲学,道家的宗教分支影响很小,佛教的影响其实也不算大,但佛学影响很大。佛学和道家的结合,产生了禅宗,也实际就是现在我们每个人所感受到的佛教意味,也是影视作品、武侠小说中最常出现的佛家的样子。尤其禅宗讲究“顿悟”,让人想起《昆仑》中那个痴傻的花生。

读禅宗那一部分的时候,印象很深刻,这一年来,自己的心态和佛家所追求的心态很相似,心无挂碍,对任何事情都“无滞着”。很多时候,即便遇到事情,需要讲些什么的时候,也不会开口,开口与否有什么关系呢,这倒很像禅宗的第一义“静默”,什么也不言语。这其中有很多哲学意味的道理,但我很难理解这个哲学的意义在哪里。禅宗和道家都提倡“无为”,这里不是讲汉初“无为而治”那种政治上的理念,而仅仅就哲学层面讲,“无为”就是“无心”,做事不求任何结果,既不求好的结果也要避免坏的结果,“毋宁说它的目的,在于做事而不引起任何结果”。认为“以无心做事,就是自然的做事,自然的生活”,这在现代生活中根本不具备可操作性,即便有操作性,这玩意有什么用呢。读完这书我对佛学就更加困惑了:既然有知识的高僧都明白佛祖只是肉身凡人,佛是精神上的而非宗教上的,那么修行成佛是为了什么?冯友兰先生说,儒家修身是为了成为圣人,或者最高境界“内圣外王”,可以做政治家,施展政治抱负,但是佛家呢,修行的目的竟然是涅磐!“涅磐”是什么,它是说,人有欲念,就会有痛苦,就逃离不了生死轮回,万劫不覆,涅磐,就是超越生死轮回。修行的目的难道就是看破一切、麻木不仁?我是实在无法理解,若能相通这一点,我估计也可以顿悟成佛了。

新道家和新儒家都是很有趣的主题,涉及到很多大人物,尤其加上清代的古文学派和今文学派之争,让这段发展历程一下子生动起来。冯友兰先生对理学和心学的分析也很值得品味。在读程朱理学部分的时候,正巧也在读一些Haskell相关的论文,又回想起《西方哲学史》中描述的那些奇怪的宇宙发生论,突然意识到,其实这些东西并非因为缺乏精确的科学依据而毫无用处。这些假想其实是一个模型,我们可以在这个模型上验证我们所理解的现象。若是这些现象在这个模型上可以得到解释,并能自圆其说,那就说明这个模型是合理的,并且在此基础上还可以推演出一些未知的东西,这就是理学,也是目前很多研究领域所采用的方法。中国人认为宇宙由阴阳组成,并用阴阳来解释所有现象,我们解释得通,也就说明宇宙从某种程度上来说,的确是有阴阳构成。即便将来某种科学理论可以完美的解释宇宙,我们也可以断定,那些公式的推演过程中,必定有会在某些条件下会以阴阳作为表象为我们所辨识。

名家和法家篇幅也比较多。名家更接近西方的逻辑主义,即冯友兰先生所谓“正的方法”,逻辑学很有趣,跟编程语言也关系紧密,但名家本身不是太有趣。法家更像是那个社会背景下的独特产物,但他们的思想我很赞同,韩非认为“势”,“法”,“术”是治国三个不可缺少的要素。这三个词很像雕爷提起的曾鸣教授的经商之道“取势、明道、优术”。法家的治国之术非常简单明了,领导只要懂得赏罚就可以了,他只要将特定的职务授予一定的人,然后观察事情做得如何就可以了。他不需要也不应该操心下属用什么方法完成,只要他能完成就行。完成的好,就奖赏,完成的不好,就惩罚。多么简单易学的道理,所以在一个官僚机构,越大的领导越好当。换句话说,懂了这个道理,什么人都可以当大领导,这也是为什么这个构建于裙带关系之上的社会,还可以照常运转。

这几个月读书效率比较低,很多书看不进去,心情也很糟糕,最近又翻出三岛由纪夫的书来读,希望能找到一些安慰。在重读《假面的告白》,打算读完后重读《挪威的森林》,然后重读保罗·奥斯特的《孤独及其所创造的》。三岛由纪夫、村上春树、保罗·奥斯特,这三位是这些年来最喜爱的小说家了,每当心情差的时候,只有他们的书能看得进去。这三位好像一点交集也没有。

Haskell中概念的理解(一)

刚才看了Bryan O’Sullivan的一个演讲《Running a startup on Haskell》,讲公司招人以及使用Haskell的一些现状。很赞同Brayan说的一句话“if cerntain skills are really widely distributed in a population they become completely meaningless”,如果某个技术非常流行,说懂这个东西毫无意义,如果一个人在简历里写他懂PHP懂Java,就像是在告诉别人他长了个脑袋。

断断续续学习Haskell也好几年了,没事的时候就会拿它作为一种思维训练,试图准确理解其中的概念,但是智力实在不够,每次多理解一些而已。最近好像突然领悟了很多(不敢说“想通”),不知是不是量变到质变的一个过程。

最近连续读了很多材料,有些是以前读过但未理解的。回想这个过程,应该是从理解Polymorphic Typechecking开始,读的是Basic Polymorphic Typechecking,主要收获是了解了大致的算法,对datatype(type constructor)有个更具体的感知。学习Haskell,理解类型多态很重要,只有理解了它才能理解typeclass和高阶多态,其实类型多态很像C++的模板,只是用在type constructor的定义中让人感到理解困难而已,尤其是type constructor中包含函数时,考虑下datatype中类型变量的顺序问题会有助于理解这个问题。然后重读了《Learn You a Haskell for Great Good!》第八节“Making Our Own Types and Typeclasses”,这一节算起来读过不止一次,但仅这次完全读懂,对类型理解也更加深刻,尤其是我感到自己可以很容易的理解StateMonad那诡异的s->(a,s)了,于是开始读一些Monad的tutorial

这几年学Haskell,大部分时间都花在Monad上,很早就会用,但是理解不到这个东西的好处,以及Monad到底如何“隔离副作用”,更别提自己在何时、如何设计一个Monad了。最近这块感悟比较多,有几个重要材料值得参考。Haskell两本免费教材《Learn You a Haskell for Great Good!》和《Real World Haskell》中Monad相关章节很重要。《All About Monads》对每个常用的Monad都有讲解,文中第一段直指Monad核心,理解这句话就理解了Monad,但是理解这句话是结果,试图理解这句话本身并没有太大意义;文中提到Monad的三个重要属性Modularity、Flexibility、Isolation ,其实这三个属性说的是一回事,理解这点很重要,同样理解这点是结果,另外要理解One-Way-Monad以及为什么Maybe不是One-Way-Monad,这是隔离副作用的关键。然后是Brian Beckman的视频“Don’t fear the Monad”,这个视频我很早以前看的时候还以为他讲跑题了,这次看完才恍然大悟,Brian的Bind类型推导过程也很精彩,看的时候回想一下《All About Monads》的第一段。正如Brian也提到的,其实Monad的所有魔力都在于那个bind(>>=)函数,每个Monad有自己个性化的bind。对于One-Way-Monad,这个函数使得我们可以操作m a中的a,而不会将a泄露出去,这是Monad的隔离性。一组相同的Monad(如IO)可以放到一个do-notation里执行,也就是combining computations,这种组合方式其实是一种模块化,它将一类操作关联到一处,而不影响其他部分,举例说,如果出现大量的嵌套case+Maybe语句,就可以将这些Maybe放到一个do-notation里,精简代码而又永远不会出错,因为与Maybe有关的计算都在bind里定义了。这样一个do-notation其实代表了某一类型Monad的一个计算序列(bind),而其计算的内容在其内部(类型m a的a)无法被外界直接取得,除非通过bind,也就是《All About Monads》的第一句话,“A monad is a way to structure computations in terms of values and sequences of computations using those values. ”

理解Monad之后才发现,其实这个概念并不难,理解它也并不一定需要范畴论的知识。让我吃惊的是发现在haskell中,这东西并不是什么重要的组成部分,举个不恰当的例子,就好像微积分在量子物理学中的作用一样,这是个必备技能,但根本没有触到关键。学习Haskell的难点还在于类型上,从类型的角度思考问题,通过类型来做问题抽象,才是困难之处。

下面说说typeclass,这是haskell中的一个核心概念,从SPJ的一个分享来看,这个东西最初只是一个实验功能。可以从Mark P. Jones的paper《Functional Programming with Overloading and Higher-Order Polymorphism》中找到这个概念诞生的原因。typeclass是为了解决任意多态类型所存在问题的一个折中方案。考虑函数size(例子瞎举的),它返回“某个数据结构的的大小”,如果希望它是多态的,而不是每次都要定义某个类型自己的sizeA::A->Int,sizeB::B->Int的话,就需要定义size::a->Int,但是如果不对a的类型加以限制,就可以将没有size的类型应用于这个函数,导致运行时错误,这是haskell这类静态类型语言所无法容忍的。于是typeclass出场了,通过定义一个typeclass叫做Countable,并将size定义为size::Countable a => a -> Int,对于每个应用于size的类型,都要求其为Countable的instance,否则类型检查失败,这样就非常优雅的解决了这个问题。可以看出typeclass更加接近interface。总之,它最初的出现是为了让haskell更精确的做类型推导,不会出现运行时错误,同事保持多态的优美。我前面说它是个折中方案是因为,(我认为)它其实是一种人工向编译器提供类型信息的方法,同时增加了不小的编码成本。

Mark P. Jones的paper非常优秀,其中不仅讲解了typeclass,还介绍了kind system,这是理解haskell类型系统的一个重要部分。有时间要细读读Mark以及Simon Peter Jones, Simon Marlow, John Hughes, Philip Wadler这群神人的paper,正是由于Haskell背后有这群人,这门语言才会这样魅力四射、引人入胜。

Haskell中还有很多其他内容没有花时间去学习理解,比如并发和Arrow,估计近期也没太多时间了。