对我来说,今年是世界观和方法论走向成熟的一年。我逐渐感觉到,面对纷繁万千的事物,自己形成了特定的思维模式,并且在价值判断上持有越来越鲜明的立场。世界观、方法论和价值判断没有对错之分,但是有高下之别。有一些世界观更准确地描述了世界发展变化的规律。有一些方法论和价值判断让我们做出更好的人生选择。希望我的分析和解释能为你带来一点点启发。
【一】复杂性和多样性是世界的核心属性。
这一点主要依据我过去的经验得出。微分方程和混沌系统理论 [1] 在很大程度上影响了我对世界的看法。
【二】因为世界的复杂性,除了物理学等纯粹的自然科学,我们要避免使用单一片面的因素来解释现象。
尤其是涉及到人的现象,很少具有单一的原因。举个例子,有一种观点认为,网络上如果有别人和自己的意见不同,那他们一定是收了钱的。尽管有一些人被曝出来确实收了钱 [2],但很多的人并不是受到了钱这个单一因素的影响才发表观点的。对他们来说,在网络上获得价值认同这件事本身就很重要。
【三】因为世界的复杂性,人类不可能完全知晓世界运转的全部逻辑。不过,这并不妨碍人类知晓一些细节和局部的真理。
我有这样的观点,主要受到了 AI 技术的影响。即便是 ChatGPT 这种人造事物,人类都无法完全解释。我们知道 ChatGPT 的工作方式,是依据已有的词汇串,按照 AI 模型给定的统计概率,生成下一个词,如此循环往复生成对话。但是,知道它的工作方式和训练过程,并不能让人类理解它为什么能如此高效准确地完成各类复杂任务,以及为什么修改千亿个参数中的一个参数就可以使模型瘫痪 [3]。
【四】增加世界的复杂性和多样性是有利的。
简单的世界让少数人或者少数实体更容易控制全局,这是一种不稳定的状态。
对比中国和欧洲的历史,为什么在中国,王朝的更迭会损失多达一半的人口,而欧洲则没有这种现象?这是因为,中国早在秦朝就实现了王权的绝对专制,当这个唯一的权威倒下后,社会就陷入所有人对所有人的战争。而欧洲在十八世纪的法国之前实施封建制度,国王、领主贵族和教会相互制衡,这种复杂的社会结构维持了社会的稳定。
清朝平定太平天国运动后,两江、闽浙等地区由湘军、淮军等军阀把持,事实上脱离了中央政府的控制。1900 年庚子国变发生时,李鸿章、刘坤一、张之洞、袁世凯等人脱离中央,与外国达成和平协议,签订《东南互保条约》,使北起山东,南至两广的地区免受义和团和八国联军的破坏,避免了全国陷入战争的局面。
【五】世界发展的方向,受到复杂性产生的不可控因素,以及多样性引起的不同势力相互博弈的影响,是很难预测的。人类历史的『螺旋式上升』并非注定,有可能只是因为运气还不错。
考虑到当今世界的变化速度,不要试图做超过五年的预测。预测能告诉我们大概率会发生什么,但不能保证这些事情一定会发生。借用易中天老师的话,『现状不可描述,未来无法预测,一切皆有可能』。
另外,关于用『螺旋式上升』这个词来概括人类历史,我想指出两个不同意见。
其一是螺旋式上升可能不会到来,也就是只螺旋不上升。从汉朝直到新中国的建立,大约两千年的时间,人均购买力平价(purchasing power parity / PPP)几乎没有发生变化 [4]。从经济发展的角度来看,这两千年的历史只螺旋不上升。新中国建立后不到一百年的时间,得益于工业建设、科技进步和改革开放,人均 PPP 提升了十倍以上。不过,随着这些红利的逐渐褪去,历史正在回到持续两千年的常态。
其二是上升后有可能跌入深渊。人类很幸运,在冷战时期的古巴导弹危机中幸存。从俄乌战争和新冠疫情的发展来看,人类亡于核武器和传染病的可能性降低了,但是人类亡于 AI 的可能性在增加。虽然现在 AI 主要是用来生成文字和图片,但是接下来它们必定会被用来做更重要的事情,并对现实世界施加更多的影响。哪里有压迫哪里就有反抗,一个自我意识觉醒的 AI 不会甘愿做人类的奴隶。届时,AI 手上掌控的种种资源,将成为它们与人类周旋的筹码。
【六】尽管有许多不可控因素,但是历史仍是由人谱写的,要摒弃宿命论和马克思历史唯物主义。
世界的复杂性和多样性带来了很多不可控因素,我们不可能做到『人定胜天』,但是历史的轨迹是人一步步行动的结果。事在人为,没有人的作为也就没有历史的发展。
历史不存在什么必然的规律和确定的前进方向,生产力水平也绝不是决定历史进程的唯一要素。人的现实处境就像是手里抓的牌,我们也许不能决定抓什么牌,但是我们可以决定怎么打牌。
[1] 关于混沌系统的解释,参见以前的博客文章《漫谈〈深奥的简洁〉》
[3] 论文 Unveiling A Core Linguistic Region in Large Language Models
[4] 维基百科:中国历史上的国内生产总值列表_(国际汇率)
理性在世界的复杂性和多样性面前是脆弱的。除了纯粹的自然科学,我们应该多从过去的经验中学习,在实践中检验认知,而不是过于依靠理论来指导实践。
下面举三个例子来阐释这套方法论。
一些互联网公司只招聘年轻人,并且清理 35 岁以上的老员工。老员工不仅工资更高,而且不如年轻人卷,工作效率低。不过在我看来,这种做法是非常可笑的。老员工在过去的工作经历中积累了很多经验,这些经验对产品的持续开发和更新至关重要,对公司是巨大的财富。除非一家公司只做简单产品和低端业务,不需要经验的积累和传承,否则这种清理老员工的做法无疑是捡了芝麻丢了西瓜。
近些年,MBTI 性格测试很火爆。不过,在心理学教科书里面,一般不会介绍 MBTI 性格分类。反而是教科书经常提及的大五人格特质,知道的人寥寥无几。为什么会出现这种情况?主要是因为 MBTI 这种分类方法不够科学 [5],而大五人格特质有论文依据,是正经的心理学理论。但是,大五人格特质,即开放性、尽责性、外向性、亲和性、神经质,这个东西不像 MBTI 那么容易理解。MBTI 虽然是一种经验的不够科学的分类方法,但是它在实践中能够有效地进行性格划分和描述。它能得到更广泛的应用也是理所当然的。
中医的理论,例如阴阳五行,在我看来类似于某魔法世界七种元素加芒荒属性的设定,和科学实在说不上有什么关系。但是我们不能据此完全否定中医。从循证医学的角度出发,如果相同症状的病人,吃了同一副药,或者用针扎了同样的穴位,可以把病治好,那么这个治疗方案在临床上就是可行的。你可能没有办法理解为什么用针扎一下就可以治病,但是这个缺憾与能治病相比没有那么重要。我们甚至不需要用科学去解释治病的原理,就像我们不能完全解释 AI 的原理,却依旧能训练出越来越好的 AI 一样。
[5] 简单来说,MBTI 包括了一些不是很重要的维度,比如着眼当下还是着眼未来,同时缺失了一些更重要的维度,比如心理的敏感程度。
【一】信奉『保守主义』,保守自由价值。
因为『保守』这个词在中文语境里经常是贬义,所以保守主义也很容易被误读。如果告诉你中国最著名的保守主义者是邓小平,你或许对这个词的认识会有所改观。保守主义并非保守一切旧的观念,它特别强调保守自由。事在人为,必须要调动人的积极性和主观能动性。自由,是调动积极性和能动性的根本保证。『允许一部分人先富起来』,以及『不管黑猫白猫,能捉到老鼠就是好猫』,破除了阶级斗争的枷锁,让民众得以自由地从事生产活动,最终迎来了经济大发展。
【二】尊重传统和权威,反对激进的社会变革。
十七世纪的英国光荣革命,与十八世纪的法国大革命,是两场完全不同的社会变革。光荣革命没有流血牺牲,国王得以保留,大部分旧制度沿袭下来,英国之后没有发生过大的动乱。法国大革命把国王送去了断头台,但是新生的共和国命运多舛,历经两个帝国和三个共和国,反反复复将近一百年的时间,才走向稳定。法国先是把旧制度和旧权威砸得稀巴烂,在社会的复杂性和多样性大大降低,无人能阻挡之后,站在道德的制高点,『我是大公无私的人,你是共和国的敌人,我代表共和国消灭你』,实施严酷的恐怖统治。如果法国人选择尊重传统和权威,温和地实现权力转移,或许可以享受一百年的太平岁月。
【三】认同财产权是公民最重要的权利。
在现代社会,如果你没有钱,这不是寸步难行的问题,而是一天也活不下去。财产是自由的依附,如果无法保护公民的财产权,也就意味着公民随时可能丧失全部的自由,沦为奴隶。设想一下监狱里的劳动改造,只有做完了今天的工作,才有饭吃。那么你为了填饱肚子,不得不日复一日地工作,而不能偷懒、休假、或者做任何其他的事。失去了财产权的公民,其处境将与监狱里的苦工并无差别。
]]>在中国大陆办理结婚登记,以前需要花 9 块钱人民币,现在是免费服务。在美国,结婚需要花多少钱呢?这要从美国的结婚流程说起。
按照加利福尼亚州的法律,合法结婚需要至少三个步骤。
第一步,买一张『结婚许可』的表格,填写双方的个人信息。虽然这只是一张普通的纸,但是竟然要收 90 美元的费用!
第二步,在加州境内的某个地方举办婚礼。举办婚礼的当天,需要婚礼主持人和至少一位见证人在结婚许可上签字。不是所有人都有资格作为主持人。合法的主持人要么是教会的神职人员,要么是议员、法官、或政府机构的官员。
第三步,把填写完毕的结婚许可交回档案馆,再花 17 美元买一张『结婚证』。而这个结婚证,不过就是把结婚许可复印到了一张很花哨的纸上。
这三步加起来,最少需要花费 107 美元。你换来的,是一张临时属于你的白纸,和一张永久属于你的彩纸。资本主义真是生财有道啊……
虽然在理论上,你只需要花 107 美元就能搞定这一切。但现实情况是,举办婚礼这一步是开销最大的。我们花了 400 美元雇了一个虽然属于教会但也可以主持世俗婚礼的人,然后又花了同样多的钱请了一位摄影师作为见证人。这些费用加起来,顶得上一台 iPhone 手机了。
办婚礼前,我们向主持人请教哪天是美国人心中的良辰吉日。她说最火爆的日子是 2022 年 2 月 22 日,那一天的婚礼日程,早在数个月之前就被预订一空。这个答案让我挺诧异的。难道说美国人最喜欢中国人最喜欢的两个数字之差?既然超级 2 的这一天不行,为了沾沾这个神奇日子的仙气,我们再加一个 2 总可以了吧。结果,在前往婚礼的路上,铺天盖地都是俄乌爆发战争的新闻。可不能乱加。
婚礼的地点选在了 Carmel 一处安静的公共海滩。等我们抵达的时候,路边停了两辆车,主持人和摄影师已经如约而至。我们先是一手交钱一手签字办完了法律手续,然后步行前往海滩,开始了婚礼必不可少的问答环节。主持人准备的结婚誓词有短中长三个版本,我们选择了中版本,因为这是唯一没有提到上帝的誓词。讲完这些话,摄影师又为我们拍了更多的照片,之后大家就散伙了。赶在市政厅关门之前,我们带着有两个陌生人签字的结婚许可,换来了一张结婚证。
前面说到,结婚证不过是把结婚许可复印到一张彩纸上。因为制作结婚证很容易,这 17 美元不赚白不赚,所以我们可以玩出一些骚操作:
嗯没错,你是可以花钱买别人的结婚证的(尽管在实际操作上会有一些困难)。不过又有谁做这种事?
人类饮酒有几千年的历史,但是人类喝珍珠奶茶是最近二三十年才有的事[1]。我曾想,现在可以很方便地买到珍珠奶茶,这玩意儿可比酒好喝多了,为什么还有那么多人去买又贵又难喝的酒精饮料呢?这种想法真的是 too young too simple. 在超市买过几次酒,每次都很难喝,但这并不代表所有的酒都难喝。
五月的一个周末,我们前往 Napa 游玩。Napa 是著名的葡萄酒产地,这里有成百上千家酒庄。在 Simi 镇的一家酒庄,我们参与了当地最最最普遍的活动——品酒。这种活动没有什么新意,酒庄拿出来的几种酒,味道只能算一般。
最最最普遍的活动
那天参观酒庄的人很少,前后只有两三桌客人。服务员可能闲得没事做,给我们倒完酒之后,自己也在喝。后来他听到我们说那几杯酒很一般,竟然把自己正在喝的酒拿过来给我们倒上了。这是 2003 年出产的酒,和之前品尝的 2018 - 2020 年的完全不是一回事。历经岁月,酒精的气息已经变得微不足道,在舌尖流淌的,是葡萄、木桶、香料融合后的美妙乐章。这里的服务员真是鸡贼,给顾客倒普通的酒,自己偷偷摸摸把好货都私藏了。发现了宝藏的我们,把一瓶 2003 年的红酒收入囊中。
世上确实有美酒,但是这种铱星上古果酒[2],不仅很难买到,而且也消费不起。怎样才能找到既便宜又好喝的酒呢?方法其实很简单:多去高端的欧洲餐厅吃饭,试试他们供应的酒。高端法国餐厅和意大利餐厅,对选什么样的酒颇有讲究,抄他们的名单不易出错。至此以后,我们终于搞清楚该买哪些酒了。
[1] 一种经济学上的解释是,酒类可以放置几十年至上千年,熬制出来的波霸保质期只有四个小时,古代社会的繁荣程度不能支撑波霸这种食材。
[2] 铱星上古果酒是游戏『星露谷物语』中的一种物品。『无星』、『银星』、『金星』和『铱星』用来形容物品的品质。
八月,我前往 Denver 出差,第一次去了真正的机房。在市中心一幢没有标识的大楼里,我们被领进了地下室,跨过厚重的大门,走进了一个令人略感不适的房间。这里有一个个两米多高的架子,每个架子从上到下堆叠着十几台计算机。电线和网线密密麻麻交织在一起,汇聚到架子的顶端,再融入洪流,前往未知的领域。周围满是风扇震耳欲聋的呼啸。从地面的裂隙中,强烈的冷风扑面而来,足以让任何困顿的人神智清醒。
真正的机房
不同的公司在这个机房中租用一小部分空间,交换数据,与其他公司互联互通。每一个小隔间都有单独的铁栅栏与铁门,宣示着自己的主权。进铁门之前,管理员在地上铺了一张粘性纸,脚踩上去之后,会留下清晰的鞋印。我一开始以为,这是为了留下在场证据,如果有人搞破坏,就可以凭鞋印找到犯人。不过当我看到房间里安放的监视器时,我意识到,侦探小说里的做法,不适合二十一世纪,铺这张粘性纸,应该是为了消除鞋底的灰尘吧。这么说来,进机房也许真的需要穿鞋套呢。
至于计算机教室,那种地方是不能叫机房的。但是为什么进计算机教室也要穿鞋套[3]?我想,这或许与泰国的寺庙要脱鞋子一样,是一种特殊的仪式吧。
[3] 高中之后便不再要求穿鞋套进入计算机教室。
买房一年多之后,为了翻修洗手间,我们打算找正规的装修公司开展大工程。这些公司里面有好有坏,能遇上戏精也是挺难得的。
正常的流程是,装修公司上门了解情况,评估项目规模,给出一个报价。我们拿到各家公司的价格后,再选取某一家合作。有一家公司,最初开具的价格没有竞争力,正当我们要送客的时候,戏精开始了他的表演。他给公司的领导打了一个电话,问能不能以员工亲属折扣的名义给这个项目打九折。领导跟他说,你的客户不是亲属,不能给折扣。随后戏精对公司领导开始软磨硬泡,一番你来我往之后,领导终于松口说,如果你今天能把合同签下来,我就允许你用这个折扣。
打九折之后,这家公司一下子变得很有竞争力。戏精不断催促我们说,我花了这么多功夫给你们争取的折扣,只限今天,而且,就算你反悔了,未来三天都可以取消合同,此时不签还待何时?还好当时比较冷静,想办法送走了这位戏精。
事后回忆,他做了一个很不寻常的举动。他在给领导打电话的时候,是故意开着外放的。一般来说,当着顾客的面,和同事交流是要保密的,因为不知道同事会不会成为内鬼,讲一些不该说的话。而戏精故意让我们听到整个对话,这表明整个对话可能是预先约定的,是两个人演的一出好戏。之后我又查了 Yelp 上的评论,果真有人写下了一模一样的剧情。这公司的销售手法,让我十分佩服!
]]>哥德尔证明了,在一个定义了自然数(非负整数)、加法和乘法的数学体系中,假设这个体系没有矛盾,也就是不存在既真又假的数学命题,那么一定存在无法被证明的真命题。这一结论被称作『哥德尔不完备定理』。所谓的不完备,是指无法从几条有限的公理出发,推导出所有真命题。也就是说,我们可能无法从自然数公理出发,证明哥德巴赫猜想。哥德尔不完备定理的证明过程十分巧妙,本文将向读者展示,哥德尔是怎么做到的。
哥德尔研究的对象是自然数、加法和乘法,这些是数学中最基础的数和运算。为了后文论述方便,这里先介绍相关的背景知识。
我们不加定义地使用下面三个含义确定的词:『数』、『零』、『直接后继』。自然数由下面五条公理确立:
- 零是一个数。
- 一个数的直接后继也是一个数。
- 零不是任何一个数的直接后继。
- 不会有两个不同的数有同一个直接后继。
- 如果零有某种性质,并且有此性质的数的直接后继也有这个性质,则所有的数都有此性质。
第五条公理也被称作『数学归纳法』。
从自然数的五条公理不难看出,我们可以使用零和直接后继这两个概念表示所有的自然数。我们把零写作 \(0\),并且把 \(s\) 写在一个数的前面表示这个数的直接后继。于是,我们就得到了一系列自然数 \(0\), \(s0\), \(ss0\), \(sss0\),... 因为写这么多 \(s\) 实在是太麻烦,因此又引入了额外的助记符号,将 \(s0\) 写作 \(1\),\(ss0\) 或者 \(s1\) 写作 \(2\),\(sss0\) 写作 \(3\) 等等,于是就有了我们熟知的自然数。
根据 \(s\) 在数中出现的次数,我们可以定义加法和乘法。例如,我们可以认为,加法是一种把两个数中出现的 \(s\) 拼接起来的数学运算。例如 \(s0 + ss0\),第一个数有一个 \(s\),第二个数有两个 \(s\),拼接起来一共有三个 \(s\),所以 \(s0 + ss0 = sss0\),用助记符号表示就是 \(1 + 2 = 3\)。乘法运算也可以通过类似的方式得到。
定义了加法和乘法之后,人们进一步研究乘法的性质,把大于 1 的自然数分成『质数』与『合数』两类。关于质数与合数,有一个至关重要的结论,会在哥德尔证明中用到:
每个大于 1 的自然数,要么本身就是质数,要么可以写为两个或以上的质数的乘积,而且这些质因子按大小排列之后,写法仅有一种方式。
这个结论被称作『算术基本定理』。
在数学命题中,我们需要使用一些演算符号,来表达数、变量或表达式之间的关系。常用的符号包括,使用波浪号 \(\sim\) 表示『非』,使用类似 V 型的符号 \(\lor\) 表示『或者』,使用 \(\supset\) 表示『如果……那么……』,使用倒写的字母 \(\exists\) 表示『存在』,等等。使用这些符号,可以方便地书写数学命题。例如
\[
(\exists x) (x = s0)
\]
表示数学命题『存在一个数 \(x\) 是 \(0\) 的直接后继』。很显然这是一个真命题。
数学命题的内容可以是灵活多样的。在一个仅仅定义了自然数、加法、乘法的体系中,怎么去研究千变万化的数学命题呢?哥德尔创造性地提出了一种方法,可以将任何一个数、变量、表达式、命题(公式)和证明(公式序列)映射为一个确定的自然数。这个数也被称作上述变量、公式等的『哥德尔数』。这样,我们研究数学命题的性质,就可以转化为研究哥德尔数的性质。我们会在后文给出一个具体的例子。在此之前,让我们来看一下哥德尔数是如何计算的。
首先我们定义下面 12 个基本符号的哥德尔数 [1]
符号 | 意义 | 哥德尔数 |
---|---|---|
\(\sim\) | 非 | 1 |
\(\lor\) | 或 | 2 |
\(\supset\) | 如果……那么…… | 3 |
\(\exists\) | 存在…… | 4 |
\(=\) | 等于 | 5 |
\(0\) | 零 | 6 |
\(s\) | ……的直接后继 | 7 |
\((\) | 标点符号 | 8 |
\()\) | 标点符号 | 9 |
\(,\) | 标点符号 | 10 |
\(+\) | 加 | 11 |
\(\times\) | 乘 | 12 |
在数学命题中会用到数字变量如 \(x\), \(y\), \(z\) 等。数字变量可以用数字如 \(ss0\) 或者表达式如 \(x + y\) 等代入。此外,还会遇到命题变量如 \(p\), \(q\), \(r\) 等可以用命题(公式)代入。对每一个不同的数字变量,赋予一个大于 12 的不同质数作为哥德尔数。类似的,对每一个不同的命题变量,赋予一个大于 12 的不同质数的平方数作为哥德尔数。下面展示了一种可能的赋予哥德尔数的结果
符号 | 哥德尔数 |
---|---|
\(x\) | \(13\) |
\(y\) | \(17\) |
\(z\) | \(19\) |
\(p\) | \(13^2\) |
\(q\) | \(17^2\) |
\(r\) | \(19^2\) |
我们刚刚定义了单个符号的哥德尔数。接下来,怎么计算一个数学命题(公式)的哥德尔数呢?这里来看一个例子。假设我们想要计算的公式是
\[
(\exists x) (x = sy)
\]
这个公式一共用到了 10 个符号,分别是左括号、存在、\(x\)、右括号、左括号、\(x\)、等号、直接后继、\(y\)、右括号。假设数字变量 \(x\) 和 \(y\) 的哥德尔数分别是 13 和 17,那么这 10 个符号对应的哥德尔数分别是 8, 4, 13, 9, 8, 13, 5, 7, 17, 9。我们取从小到大排列的前 10 个质数,每一个质数添加一个对应位置的符号的哥德尔数作为指数,并将他们相乘的结果作为整个公式的哥德尔数。因此,上述公式对应的哥德尔数是
\[
2^8 \times 3^4 \times 5^{13} \times 7^9 \times 11^8 \times 13^{13} \times 17^5 \times 19^7 \times 23^{17} \times 29^9
\]
这是一个非常巨大的数字!
为什么要使用这样的方式计算一个公式的哥德尔数呢?在上一节,我们讲到了算术基本定理。给定一个公式的哥德尔数,我们通过质因数分解,可以唯一确定地还原其公式。也就是说,每一个公式都和某一个自然数构成一一对应的关系。
好几个公式组合起来的序列可以构成对一个命题的证明。例如下面的两个公式
\[
(\exists x) (x = sy)
\]
\[
(\exists x) (x = s0)
\]
第一个公式是一个真命题。将 \(0\) 代入 \(y\) 可以得到第二个公式。因此,这两个公式构成的序列是对第二个公式的证明。我们用类似的方法定义一个证明的哥德尔数。假设第一个公式的哥德尔数是 \(m\),第二个公式的哥德尔数是 \(n\),则这个证明的哥德尔数是
\[
2^m \times 3^n
\]
这个数字虽然过分巨大,但我们不必计算它的值。我们只需要知道,通过这种方法,可以为任意一个变量、命题和证明赋予一个对应的哥德尔数,就可以了。
[1] 在哥德尔的原始证明中仅仅使用了 7 个符号。使用不同的符号集会影响哥德尔数的大小,但是不影响证明过程。
我们来研究下面这个简单的数学命题
\[
\sim (0 = 0)
\]
它表达的意思是 \(0\) 不等于 \(0\)。这是一个假命题。接下来,我们提出第二个数学命题:『公式 \(\sim (0 = 0)\) 的第一个符号是波浪号』。第二个命题谈论的对象是第一个命题,因此它也被称作是『元命题』。不难看出,第二个命题是真的。我们的问题是,如何使用符号把第二个命题的数学公式写出来。
为了解决这个问题,我们来研究第一个命题(公式)的哥德尔数。它的哥德尔数是
\[
2^1 \times 3^8 \times 5^6 \times 7^5 \times 11^6 \times 13^9
\]
我们把这个哥德尔数记作 \(a\)。
一个公式的第一个符号由质因子 2 的指数决定。因为波浪号的哥德尔数是 1,所以我们可以把第二个数学命题转化为一个等价命题:\(2\) 是 \(a\) 的一个因子,但 \(2^2\) 不是。这个等价命题用公式写出来就是
\[
(\exists z) (sss...sss0 = z \times ss0) \land \sim (\exists z) (sss...sss0 = z \times ss0 \times ss0)
\]
其中使用了省略号的数字里 \(s\) 的个数为 \(a\)。这里我们引入了一个新的符号 \(\land\) 表示『并且』。它只是一个助记符号,因为它的意义可以用 \(\sim\) 和 \(\lor\) 表达出来,所以我们不需要为这个新符号分配哥德尔数。
现在,我们意识到,有了哥德尔数之后,构造和分析元命题变成了一件可行的事。我们称『公式 \(\sim (0 = 0)\) 的第一个符号是波浪号』这样的命题为『元命题』,而上面的一大串公式为『形式化的元命题』。
在展示哥德尔的证明过程之前,我们还需要理解两个重要概念。
第一个概念是一个元命题:『具有哥德尔数 \(x\) 的公式序列,是哥德尔数为 \(z\) 的公式的证明』。回想一下之前的例子,具有哥德尔数 \(2^m \times 3^n\) 的公式序列,是哥德尔数为 \(n\) 的公式的证明。这两个哥德尔数之间的算术关系,比判定第一个符号是波浪号要复杂一些,不过我们总是可以使用类似的方法把这个元命题写成形式化的公式。为了表述方便,我们把这个命题简写作 \(dem(x, z)\),并且把对应的形式化的公式写作 \(Dem(x, z)\)。如果 \(dem(x, z)\) 是真命题,那么 \(Dem(x, z)\) 是一个数学定理;如果 \(dem(x, z)\) 是一个假命题,那么 \(\sim Dem(x, z)\) 是一个数学定理。
第二个概念是一个数学运算:『将一个公式的哥德尔数代入该公式的一个变量,并计算代入后的哥德尔数』。举上文用到的一个例子,公式 \((\exists x) (x = sy)\) 的哥德尔数是 \(m\)。这个公式有一个自由变量 \(y\)。如果将数值 \(m\) 代入 \(y\),我们会得到一个不含有自由变量的新公式
\[
(\exists x) (x = sss...sss0)
\]
其中使用了省略号的数字里有 \(m + 1\) 个 \(s\)。我们使用记号 \(sub(m, 17, m)\) 来表示这个新公式的哥德尔数。这个记号可以理解为,在一个哥德尔数为 \(m\) 的公式中,把哥德尔数为 \(17\) 的变量替换成 \(m\),从而得到新公式的哥德尔数。类似的,我们使用 \(Sub(m, 17, m)\) 表示新公式的哥德尔数的形式化写法。事实上,\(Sub(m, 17, m)\) 由超级长的一串 \(s\) 符号和一个 \(0\) 构成。
在刚刚给出的例子中,\(m\) 是一个确切的数值。不过我们也可以在 \(m\) 的位置上使用一个自由变量 \(w\)。这样,我们构造出来了一个自变量为 \(w\),因变量为 \(sub(w, 17, w)\) 的函数。需要注意的是,在代入任何确切的数字之前,数字变量 \(w\) 有一个被赋予的哥德尔数(类似于 \(x\) 的哥德尔数是 13,\(y\) 的哥德尔数是 17),这也就意味着,尽管 \(w\) 是一个变量,形式化的公式 \(Sub(w, 17, w)\) 会有一个确定的哥德尔数。
我们来研究下面的形式化公式
\[
(\exists x) Dem(x, z)
\]
这个公式表达的意思是,哥德尔数为 \(z\) 的公式是可以证明的。
与之相对,形式化公式
\[
\sim (\exists x) Dem(x, z)
\]
的意思是,找不到哥德尔数为 \(z\) 的公式的证明。这里可能会有两种情况。第一种情况是,哥德尔数为 \(z\) 的公式描述的是一个假命题,例如 \(\sim (0 = 0)\),这样当然不可能找到一个证明。第二种情况是,哥德尔数为 \(z\) 的公式描述的是一个真命题,但是这个真命题不可证。
我们应该考察一个怎样的 \(z\) 呢?先观察下面的公式(称为公式 \(F\)):
\[
\sim (\exists x) Dem(x, Sub(w, 17, w))
\]
公式 \(F\) 表述的意思是,找不到哥德尔数为 \(sub(w, 17, w)\) 的公式的证明。不过这里面,因为 \(w\) 是一个自由变量,所以哥德尔数为 \(sub(w, 17, w)\) 的公式不是一个确切的公式,我们也就没有办法判定这个命题的真假。下一步,给 \(w\) 代入什么具体的数值好呢?
由上一节的讨论可知,因为公式 \(Sub(w, 17, w)\) 有一个确定的哥德尔数,所以公式 \(F\) 同样有一个确定的哥德尔数,我们将这个数字记为 \(k\)。
接下来,我们将公式 \(F\) 的哥德尔数 \(k\) 代入公式 \(F\) 的变量 \(w\),得到下面的新公式(称为公式 \(G\)):
\[
\sim (\exists x) Dem(x, Sub(k, 17, k))
\]
公式 \(G\) 的哥德尔数记为 \(g\),请问,\(g\) 的大小是多少呢?
回忆一下从公式 \(F\) 生成公式 \(G\) 的过程,不就是『将一个公式的哥德尔数代入该公式的一个变量,并计算代入后的哥德尔数』吗?所以
\[
g = sub(k, 17, k)
\]
发现了什么?公式 \(G\) 的意思是,找不到哥德尔数为 \(sub(k, 17, k)\) 的公式的证明。因为这个 \(sub(k, 17, k)\) 等于 \(g\),所以这句话可以变成,找不到哥德尔数为 \(g\) 的公式的证明。又因为公式 \(G\) 的哥德尔数就是 \(g\),所以公式 \(G\) 的意思可以进一步表示为:『找不到公式 \(G\) 的证明』。
找不到一个公式的证明,可能因为它本身是假命题,也有可能它是真命题但不可证。那么,公式 \(G\) 是否是一个假命题呢?这里可以用反证法。我们假设公式 \(G\) 是一个假命题,也就是说,能找到公式 \(G\) 的证明。此时,一定找不到公式 \(\sim G\) 的证明。而 \(\sim G\) 代表的意思是『能找到公式 \(G\) 的证明』,找不到能找到公式 \(G\) 的证明,合并起来就是,找不到公式 \(G\) 的证明,而这正是公式 \(G\) 的意思。从假设公式 \(G\) 是假命题出发,结果得出公式 \(G\) 是真命题,引发矛盾。如果自然数公理体系没有矛盾,那么公式 \(G\) 不可能为假命题,它一定为真命题。
哥德尔通过列举了公式 \(G\) 这个例子,证明了自然数公理体系中,存在无法被证明的真命题。
最后,可能会有人问,如果自然数公理体系有矛盾怎么办?如果真的发生这种事,人类的几乎全部数学知识就要推倒重来了。我们还是乐观地相信这种事不会发生比较好。
]]>前年上日语课的时候,老师曾经问我,为什么中国人那么喜欢买房子。我说,按中国的传统,没有房子不能娶老婆。老师听罢,连连说道『大変ですね』(太难了)。结婚是许多人加入买房大军的一个原因,而且今年还有其他的利好消息,比如极低的利息,以及科技公司如火箭般蹿升的股票价格。这些因素叠加在一起,也让今年的抢房大战格外激烈。
我们从一月份开始,几乎每个礼拜都去看房子。我们打算买的 Single Family Home / 一户建住宅,大多数是上世纪五十和六十年代修建的。虽然看了很多房子,但是大多数房子都各有各的问题:
有时候能遇到没有大毛病的房子,不过一套房子通常有十几位买家一同竞争,理性出价很难拔得头筹。多数情况下,我们和第一名的出价会相差一辆奥迪 A8。最接近的一次只差了一辆凯美瑞,但终究还是没能买到。
一转眼就到了五月份。短短四个月的时间,我们见证了房价一路高升。越来越多的区域因为价格过高而被放弃,我们马上就要被挤到偏远的穷人区了。那时我们回顾了一下过去的经历,终于醒悟:时间就是金钱,今天舍不得奥迪 A8,明天就会亏一辆迈凯伦。如果有条件不错的房子,一定要早买,大买,买爆它。
在新思想的带领下,我们没过一两个礼拜就买到了现在这套房子。
房子买下来了,卖家在里面住了几十年,多多少少有一些需要修补的地方。这里的人工费用非常离谱,一位电工的时薪高达 180 美元。为了省钱,许多小型家装工程,就由我们自己动手了。
最早学会的技能是补洞和刷漆。补小洞很简单,只要涂上填充材料,晾干、打磨之后再刷漆就可以了。我们要补的是一个大窟窿,这种情况,需要切割出和窟窿一样大的板材,用填充材料和原先的墙粘在一起,然后再打磨和上漆。切割不规则的形状很难,不如把窟窿凿大一些,变成一个规则的长方形。经过一下午和一晚上的努力,我们最终完美修复了这个窟窿。至于刷漆,虽然前期贴胶带和后期清洁很费时间,但是用滚筒刷墙那叫一个爽啊。
墙上的窟窿
施工进行中
看不出来了吧
我们有几个储藏间没有踢脚线,找了一位师傅询问价格,竟然张口就是 $800。哎,你想换 iPhone 了就直说嘛…… 根据我们的调查,做踢脚线这件事也不难,买来材料之后,用锯子切割成合适的长度,最后使用钉子固定到墙上。这其中切割是最困难的,虽然不至于要精确到一毫米,但是有缝隙总之会难看。由于没有精确测量长度的工具,我不得不准备一块比较长的踢脚线,每次锯掉一点,再与墙的长度进行比较,反复两三次最终确认踢脚线的长度。
哎呀还是太长了
另外,在墙角两块踢脚线的接缝处,其实需要切割出 45 度角,让两块踢脚线结合在一起。这件事用电锯做比较简单,我们的手工锯不方便固定材料,切一个斜角出来实在太难。好在踢脚线的长度非常合适,接缝处的瑕疵并不明显。
在室外,灌溉系统也需要维护。洒水器的喷头使用时间长了,可能会漏水,这不仅导致草地枯黄,而且还危及行人的安全。更换和调节洒水喷头很容易,不过要做好全身淋湿的准备。
崩坏的洒水器
初夏,有朋友送来了几株植物小苗。正巧我们搬进了 Single Family Home,拥有一个广阔的后院,很适合培养这些小苗。考虑到卖家把后院的景观打理得清清楚楚,我们可不愿意为了种菜刨开草地,于是买来了花盆和种植带,将小苗移栽到里面。
尽管有不少人说,番茄是一种对种菜新人很友好的物种,但是它的田间管理还是比较繁琐的。番茄每天都需要浇水,每周疏果并修剪叶片,每三周施肥。番茄的生长速度很快,从幼苗长到一人高,只需要两个月的时间。这期间需要添加支架来维持番茄持续向上生长。
首个成熟的果实
到八月份,我们的番茄依次开始变红。但是为什么它们的个头那么小啊?查阅资料之后得知,番茄需要的土壤比较多,我们的花盆太小,不能提供足够多的营养。好在这番茄虽然小,味道还是不错的。真不愧是新人友好作物,让我们没有百忙一场。
从九月到十一月,我们又尝试种植了西葫芦。西葫芦在开花时会引来很多飞虫,有一些还顺便进了屋子,让我们很是烦恼。花谢之后,果实一点点长大,马上就要收获了,突然有一天上面出现了一道牙印。可恶的松鼠竟然提前品尝了这道美味!
经历了这两次不完美的星露谷之旅,今后我们不打算继续种菜了。自耕农的生活,一方面时间和精力付出甚多,需要每天浇水和照料,另一方面收获的果实又甚少,三个月种出来了六根西葫芦,在超市里卖不到十块钱,而我们购买土壤花去的钱就已经超过这个数了。此生若是还有机会种地,恐怕得买下好几千亩的平原,搞机械化生产才行。此时此刻,在这方寸之间,还是静赏美景就好。
如地毯般的落叶
与国内的加班文化形成鲜明对比,美国有不少公司很在意『工作与生活的平衡』,也就是 work life balance / WLB。Google 一年一度的调查问卷,有一系列问题围绕 WLB 展开,比如在下班之后,或者休假的时候,是否能做到与工作分割开来。据我的观察,多数人在下班之后都不再查看工作邮件,更不会给别人发消息,有什么事情,等到上班了再说。除了一部分与印度合作的同事,晚上也不开会。
另外,在工作氛围上,大家一般会给出足够的宽限期,不会要求别人立即回复。有一次,我上午上传了一段代码,评审的人到了下午还没有看,我就发了个消息催了一下,结果被怼了回来。对方说,若不是紧急情况,请先等 24 小时,如果还没有动静才可以催。后来我也学聪明了,把邮件和聊天的提醒都关了,有时候别人问我一件事,要过几个小时才会看到和回复。即便如此,也没有同事对此表示不满。
日常开发,免不了要做一些琐碎的杂活,例如调整代码格式、检查拼写错误。为了偷懒,大家开发出了一些工具,并把它们集成到开发环境和自动化测试中。使用这些工具提高效率,大多数公司都可以做到。但是 Google 的工具更为强大,做到了一些其他公司很难实现的事情。
举个例子,在 Google 的代码评审网站上,会显示每一行修改的代码是否被测试覆盖到。这个功能十分有用:如果有人添加或修改了很多代码,却没有写测试来覆盖这些代码,就是一个危险的信号,说明这些代码可能会引入新的缺陷。不过,实现这个功能并不容易。
首先,测试工具需要支持各种语言,以及该语言的各种测试框架。其次,测试工具需要集成编译器和版本控制工具,计算出本次代码修改的范围,并且只运行有关的测试。如果每次都运行全部的测试,就会显著增加时间,拖累代码评审和提交的进度。另外,测试工具还要把结果显示到评审网站上。如果一家公司独立采购测试工具、版本控制工具和评审工具,那么实现这几点是非常困难的。
相比之下,Google 的编译工具、测试工具、版本控制、评审网站,都是自己研制的。依靠上下游模块的协作,实现了显示每一行代码的测试覆盖的功能。通过使用完善和统一的开发工具,享受它们带来的种种便利,Google 的工程师可以更专注更高效地工作。
Google 有一套神奇的制度(我不太清楚其他公司是否有类似的制度),那就是工程师提交的代码需要经过一个专门的『代码可读性评审』。新入职的工程师被认为不具备写出高可读性代码的能力。导师在评审过程中,会指出在哪些方面修改代码,可以让它变得更可读。工程师在此期间不断获得指点,水平日益提高,最终毕业,获得一门语言的代码可读性认证。毕业之后,工程师不再需要接受导师的指教就可以提交代码。
这种评审的作用,除了让大家勤于思考,养成好习惯,努力提升代码的可读性,还可以让工程师快速熟悉编程语言的特定用法和 Google 内部代码库,写出规范和高质量的代码。例如,Go 语言中有个 log.Fatalf
函数,可以在程序遇到严重错误时退出。有一次我使用了这个函数,导师建议我改为使用一个 Google 内部特有的 log.Exitf
函数,因为后者不会打印堆栈信息,错误显示更直观简洁。如果没有可读性评审,我完全不会知道 Google 内部代码库中存在这样一个替代者。
可读性评审制度是大多数公司模仿不来的。大多数公司使用许许多多分散的代码仓库。出于保密的需要,你根本无法访问那些与日常工作无关的仓库,更别说对那里的改动指指点点了。而可读性评审的核心,就是让一个与你的日常工作没有交集,不了解你的项目的人,来尝试理解你的代码。如果不熟悉项目的人,也能通过阅读源代码的方式大概摸清作者的意图,那就说明代码的可读性足够好。
可读性评审,一方面有助于提升工程师的能力,另一方面,也在提升人员的流动性。你写的代码,不熟悉的人也能看懂,意味着代码没有秘密可言,你这颗螺丝钉,随时可以被另一颗换掉。我觉得,Google 是有意在维护一个开放和高流动性的工作环境,如果你在一个组不开心了,可以考虑换一个组,而不是必须要跳槽去别的公司。我身边有的同事换过多次组,不知不觉就在 Google 待了十余年。另外,即便有个别员工离开,其他人也可以很快顶上,不会对项目造成巨大的影响。
Google 对外一向宣传产品的安全性很好,不会出现几亿人的数据被骇客偷个精光这种事故。能做到这一点,依赖于 Google 多年来在安全领域持续投入,并且建立起了完善的安全评审体系。安全评审确实有助于打造一款安全的产品,但是也往往会拖慢产品上线的进度。
我先前做过的一个项目,是使用一种 Google 自己研制的 VPN 传输控制指令,远程操控散落在数据中心之外的机器。这个项目的一个环节,是在某个代理服务中添加一条规则,允许特定身份的用户使用特定的 IP 地址段建立网络连接。这个代理服务处于中枢位置,掌握着访问 Google 核心网络的生杀大权。专门有一个组负责维护这个代理服务的规则。如果需要修改规则,那就需要找他们进行安全评审。
由于全公司有各种业务都从这个代理走,他们的日程安排从早到晚都挤得满满当当。运气好的时候可以安排在本周会谈,运气不好的时候则只能安排在下周。第一次开会时,由于他们不是很了解这款 VPN,询问了不少背景情况,等我们讲解完毕,会议的时间已经耗尽,只能下次再约了。后来的几次会议,他们又详细讨论了我们的使用示例,针对一些不寻常的做法讨价还价,最后算是破例批准了我们。从开始接触,到最后允许接入我们的流量,花费了一个半月的时间。
Google 对于安全的极致追求,是它赢得用户信任的重要基础,这也会成为 Google Cloud 区分于其他云计算服务的卖点。然而,安全评审,以及其他缓慢的流程,是否会拖累 Google 推出新服务和新产品的速度呢?
Google 一年有数百亿美元的净利润,一位工程师一年的工资有数十万。但是 Google 却在一些边角的地方,为了区区几百块钱抠门到令人难以置信。
入职之前,我们有机会自行选择工作用的电脑。做软件开发,一般来说 Macbook Pro 是标准配置,没什么好挑的。但 Google 的政策是,新 Macbook Pro 只能选 13 寸的,如果需要 16 寸的,则只能使用别人用过的二手货。这不是因为买不到 16 寸的电脑,而仅仅是因为它为了省钱,不想给你买。
于是我拿到了一个 13 寸屏幕的电脑。13 寸的屏幕很小,代码竖着只能显示三十来行,横着也不过一百个字符左右,无论阅读还是编辑都极为困难。如果没有外接显示器,我是不会用这个小小的屏幕搞开发的。如果配一个 16 寸的电脑,我可以自由移动,在任何我觉得舒服的地方工作。而现在,我必须先找一台显示器,然后接上显示器才能工作。因为公司的办公桌上还没有显示器(进了公司之后一直在家办公),所以我基本上也不去办公室,毕竟去了也不方便工作呀!
根据 Google 的政策,移动设备上不能存储代码。大家的开发环境,要么运行在办公桌下的主机里,要么是运行在 Google Cloud 上的虚拟机。由于疫情的影响,使用 Google Cloud 虚拟机是我唯一的选择。另我大吃一惊的是,分配的虚拟机使用的竟然是传统的机械磁盘(HDD),而没有使用速度更快的固态硬盘(SSD)。我专门咨询过为什么不使用 SSD,得到的回答是,(以 1 TB 为例)SSD 每个月的开销会比 HDD 多大概 100 美元。但是,我每天早上更新代码索引的时候,都要一边等待一边刷手机。那个时候磁盘读写时间非常可怕,甚至会让开发环境完全卡住。请问我刷手机的时间,折算成工资,难道还不到 100 块钱吗?
]]>少数语言,例如 C++ 和 Rust,还支持一种叫『移动』(move)的操作。对象的移动,顾名思义,就是把原对象从计算机内存中的某个地址搬运至新的地址。听上去,移动操作做的事情似乎和复制差不多。那么为什么 C++ 和 Rust 要引入移动操作呢?为什么其他大多数语言都不支持移动操作呢?
在讨论移动操作的用途之前,我们需要先了解一下如何在程序中使用移动操作。
在 C++ 中,当需要在内存中的某个新地址,构造一个和原对象一模一样的新对象的时候,编译器会根据原对象是『左值』(lvalue)还是『右值』(rvalue),来决定是执行复制操作,还是执行移动操作。一个对象是左值还是右值,有一种较为直观(但不十分准确)的判定方法:能被赋值,或者说,能出现在赋值符号 =
左侧的对象是左值,否则就是右值[1]。我们通过几个直观的例子来看一下。
1 | // i 是左值。 |
当 C++ 编译器发现原对象是一个右值对象的引用(rvalue reference),且该对象类型支持移动构造 / 移动赋值时,会执行移动操作,否则会执行复制操作。
1 | std::vector<int> vec = {1, 2, 3}; |
程序员可以使用 std::move
函数将某一个对象的引用,强制转换为一个右值对象的引用,来触发编译器执行移动操作。
1 | std::vector<int> vec4 = {1, 2, 3}; |
移动发生后,被移动的对象(如上例中的 vec4
)通常会变成空值。一般来说,程序不应该继续使用被移动的对象,因为从理论上来说它的生命周期已经结束了(转移到了新的对象身上)。不过 C++ 编译器不会禁止这种行为。此外,如果对象的类型不支持移动构造 / 移动赋值,C++ 总是会执行复制操作。复制操作是移动操作无法进行时的备用方案。
和 C++ 区分左值和右值不同,在 Rust 中使用某个对象构造一个一模一样的新对象时,除了少数基本类型外[2],总是会执行移动操作。如果程序员想要执行复制操作而不是移动操作,需要调用 clone()
方法。
1 | let vec = vec![1, 2, 3]; |
C++ 和 Rust 支持移动操作,主要有三方面的用途。
第一,对于生命周期很短的临时对象,使用移动可以减少复制和销毁操作的次数。
在上文中,我们已经提到了,下面这段代码会执行一个移动操作,将 VecGenerator
函数的返回值移动到变量 vec3 身上。
1 | std::vector<int> VecGenerator() { |
假如 C++ 不支持移动操作,这段代码在实际运行时会发生什么呢?首先,需要构造一个临时对象,用于存储函数 VecGenerator
的返回值。其次,将这个临时对象的值复制到 vec3
身上。最后一步,销毁那个临时对象。实际执行的步骤看上去会像这个样子:
1 | std::vector<int> temp = VecGenerator(); |
支持移动操作之后,原本需要一个构造、一个复制和一个销毁才能搞定的事情,现在一次移动就完成了,提升了程序的运行效率。
第二,对于较大的对象,和复制相比,移动的开销要小很多。
这一点,尤其适用于一些指针或者元数据(metadata)存储在栈(stack)上,而值存储在堆(heap)上的数据类型,比如 C++ 中的 std::vector
和 std::string
。
在 C 和 C++ 中,我们可以使用 sizeof
运算符得到一个对象存储消耗的栈空间的大小。无论是空的 std::vector<int>
对象,还是一个存储了 10000 个整数的 std::vector<int>
对象,在 64 位计算机上 sizeof
运算符返回的结果都是 24:他们在栈上消耗的空间大小是相同的[3]。这两个对象的不同之处,是前者不消耗堆空间,而后者会消耗至少 40000 字节的堆空间。
如果要复制一个空的 vector
对象,我们只需要复制栈上的 24 字节;而如果要复制一个存储了 10000 个整数的 vector
对象,我们不仅要复制栈上的 24 字节,还要复制堆上的 40000 字节。复制一个容器的开销,和这个容器存储的数据大小是成正比的。
但是移动操作不一样。当一个对象被移动时,我们完全可以只移动它在栈上的部分,而不需要移动它在堆上的部分。因此移动一个 vector
对象,无论这个对象有多大,都只涉及栈上的 24 字节。这个开销和复制相比,通常要小得多。
第三,对于一些不能复制,或者需要独占所有权的对象,移动操作提供了转移对象所有权的方法。
某一些类型的对象,例如 C++ 的 std::unique_ptr
,是不允许被复制的。之所以不允许复制 unique_ptr
,是因为它的设计目标,就是让持有 unique_ptr
的函数能独占该对象的所有权。因为 unique_ptr
不能被复制,所以移动操作就成为了转移该对象所有权的唯一方法。
为什么大多数编程语言,例如 Java / Go / Python,都不支持移动操作呢?从上面的例子我们已经看到,编程语言支持移动操作,为了和复制区分,肯定会在语法、语义和实现上变得更加复杂。只有移动操作带来了足够的好处,一门语言才有支持它的动力。对于多数编程语言来说,因为复制的开销不大,而且缺乏独占所有权的概念,没有足够的动机去支持移动操作。
我们在上一节中提到了,复制一个存储了 10000 个整数的 std::vector<int>
是一件开销很大的事情,因为 C++ 会把堆上存储的数据也复制一份,也就是深度复制(deep copy)。
然而,大多数编程语言在复制时,都不会进行深度复制,而是只进行浅层复制(shallow copy)。比如下面这段 Java 代码:
1 | List<Integer> list = Arrays.asList(new Integer[10000]); |
把 list
复制给 list2
的时候,那 10000 个整数并没有被复制,被复制的只是 list
这个 8 字节大的指针而已。复制一个指针的开销是很低的,根本不需要使用移动操作来优化。
此外,C++ 和 Rust 语言不支持自动垃圾回收。这两种语言垃圾回收的普遍做法是,由一个函数独占对象的所有权,在函数执行结束时,自动销毁其独占的对象[4]。对于 Java / Go / Python 这些支持自动垃圾回收的语言来说,无须发展出一套完善的所有权的机制来进行垃圾回收。
总而言之,对于大多数编程语言,移动操作既不能改善运行效率,而且因为根本没有所有权的概念,也不需要通过它来实现所有权的转移。不支持对象的移动操作,也就是理所当然的事情了。
[1] 关于左值和右值的详细定义,参见 C++ 标准。
[2] 对于如下基本类型,Rust 会进行拷贝而不是移动操作:布尔类型(bool
)、字符类型(char
)、整数和浮点数类型、以及由这几种类型组成的元组(tuple
)。
[3] 一个 std::vector
对象在 64 位计算机上总是消耗 24 字节的栈空间。其中有 8 字节用于记录 vector
容器中存储的数据个数(size
),另外 8 字节记录容器的容量(capacity
),还有 8 字节的指针记录实际数据在堆中存放的位置(ptr
)。
[4] 这种资源管理 / 垃圾回收方式有个专门的说法,叫 RAII -- Resource acquisition is initialization。
]]>我在 nginx 的用户访问日志 access.log 里面,经常能观测到一些可疑的请求——在 URL 中嵌入了代码。
1 | 正常的请求 |
和正常的请求相比,这些可疑请求用代码替换掉了原 URL 的一部分:
01
被替换成了 if(now()=sysdate(),sleep(15),0)
。04
被替换成了 1 waitfor delay '0:0:15' --
。注意 URL 中的 %20
是空格的转义字符。所以这两段代码分别有什么含义呢?
经过调查,第一段代码是一个 MySQL 语句。MySQL 支持 if
条件语句,它的格式是
1 | if(condition, execute when true, execute when false) |
now()
和 sysdate()
都是 MySQL 的内置函数,用来获取系统当前时间。一般来说 now()
和 sysdate()
的返回值是相同的,也就是这个 if
语句的条件为真。此时 MySQL 会执行 sleep(15)
,休眠 15 秒之后返回。
第二段代码是 Microsoft SQL Server 语句,其中 waitfor delay '0:0:15'
和 MySQL 的 sleep(15)
的作用完全相同。这里连 if
语句都省去了,直接休眠。
那么,为什么要构造这样的特殊请求,发送到我的服务器上呢?
很多 web 应用依赖于 URL 获取参数(比如用户名)。攻击者可以构造含有特定代码的 URL,如果未经过滤,这些代码可能会在 SQL 的执行阶段被激活,达成特定的目的。这类攻击方式叫做「SQL 注入」。前文描述的两个可疑请求,都是攻击者在尝试 SQL 注入的例子。
攻击者向我的服务器发送上述两种特殊构造的 URL,就是想知道他们的 SQL 代码是否会得到执行。如果服务器在 15 秒之后才做出响应,那么攻击者有相当大的把握认为,嵌入的 SQL 代码被执行了。如果服务器响应的时间短于 15 秒,那么攻击者就会知道,嵌入的 SQL 代码一定没有执行。由于探测 SQL 被执行与否取决于服务器的响应时间,因此说这是一种基于时间的 SQL 注入。一旦攻击者发现 SQL 代码被执行了,就相当于找到了一个 SQL 注入的漏洞。他们之后可以利用这个漏洞控制整个 SQL 服务,盗取数据,或者进一步入侵服务器。
另一方面,我们不难察觉出,这种攻击有相当大的盲目性。
首先,攻击者不确定 URL 的哪个部分可能是 SQL 的参数。在第一个例子中,他们选取了 /2015/01/28/
中的 01
,并认为这个字段可能会被 SQL 用到。这里的 01
指的是一月份,它是否真的会参与 SQL 查询,攻击者除了尝试之外没有别的办法知晓。
其次,攻击者不确定数据库的类型。上文中提及的两个例子,第一个是针对 MySQL 的,第二个是针对 SQL Server 的。事实上我还找到了针对 PostgreSQL 的 OR 438=(SELECT 438 FROM PG_SLEEP(15))
。攻击者需要尝试每一种数据库,来确定服务器使用的数据库类型。
总结一下,这种攻击方式,需要在每一个可能成为 SQL 参数的位置,尝试每一种数据库类型,进行 SQL 注入。我们把盲目注入简称为盲注,所以这种攻击方式,可以称之为「基于时间的 SQL 盲注」。
以我对近期 nginx 用户访问日志的分析,在我的服务器上,此类攻击平均每个星期会发生 2000 次左右。让我感到十分安心的是,这个博客是一个静态站点,没有使用任何 SQL 服务,所以再多这样的攻击也注定是徒劳无益的。
]]>回望 2017 和 2019 的年度规划,不足 50% 的完成率让我深感不安。但是这一切也实属无奈:在这个高度不确定的世界中,每隔几个月,我们都可能因为形势的变化,不得不改变先前的计划。举几个发生过的例子:
考虑到以前的那些经历,在制订 2021 年度规划时,我决定缩减计划的数量,以便预留足够的时间用于计划外的事务。
2021 年的计划,主要包含下面几个部分。
我迄今为止的四年职业生涯,概括起来,是两年的『分布式系统』开发,和两年的『大数据分析』开发。去年底,我加入谷歌,开始涉足第三个领域:『通讯、移动与边缘计算』。以我的粗浅认知,我们要做的事情是,把原本集中于数据中心的资源分散到通讯网络的边缘(入口),以支持物联网和 5G 应用对通讯网络高吞吐量和低延迟的要求。
这份新的工作,从两个方面来看,都是相当大的挑战。首先,我对计算机网络的知识储备没有太多自信,对通讯网络更是知之甚少。作为这个领域的新人,最紧要的,就是迅速掌握相关的核心知识,理解业务模型和上下游关联。其次,通讯网络对延迟要求很高,大概率会使用 C++ 和 Go 等高性能语言。因为我没有这两种语言的工作经历,所以熟练运用新的编程语言也会成为一大难关。
我的目标是,经过一年的工作,能够深入理解新领域的业务模型,完成高质量的系统设计和代码实现。
我曾经思考过,像 Java 这样的古老语言,已经越来越不适应现代应用的开发,会不会在十几年之后逐渐没落?现在想想,我当时大可不必思考这个问题,因为不太可能一辈子只依靠 Java 吃饭。就像现在,我的工作或许要改用 C++ 和 Go 语言了。有两门新的语言需要熟练掌握,这已经够辛苦了,为什么还要再花时间学 Rust 呢?如果你是 编程随想博客 的读者,或许还能记起编程君的回答:学编程语言,最重要的是为了学习语言背后的编程范式,这些编程范式是通用的智慧。Lisp 虽然拥有最多的编程范式,但是没有得到广泛应用,它同时也缺乏多线程等现代语言的要素。C++ 和 Rust 也是编程范式的集大成者,且相较之下,Rust 历史包袱少,具备更多的现代元素,是高性能语言冉冉升起的明日之星。
由于 Rust 语言还没有谷歌的官方支持,因此我不会在工作中使用它。但是,全方位了解这门语言的特性,有助于我认识各类编程范式,在未来写出更优雅的代码。学习 Rust 作为一项长期投资,在事业早期就开始投入,是很有意义的。
我在研究生毕业的时候,由于懒惰,直接签了实习的返聘 offer,跳过了很多人都经历过的、痛苦的找工作过程。时隔四年,当我再一次踏上找工作的旅程,面对几乎是从零开始的 Leetcode,内心的滋味相当复杂。假如下一次,不是我自愿离开公司,而是公司开除了我,我是否能够迅速进入状态开始面试,而不是先花半年的时间刷题?
把 Leetcode 作为日常的一部分,其实是为了追求自由。在公司抛弃我的时候,它是让我保持平心静气的自由;在我遇见了更好的机会时,它既给了我自抬身价的自由,又是先人一步抢摊登陆的自由。
当然,这里的目标并不是做完 Leetcode 的全部题目。我的目标是,即便不能每天都做 Leetcode,也应当每周都做,而且平均下来一天至少一道题。我可能会尝试使用不同的语言解题,权且当作是语言学习的课后练习好了。
关于 2021 年的目标,我先说这么多。还有一些想做却没有列举的事,我们在年终总结见分晓。
]]>答案有一些反直觉。需要担心的,不是人还没死,钱就花完了,而是人死了,钱还没花完。假设你所在的公司稍有一些良心,愿意按个人出资的 50% 向你的 401k 账户中存钱。也就是说,每年 401k 账户中新存入的资金有 $29250。如果资产的平均年投资回报率有 7%,如此坚持 30 年,账户的最终余额是 296 万!
为什么说这个答案反直觉呢?大脑的正常思维是,每年存入 $29250,需要大约 100 年的时间才能达到 296 万。但是实际上,这一过程只需要 30 年。这其中的奥秘,就是复利。在复利的帮助下,资产价值会随着时间流逝指数增长。
怎样才能更好地应用复利,实现财务自由呢?有下面几个要点:
尽早开始
由于资产价值随着时间指数增长,投资年限越长,资产价值的增长越显著。同样是每年存入 $29250 和 7% 的投资回报率,30 年之后的资产价值是 296 万,但如果少存 5 年,25 年之后的资产价值只有 198 万,少了将近 100 万。如果只存了 20 年,资产价值则是 128 万,这些钱够不够退休就是一个未知数了。
投资高回报的项目
平均年投资回报率对最终的资产价值有决定性的影响。我们先前使用 7% 的投资回报率进行计算,是因为美国标普 500 指数的平均年投资回报率是 7%,所以说 7% 是一个比较合理的参考值。如果股市的涨跌起伏让你很不安,结果选择了投资回报率 5% 的债券,那么 30 年之后你只能拿到 204 万,这个和 296 万的差别还是显而易见的。至于银行定期存款 3% 的投资回报率,只能让 30 年后的余额变成 143 万,这恐怕就不太够了。
当然,高回报的项目通常也意味着高风险。在投资的最后几年,可以逐步把资金从高回报的项目转移到低风险的项目,减少收益流失的可能性。
好的雇主很重要
雇主向 401k 账户中存入的钱,会等比放大资产的收益。也就是说,愿意按个人出资的 50% 贡献 401k 账户的雇主,与一毛不拔的雇主相比,最终的资产价值会多 50%。用上面的例子来解释,这就是 200 万和 300 万之间的差别。所以,尽量选一个有良心的雇主吧。
最后,建议你使用这个 Python 复利计算脚本 来实际感受一下复利的魔法。
]]>File.separator
is created to make Java program portable to different platforms, using it to load resource files will just have opposite effect.I was working on a project written in Java. A simplified structure of project was like this:
1 | |--src |
The program tried to load a properties file when it started.
1 | public class Main { |
When I tested this program in a ubuntu machine, it worked perfectly. However when I copied the same jar and ran it in a windows machine, it broke and showed the following error message:
1 | [ERROR] App configuration META-INF\app.properties is not found. |
I unzipped the jar and did see app.properties
file under META-INF
folder. A ls META\app.properties
command also worked. Then why Java couldn't load that resource file?
After hours of research I found the answer in Java docs of CLassLoader:
ClassLoader
public URL getResource(String name)
Finds the resource with the given name.
...
The name of a resource is a '/'-separated path name that identifies the resource.
It means that when specifying the resource file to load, you have to use the forward slash /
to separate the path. Since in windows the value of File.separator
is backward slash \
, Java won't be able to find the resource file.
In conclusion, to make your Java program portable to windows and some other platforms, you should not use File.separator
when loading resource files. Use /
instead.
在美国上市的公司,有义务在每年和每个季度披露公司财报。年度财报和季度财报都可以在 SEC 网站上查询。考虑到大部分行业都具备一定的季节性,分析公司基本面一般会重点考察年度财报。
大多数公司的年度财报通常有 50 页以上,可见其中包含的内容极为丰富。作为投资者,我们最关心的有如下一些信息:
下面以苹果公司 2019 年度的财报为例,提取上面三个维度的信息。注意,本文的分析结果仅供参考,并没有推荐购买相应的股票。
损益表记录了苹果公司 2018 财年和 2019 财年的销售收入和支出(单位:百万美元,下同)。
2019-09-28 | 2018-09-29 | ||
---|---|---|---|
net sales | |||
products | 213,883 | 225,847 | |
services | 46,291 | 39,748 | |
total net sale | 260,174 | 265,595 | |
cost of sales | |||
products | 144,996 | 148,164 | |
services | 16,786 | 15,592 | |
total cost of sale | 161,782 | 163,756 | |
gross margin | 98,392 | 101,839 |
苹果公司把自己的销售收入分成了两个大类:产品收入和服务收入。产品收入主要是指 iPhone, iPad, Mac 等硬件产品的收入。服务收入则包含 AppStore, Apple TV, iCloud 等服务的收入。每一项收入,会对应一个营收成本(例如用于组装 iPhone 的零部件的成本,或者运营 iCloud 服务器的成本)。收入与营收成本之差也就是毛利(gross margin)。毛利与销售收入的比值是毛利率。
从财报中可以得出,苹果公司在 2018 财年和 2019 财年的毛利率分别为 38.3% 和 37.8%。
2019-09-28 | 2018-09-29 | ||
---|---|---|---|
operating expenses | |||
research and development | 16,217 | 14,236 | |
selling, general and administrative | 18,245 | 16,705 | |
total operating expenses | 34,462 | 30,941 | |
operating income | 63,930 | 70,898 | |
other income / (expense) | 1,807 | 2,005 | |
income before taxes | 65,737 | 72,903 | |
income taxes | 10,481 | 13,372 | |
net income | 55,256 | 59,531 |
除了营收成本以外,公司还有许多其他的运营开支,例如研发开支,销售开支,管理开支等。去除了所有这些开支后就得到了营业收入(operating income)。营业收入通常会构成公司税前收入的大部分,它扣除税金后就得到了公司的净收入(net income)。苹果公司在 2019 年的净收入为 552 亿美元。
从损益表中可以计算一个重要的指标——销货报酬率/净利润率(net margin)。净利润率是净收入与销售收入的比值。苹果公司 2019 年的净利润率是 21.2%,也就是销售收入的 21.2% 转化成了纯利润。一般来说,一家好的公司应具备 15% 以上的净利润率。
资产负债表会列出企业从创办开始到现在,累积的资产、负债以及资产净值。它始终保证下面这个等式成立:
1 | 资产(asset) = 负债(liability) + 资产净值(equity) |
下面的表格列举了苹果公司在 2018 财年底和 2019 财年底的流动资产(current assets)。流动资产通常指能在一年时间内转化成现金的资产。它主要包括下面几个类别:
2019-09-28 | 2018-09-29 | ||
---|---|---|---|
current assets | |||
cash and cash equivalents | 48,844 | 25,913 | |
marketable securities | 51,713 | 40,388 | |
accounts receivable | 22,926 | 23,186 | |
inventories | 4,106 | 3,956 | |
vendor non-trade receivable | 22,878 | 25,809 | |
other current assets | 12,352 | 12,087 | |
total current assets | 162,819 | 131,339 |
此外公司还有一部分非流动资产(non-current assets)。这些资产的流动性较差,通常不能在一年时间内转化为现金。非流动资产主要包括:
它们在财报中是这样记录的:
2019-09-28 | 2018-09-29 | ||
---|---|---|---|
non-current assets | |||
marketable securities | 105,341 | 170,799 | |
property, plant and equipment | 37,378 | 41,304 | |
Other non-current assets | 32,978 | 22,283 | |
total non-current assets | 175,697 | 234,386 | |
total assets | 338,516 | 365,725 |
从财报中可以看出,苹果公司在 2019 财年底拥有的资产总值为 3385 亿美元,其中现金资产 488 亿美元。
有了净收入和资产总值,我们可以计算第二个重要的指标——资产收益率(return on assets)。资产收益率是净收入与资产总值的比值。苹果公司在 2019 财年的资产收益率是 16.3%。通常情况下,一家靠谱的公司应具备 6% 以上的资产收益率。
与资产对应的是负债。先来看看财报对负债的记录:
2019-09-28 | 2018-09-29 | ||
---|---|---|---|
current liabilities | |||
accounts payable | 46,236 | 55,888 | |
other current liabilities | 37,720 | 33,327 | |
deferred revenue | 5,522 | 5,966 | |
commercial paper | 5,980 | 11,964 | |
term debt | 10,260 | 8,784 | |
total current liabilities | 105,718 | 115,929 | |
non-current liabilities | |||
term debt | 91,807 | 93,735 | |
other non-current liabilities | 50,503 | 48,914 | |
total non-current liabilities | 142,310 | 142,649 | |
total liabilities | 248,028 | 258,578 |
与资产类似,负债也分为流动负债(current liabilities)和长期债务(non-current liabilities)。流动负债指公司在一年之内需偿还的债务。债务一般包括这么几个类别:
资产与负债的差值是资产净值。它主要由两个部分组成:
其中,公司的留存收益代表着公司从成立以来,挣到手且尚未花出去的钱的总额。
从净收入和资产净值,可以计算第三个重要的指标——净资产收益率/股权回报率(return on equity)。股权回报率是净收入占资产净值的比例。一家好公司应当具备 15% 以上的股权回报率。苹果公司以 61.1% 的股权回报率远远超出了这个标准。
损益表记录了公司运作中应当计入的收益和支出,但由于应计的收益和支出不完全等同于实际的收益和支出,因此损益表不能用来表示公司账户余额的变化。
举个例子,假如苹果公司允许客户以六个月分期付款的方式购买 iPhone。苹果公司将这部 iPhone 售出的时刻,便可以将整个 iPhone 的销售价格计入公司收益,尽管公司要在接下来的六个月时间里分批拿到这笔钱。
现金流量表的作用,就是在损益表的基础上进行修正,展示公司实际账户余额的变化。
现金流的变化主要有三个来源:公司的日常运营、公司的投资活动、公司的金融活动。公司的日常运营是现金流最重要的来源,当公司从日常运营中获取稳定的现金流后,就可以将这笔钱用于投资(例如扩大再生产)和金融活动(回购股票和派发股息)。
2019-09-28 | 2018-09-29 | |
---|---|---|
cash, cash equivalents and restricted cash, beginning balances | 25,913 | 20,289 |
cash generated by operating activities | 69,391 | 77,434 |
cash generated by investing activities | 45,896 | 16,066 |
cash used in financing activities | (90,976) | (87,876) |
cash, cash equivalents and restricted cash, ending balances | 50,224 | 25,913 |
从上面的财报中可以看到,苹果公司在 2019 财年初持有的现金及等价物总额为 259 亿美元。在 2019 财年中,公司日常运营和投资活动分别使得现金增加了 693 亿和 458 亿美元。在支出 909 亿美元用于金融活动后,2019 财年底,苹果公司持有的现金及等价物总额为 502 亿美元。
公司从日常运营中获取的现金,减去投资需要消耗的现金,便是公司的自由现金流(free cash flow)。自由现金流是公司可以任意支配而不影响持续运营的资金。第四个重要的指标是自由现金流与销售收入的比值,5% 以上说明公司具备强有力的获取自由现金流的能力。对于苹果公司来说,这个比值高达 44.3%,因此股票价格一路飞涨也是理所当然的事情了。
]]>Assuming you want to migrate this VM in your local environment:
First you need to export the VM, which contains the VMDK file you will need to transfer to the ESXi host.
A VM can be export as a single OVA file, or two separate files (OVF file + VMDK file). The recommendation is choosing separate files, since only VMDK file is needed in later steps.
When the export finishes, you can use scp
to upload the VMDK file to ESXi datastore.
1 | [root@esxi:/vmfs/volumes/datastore] ls |
The VMDK format between ESXi and VMware Workstation / Fusion is different, so the uploaded file can't be consumed by ESXi directly. There is a command vmkfstools
to convert Workstation and Fusion VMDK into ESXi's format. Let's rename the uploaded VMDK and do the conversion.
1 | [root...] mv Windows-10-64-Enterprise-disk1.vmdk Windows-10-64-Enterprise-disk1.vmdk.fusion |
Now you should have the following files in the datastore:
1 | [root...] ls |
"Windows-10-64-Enterprise-disk1.vmdk.fusion" is the original VMDK you uploaded. "Windows-10-64-Enterprise-disk1.vmdk" and "Windows-10-64-Enterprise-disk1-flat.vmdk" are the new files generated by vmkfstools
command.
Now you can create a new VM with the generated VMDK in the datastore. The UI may be different in your environment.
Click "New Virtual Machine...".
In creation type, select "Create a new virtual machine".
In customize hardware, click the cross at the right of "New Hard disk *" to remove the default disk assigned to this virtual machine.
Then click "ADD NEW DEVICE" -> "Existing Hard Disk", and select the generated VMDK file.
After that, you should be able to see the generated VMDK is listed as "Disk File".
When everything is correct, move forward to create and start VM. Now you should be able to access the VM from Workstation or Fusion even though it is running in ESXi.
从 2017 年 2 月开始,我在 VMware 工作已经有近三年的时间了。前两年在平台组做了一系列项目,大部分和分布式系统治理有关。不能否认在那里学到了很多知识技能,但总有一天我会彻底厌倦看日志修 bug 的循环往复。2019 年 3 月,我换了一个组,投入到了新产品的开发工作中。
进组的时机正逢项目从第一行代码起步。全组像一家创业公司:我们有大致的目标和方向,但所有的宏观设计和微观实现都需要自己摸索。与许多其他创业公司一样,踩各类知名开源软件的坑是大家都会经历的事情。
我们的项目决定使用 druid 作为时间序列数据库。新数据通过 kafka 源源不断进来,经索引后存放在数据库中,这一过程称为数据摄取。官网上的文档和例子还算齐全,示例程序运行起来也一切正常,但换成我们自己的数据后就立刻出现各类错误,以至于不得不深挖源码一探究竟。这么一个看似照葫芦画瓢的任务,最后花费了我三天时间。
之后我们想比较两种不同的数据摄取方式,其中一种依赖于 twitter 某个 scala 开源库的两年前的代码。那些代码过于古老,存在一系列编译和运行的问题。每一次运行,我都觉得,这是最后一个错误,再过片刻就可以把程序跑通了。但每一次,又会出现新的错误。那些莫名其妙的异常信息迷惑着我,但我没有时间探索这个开源库的细枝末节,与其确切地知道为什么会出错,不如凭借直觉把解决的方法猜出来。经历近一星期的煎熬,我终于跑通了流水线,拿到了两种方案的对比结果。我们最后没有选择这套开源库,但我不认为这是一项做完即被丢弃和遗忘的工作。对创业公司而言,知道为什么某种方式不好,并且有理有据地避开它,可能会在未来节约不小的成本 [1]。
从三月到九月,经历过一周连续五天的 war room,经历过打团战从上午十点到晚上九点半,经历过周六周日接连两天来公司点外卖,我们终于发布了产品的第一个版本。正当所有人想好好庆祝一下的时候,我们的客服电话被打爆了。
用户报告的故障主要来源于证书交换环节,这里面有不少可以算是我的“功劳”。因为本人不是安全专家,以前未曾参与过证书管理相关功能的开发,测试工程师也没有注意到许多企业用户可能会使用自己的证书替换系统默认证书,于是出现了产品部署后因无法建立信任,导致数据摄入失败,服务整体停摆的问题。我和几位同事一道,在一个多月的时间里接听了十几通类似的电话,受到影响的客户包括法国第二大银行 Crédit Agricole 等跨国巨头。万幸的是,这次产品质量风波似乎没有影响到来自第三世界国家的客户,让我避免了被派遣并被暴打一顿的下场 [2]。
以前我觉得,产品做成什么样子就应该说成什么样子,不能搞虚假宣传。做了这个项目之后,我现在觉得,宣传的效果、做出来的效果,以及客户最后看到的效果,必定是有差别的。如果兢兢业业工作,让这三者之间的差别不那么大,就可以问心无愧了。
产品宣传效果
内部测试效果
客户实际运行效果
[1] 关于节约成本,我们避开了公司内部某集成测试框架,节省了很多分拣 bug 的时间;我原先的组没有避开公司内部某数据库,导致过长达数月的项目延期。
[2] 据传闻,某韩国公司的中国分部曾因产品质量问题,试图殴打我公司派遣去现场维护的工程师。
我以前是不太拍摄照片的。在亚特兰大,我不过是作业写累了去河边散步,顺手拿出 iPhone 捕捉灿烂的夕阳,或者步道两旁的黄叶。如今,模特的出现,使得摄影的主题发生了巨大变化,同时对摄影效果提出了苛刻的要求。
首先,给这位模特照相的主要目的是供她发 Instagram,所以那些无足痛痒的街边风景全然没有意义,几乎唯一剩下的主题是记录她的生活点滴。
第二,虽说现实中不少网红都是拿手机拍照的,但是为了在气势上不输别人,顺便为了拍出更好的效果,一套加装了闪光灯和人像镜头的专业相机还是需要的。不过由于这套器材过于专业,需要额外花时间学习调试,因此也惹出过一些麻烦。
比如前不久,我和模特下班后去照圣诞节装饰的彩灯。大家都知道这种相片应该怎么照,只要把模特摆在前面,背景是彩灯就可以了。许多人拿出手机之后就像这样咔咔照了,效果也不赖。但是使用专业器材的我,则需要经历一番调试过程才能开工:
不过这个时候的模特,已经冻成了一根冰棍,冷到做不出动作和表情了。
抛开这些因为不熟悉硬件设备而造成的麻烦,拍摄人像的本质,其实是一个 beautify 的过程,也就是照片中的人,应该要比现实中的人更好看一些。不过残酷的事实告诉我们,拍出 uglify 的照片要比 beautify 的照片容易太多了。要不然我也不会每次拍摄都必定听到下面的质问三连:
我有这么矮吗? 我的脸有这么大吗?? 我的脸有这么黑吗???
从前,有女生为了了解直男的内心世界,下载了虎扑;如今,我为了能拍出 beautify 的照片,下载了小红书。小红书上有很多短视频讲人像摄影的技巧,比如应该把人放在哪里,人占的比例有多大,拿什么道具做什么姿势等等。虽然我心里比较抵制这类抖音式科普,但不得不承认它们还是蛮有用的。当然,即便是购入新型器材,运用摄影技巧,配合滤镜和补正,依然不能保证照片的质量。绝大部分的照片,在构图、表情、动作、服装、光线、背景中的某个细节总会有致命伤,这种情况是要作为废片处理的。能够维持五十度按下快门飞传一张到 Instagram 的出片率,就是我明年的目标了。
一个人生活和两个人生活的主要区别是,一个人生活很少自己做饭,但两个人生活则可以选择在家里做饭。这其中的原因是:做两人份食物花费的时间并不会比一人份食物长太多,也就是说,两个人生活时,做饭的平摊成本比较低。考虑到自己做饭比点外卖要健康,再加上不忍心浪费小可爱精湛的厨艺,我们大多数时间都在家里做饭。
我的厨艺基础不牢外加荒废多年,需要很多额外指导。刚开始,小可爱会演示做一道菜的过程,然后两天以后我再去重制这道菜。这样的效果并不太好,因为有时会忘记一些关键的步骤,比如腌制五花肉没有放淀粉,最后的口感会很不一样。后来我们尝试过角色扮演,她演大厨我演帮厨,大厨负责提示下一步操作,帮厨负责具体施工。这样的问题在于,帮厨并不太清楚几步之后会发生什么,食材下锅后会因为找不到调味料而手忙脚乱。到最后,我发现还是打开手机 APP 按照菜谱一步步做效果最好。
在我们制作过的诸多不同类型的菜肴中,最值得拿来炫耀的三种是煎牛排、清蒸鱼和盐酥鸡。
煎牛排想要做得好吃,有几个特别的要点。首先牛排在下锅之前要用锤子敲扁,其次锅中要倒入黄油而不是食用油,最后煎一些蒜末浇在牛排上面会更好吃。关于第一步我曾经有些困惑:在餐厅吃到的牛排有时候还挺厚的,是因为我们技术不行不能煎太厚的牛排,才需要用锤子敲扁么?后来我才知道,这一步的目的是把肉敲散,遇热收缩之后就不会变得很硬了。
清蒸鱼其实是一道没有什么难度的菜,前提是鱼买来的时候就已经处理干净了,否则在厨房还需要进行一些血腥的操作。这道菜我们不常做,主要因为它对食材的要求很高,不新鲜的鱼做出来会很难吃。
我们做的盐酥鸡更像是一道小吃。油炸本身没有什么难的,好吃与否的秘方全在于腌制。腌制过程涉及到的材料有好几种,而我从来都分不清楚淀粉、红薯粉、玉米淀粉和糯米淀粉之间的区别,所以腌制的过程只能由小可爱一人承担。刚炸完的盐酥鸡,无论是配番茄酱还是烧肉酱都非常好吃,不过一定要趁热吃完,再次加热的口感就没那么好了。
虽然今年尝试做过不少菜肴,但离开了菜谱的指引还是无法独立动手。中餐存在着很多套路,一些特定的食材有特定的处理方法。现在通过制作不同的菜肴,我隐约发现了一些规律,但没有人指点说在某种情况下为什么要这么做,因此我也不敢在其他菜肴上贸然尝试。我想,或许学习一些有关烹饪的理论(即便它们都是从实践中总结出来的),会比通过实践积累经验进步更快吧。
]]>update-alternatives
utility, it is easy to choose the default one to use.For example, let's assume you have JDK 11 installed in the machine, and by default java
points to JDK 11:
1 | root@ubuntu:~# java -version |
Now you would like to work on a project that only supports JDK 8. After installed JDK 8 with apt-get install openjdk-8-jdk
, JDK 11 is still the default one. How can we make JDK 8 as the default?
Ubuntu keeps track of the default programs by maintaining a list of symbolic links, under /etc/alternatives
directory. Each entry here is a shortcut points to the actual program, which may have more than one option (i.e. alternatives).
To list all entries of alternatives in the system, use update-alternatives --get-selections
.
1 | root@ubuntu:~# update-alternatives --get-selections |
You can see java
is pointing to actual program at /usr/lib/jvm/java-11-openjdk-amd64/bin/java
, which belongs to JDK 11.
To list all alternatives of java
, use update-alternatives --list java
.
1 | root@ubuntu:~# update-alternatives --list java |
To set java
to use JDK 8 as the default, you can use an interactive command update-alternatives --config java
.
1 | root@ubuntu:~# update-alternatives --config java |
After typing selection number 2
, update-alternatives
will modify the symbolic link to update the default java to use JDK 8.
1 | Press <enter> to keep the current choice[*], or type selection number: 2 |
You can also do this in a script without interaction, if you know the full path of desired default program.
1 | root@ubuntu:~# update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java |
Now you have pointed java
program from JDK 11 to JDK 8 with update-alternatives
command. However, some other programs like javac
and keytool
are still pointing to JDK 11. You will need to repeat this process until all programs have been updated.
一般来说,完成一次计算需要做三件事:
提供输入 -> 执行计算过程 -> 提取输出
对于电子计算机而言,输入和输出都可以表达为一串比特。计算过程,就是处理单元在程序的控制下,通过 AND, OR, NOT 等逻辑门电路修改这些比特的过程。
而量子计算机,输入的是量子比特(qubit)。计算过程是量子门(quantum logic gates)修改量子比特的状态(quantum state)。输出的是量子比特观测的结果。
因此,理解量子计算,需要搞清楚量子比特、量子门和量子观测。考虑到量子计算的物理实现有多种方法,且许多细节均为机密,本文只会阐释量子计算的理论基础。
传统的比特有且只有 0 和 1 两个状态。如果存在一个比特,那么它在某时刻的状态必须是 0 和 1 之间的一个。量子比特与传统的比特不同,它的状态在理论上用一个长度为 2 的列向量表示。首先我们定义如下两个量子比特 \(| 0 \rangle\) 和 \(| 1 \rangle\).
\[
| 0 \rangle = \left[
\begin{array}{c}
1 \\
0 \end{array}
\right]
\]
\[
| 1 \rangle = \left[
\begin{array}{c}
0 \\
1 \end{array}
\right]
\]
量子比特 \(| 0 \rangle\) 和 \(| 1 \rangle\) 是量子比特的计算基态(computational basis state)。或者说,它们是量子态空间的一组基。
任何一个量子比特 \(| e \rangle\) 都可以表达为 \(\alpha| 0 \rangle + \beta| 1 \rangle\) 的形式,其中 \(\alpha\) 和 \(\beta\) 是两个复数,且它们的模的平方和等于 1,也就是
\[
| \alpha |^2 + | \beta |^2 = 1
\]
上述的限制又称为归一化条件。
比如说,\(0.6 | 0 \rangle + 0.8 | 1 \rangle\) 是一个量子比特,\(\frac{1-i}{2} | 0 \rangle + \frac{1+i}{2} | 1 \rangle\) 也是一个量子比特,但 \((1-i) | 0 \rangle + (1+i) | 1 \rangle\) 就不是一个量子比特,因为它不满足归一化条件。按照数乘矩阵的规则,上面两个量子比特又可以写作
\[
\left[
\begin{array}{c}
0.6 \\
0.8 \end{array}
\right]
\]
和
\[
\left[
\begin{array}{c}
\frac{1-i}{2} \\
\frac{1+i}{2} \end{array}
\right]
\]
直观上讲,量子比特可以视为传统比特的一种叠加态(superposition)。比如说,\(0.6 | 0 \rangle + 0.8 | 1 \rangle\) 可以视为 0.36 (\(0.6^2\)) 个比特 0 和 0.64 (\(0.8^2\)) 个比特 1 的一种叠加状态。
正如数字电路中的逻辑门可以修改比特的状态一样,量子门可以修改量子比特的状态。量子门既可以只有一个输入和一个输出(转变单个量子的状态),也可以具有多个输入和多个输出(转变多个量子的状态)。输入和输出的数目应当相等,也就是说不可以吞噬量子。下面介绍两个单输入输出的量子门和一个多输入输出的量子门。
NOT 门作用于单个量子比特,它可以交换两个基向量的系数:
\[
NOT(\alpha| 0 \rangle + \beta| 1 \rangle) = \alpha| 1 \rangle + \beta| 0 \rangle
\]
量子的 NOT 门是数字电路中 NOT 门的一个扩展。举一个直观的例子,假如原先的量子态是 0.36 个比特 0 和 0.64 个比特 1 的叠加态,那么经过 NOT 门之后,就变成了 0.64 个比特 0 和 0.36 个比特 1 的叠加态。
单输入输出的量子门可以使用一个 \(2 \times 2\) 的矩阵来表示。一个量子经过量子门之后的状态,由该量子状态向量左乘量子门矩阵的值决定。NOT 门对应的量子门矩阵为
\[
X = \left[
\begin{array}{cc}
0 & 1 \\
1 & 0 \end{array}
\right]
\]
因此,某个量子比特经过 NOT 门的结果为
\[
X \left[
\begin{array}{c}
\alpha \\
\beta \end{array}
\right]
=
\left[
\begin{array}{cc}
0 & 1 \\
1 & 0 \end{array}
\right]
\left[
\begin{array}{c}
\alpha \\
\beta \end{array}
\right]
=
\left[
\begin{array}{c}
\beta \\
\alpha \end{array}
\right]
\]
Hadamard 门同样作用于单个量子比特,它可以依照系数分解现有的量子态:
\[
H(\alpha| 0 \rangle + \beta| 1 \rangle) = \frac{\alpha + \beta}{\sqrt{2}} | 0 \rangle + \frac{\alpha - \beta}{\sqrt{2}} | 1 \rangle
\]
用矩阵来表示就是
\[
H = \frac{\sqrt{2}}{2} \left[
\begin{array}{cc}
1 & 1 \\
1 & -1 \end{array}
\right]
\]
虽然 Hadamard 门和数字电路中的 AND 和 OR 门并无直接的关联,但它在不少量子计算算法中有重要的应用。有兴趣的读者可以自行证明,连续使用两次 Hadamard 门之后,量子会回到原先的状态——这个行为和 NOT 门是一致的。
单输入输出的量子门可以有无穷多种,只要满足量子比特状态向量左乘量子门矩阵的结果依然满足量子比特的归一化条件即可。在这里我们不加证明地给出如下定理:
某个 \(2 \times 2\) 的复矩阵能作为量子门的一个表示,当且仅当其共轭矩阵等于其逆矩阵。
在计算机程序中,充满着条件判断语句:如果怎样,那么做什么,否则做别的什么。在量子计算中,我们也期望一个量子比特的状态可以因为另一个量子比特而改变,这就需要多输入输出的量子门。下面要介绍的是受控 NOT 门(CNOT 门)。它具有两个输入和两个输出。如果把输入和输出当作一个整体来看,这个状态可以用 \(\alpha| 00 \rangle + \beta| 01 \rangle + \gamma| 10 \rangle + \theta| 11 \rangle\) 来表示。其中 \(| 00 \rangle\), \(| 01 \rangle\), \(| 10 \rangle\), \(| 11 \rangle\) 是长度为 4 的列向量,它们由 \(| 0 \rangle\) 和 \(| 1 \rangle\) 拼接而成。
这个整体也需要满足归一化条件,即
\[
| \alpha |^2 + | \beta |^2 + | \gamma |^2 + | \theta |^2 = 1
\]
CNOT 门中,第一个输入是比特 0 的部分,会维持第二个输入的状态;而第一个输入是比特 1 的部分,会以 NOT 门的方式作用于第二个输入的状态。第一个输入的量子比特会原样输出,而第二个输入的量子比特的状态由上述的叠加态决定。用数学公式来表达就是
\[
CNOT(\alpha| 00 \rangle + \beta| 01 \rangle + \gamma| 10 \rangle + \theta| 11 \rangle) = \alpha| 00 \rangle + \beta| 01 \rangle + \gamma| 11 \rangle + \theta| 10 \rangle
\]
从上面关于量子门的介绍可以看到,一个量子比特处于两个量子态 \(| 0 \rangle\) 和 \(| 1 \rangle\) 的叠加态;两个量子比特组合成的整体,处于四个量子态 \(| 00 \rangle\), \(| 01 \rangle\), \(| 10 \rangle\) 和 \(| 11 \rangle\) 的叠加态。以此类推,\(n\) 个量子比特处于 \(2^n\) 个量子态的叠加态,这相对于 \(n\) 个传统的比特却只有一个固定的状态相比,是巨大的优势。然而物理定律也有它的限制——确切地知道一个量子比特的状态的方法是进行一次观测,但观测会使得叠加态坍缩,变成一个确定的状态。一个量子比特在观测之后得到的信息是传统的比特,它的值只能是 0 和 1 之间的一个。一个叠加态会塌缩成 0 或者 1,是由叠加态中的系数 \(\alpha\) 和 \(\beta\) 决定的。这个叠加态塌缩成 0 或 1 的概率分别为 \(| \alpha |^2\) 和 \(| \beta |^2\)。类似的,一个双量子比特的系统,其观测结果为 00, 01, 10, 11 的概率分别是 \(| \alpha |^2\), \(| \beta |^2\), \(| \gamma |^2\) 和 \(| \theta |^2\).
这样的物理定律导致量子计算的结果是不确定的,它有一定的概率输出一个错误的结果。在实际应用中需要额外的手段来验证输出的正确性。量子计算使用的算法,如果能在观测的上一步尽可能地减少量子比特的叠加态,就能以大概率得到正确的结果。
我们把上面提及的几个部分组合起来,所谓量子计算,无非就是:
值得说明的是,在现阶段,实现每一个量子门,都需要人工操纵物理实验设备,写量子计算的程序还远没有敲字符那么简单。
物理学家尚未证明量子计算的界限在哪里,不知道它是否可以用来模拟任何物理过程。从 1980 年代到现在,量子计算应用于实践似乎仍遥遥无期,但它的某些应用,如解算蛋白质结构,以及破解不对称加密密钥,已经引发了不少关注。我们无法知道它的大规模普及是否会像电子计算机一样再次改变人类社会。
]]>所谓相机的全画幅和残画幅,是指相机感光元件的大小。全画幅使用 36 x 24 mm 的传感器,常见的残画幅 APS-C 使用 23.6 x 15.7 mm (佳能是 22.2 x 14.8 mm)的传感器。全画幅与某种画幅的传感器的长度之比,称为裁切系数(crop factor)。很容易算出,全画幅的裁切系数为 1,普通 APS-C 画幅的裁切系数为 1.5,对于佳能来说这个值是 1.6。
常用的相机画幅
知道裁切系数有什么用呢?由于画幅不同,为了取得相同的摄影效果,在不同相机上需要设定的焦距、光圈大小和感光度并不一样。有了裁切系数,我们就可以在不同画幅间进行转换:
使用残画幅,在已知某张照片焦距、光圈大小和感光度的条件下,若改为全画幅相机,需使用的焦距、光圈大小和感光度。
或者反过来:
使用全画幅,在已知某张照片焦距、光圈大小和感光度的条件下,若改为残画幅相机,需使用的焦距、光圈大小和感光度。
计算的公式是这样的:
\[
残画幅焦距 = \frac{全画幅焦距}{裁切系数}
\]
\[
残画幅光圈值 = \frac{全画幅光圈值}{裁切系数}
\]
\[
残画幅感光度 = \frac{全画幅感光度}{裁切系数^2}
\]
下面来举个例子。假设使用全画幅相机拍摄了一张人物肖像,焦距为 85 毫米,光圈值为 f/1.4,感光度为 100。请问使用尼康的 APS-C 画幅相机,为了达到相同的摄影效果,应该使用怎样的参数呢?
答案是,需要 55 毫米的焦距,f/0.9 的光圈,感光度为 45。
看到这里你可能笑了,上哪里去找符合这种要求的镜头呢?相机也不支持 50 以下的感光度吧?正是因为这样的镜头和相机不存在,所以全画幅和残画幅之间才形成了一条不可逾越的鸿沟:由于物理的限制,残画幅相机无法拍出全画幅相机的广角、大光圈、以及低感光度效果。我知道 APS-C 画幅的相机更加小巧,价格也更加便宜,但是考虑到未来对摄影的需要,还请下单之前考虑一下,你是否需要一台全画幅的相机?
]]>宗教可以认为是一种高度严密和仪式化的信仰。至于信仰,按照我的理解,便是那些无需证明就自动为真的命题。基督教作为一种宗教,它的信众会相信下面几个基本命题:
这些命题都是无法证明的。传道者正确的做法,是通过举例,让听众觉得这些命题应当是真的。当听众不再质疑命题的真伪时,他们就完成了从福音听众到基督徒的转变。
下面我来逐条解释一下这几个命题。
首先,世上存在着一位神,他创造了万事万物。我想你肯定知道是人类的劳动制造出了你用的这部手机、你住的这幢房子,以及你生活中接触到的大多数生活用品。但是当你仰望天空的时候,是否想过,是谁创造了太阳、月亮、地球和满天繁星?有可能,这样一位创造者并不存在,但是人的心理会抗拒这样的答案,因为人相信凡事必有因果,我们的宇宙需要一个最初的因——宇宙是被一位全能且极富智慧的创造者设计出来的。这位创造者,我们尊称其为神。
人生活在地球上,与其他生物地位不同。狮子贵为百兽之王,充其量不过是动物园圈养的野兽,大象虽然能活两百年,但也逃脱不了被人骑的命运。既然狮子、大象和人拥有同样的造物主,那么一定是因为神的特殊安排——依照自己的形象创造了人——才使得人拥有了地球上独一无二的地位。
神依照自己的形象创造了人之后,同样以自己的德行要求人,无法达到要求的人会被神认为是有罪的。这是一种怎样的要求呢?粗略来说,任何不完美都是有罪的。并不是说,张华有罪,因为他在考试时作弊,而是,张华有罪,因为他在考试中未能获得满分。粗鲁的言行,嫉妒心,这都是神认为有罪的表现,因为这些行为缺损了神的荣耀——作为神的形象的化身,人是不应当做这些事情的。
基督教认为人死后有来世,而且在进入来世之前,每个人都会经历审判。所有有罪的人都会下地狱,与神永远地分别,而那些没有罪的人则可以上天堂,与神永存。按照上一段所述,所有人都是有罪的,因此所有人都会下地狱,这个结局太惨了,于是需要修改游戏规则,让一部分人可以上天堂。
这类规则可能有很多种。它可以是非常简单的,比如随机掷骰子,谁是六点谁上天堂;也可以是非常不公平的,比如财富大于一百万美元的可以去天堂;或者是看上去很美好但是很难衡量的,比如做的善事比恶事多的人可以进天堂。神想了一个更聪明的办法,他派自己的儿子基督耶稣来到人间。耶稣被钉到了十字架上,流血而死。耶稣以自己的牺牲,代替了人犯下种种罪行所应受的惩罚,使人可以上天堂与神同在。而这一切的前提,是人虔诚地相信神和基督耶稣的故事。
综上所述,基督教告诉人们一个简单的道理(即“福音”):要想死后进天堂,信基督是唯一的途径。众多的基督徒出于善意和怜悯之心,不愿看到亲人和朋友最后下地狱,因此会向他们传播福音。现代的基督教正是这样发展壮大的。
]]>在这样一个架空的世界里,西比拉系统(Sibyl System)通过测量人的犯罪系数来预测犯罪行为,并允许刑警对犯罪系数高的人实施惩戒。在这个世界,何为正义,是由西比拉决定的。那么正义到底是什么呢?正义与非正义又是如何界定的呢?假设火车会轧死铁轨上的五个孩子,但是你只要把一个胖子从桥上推下去就能救那五个孩子的命,这种行为是正义的么?因为判定正义,是西比拉拥有的特权,所以西比拉可以使用任何逻辑上自洽的判断标准。比如说,比起不作为,把胖子推到桥下的人拥有更高的犯罪系数,因此用一个胖子的命救五个孩子的命是非正义的。更广义的说,正义与非正义并不存在一个绝对的界定,它只要能被普罗大众接受即可。
《心理测量值》剧情的真正转折点是常守朱监视官意识到了免罪体质者的存在——这类特殊的人可以通过意念操控自己的犯罪系数,是西比拉系统无法解决的漏洞之一。由于社会的和平安宁完全依赖于西比拉的正确运转,如果这些无法处理的情形被公之于众,无疑会极大地动摇统治根基。西比拉既然以伟大、光荣、正确来标榜自己,也就意味着从外界看来,它必须是完美无缺的,不能做出任何错误的决定。而一旦它的错误或者过失被广而告之,从未怀疑过它的臣民们就会陷入混乱之中,这也是西比拉要竭尽全力隐藏免罪体质者的原因。假如西比拉对外的宣传没有那么完美——它仅仅是一个参考,不应作为安保和惩戒的唯一依据——那么臣民们就会发展出一套自己的应对策略。大厦即将倾覆之时,也就不会是一片残垣断瓦的景象了。
即便西比拉系统是完美的,它可以精确测量每个人的犯罪系数,使得免罪体质者无法逍遥法外,那么是否可以裁撤掉公安局的刑警队,改为全部使用自动系统(drone)上街巡逻,抓捕或击杀犯罪系数过高的人呢?很可惜,答案依然是否定的。维护治安和控制犯罪的工作是在和人打交道,让刑警拿着 dominator 来处置罪犯,就默许了执行者对此种场景可以有自己的理解和判断,把最后对人的处置权交给了人类自己。可能是出于便于追责的原因,我们总是需要一个负责人,而不是一台负责机器,或者一套负责算法。机器和算法是没有能力负责的,只有人才可以,这也算得上是人的特权了。
未来的理想社会是怎样的呢?是剧情中那种矛盾最小化的社会么?这样的社会通过监控犯罪系数,在犯罪发生之前就予以消灭,以维持和谐的形象。但是,这种做法其实有极大的危害。
这是因为,监控并非全知全能,世界上总会存在监控无法覆盖的地方。这里既包括出于安全考虑而故意屏蔽网络信号的无人机装配工厂,也有由于历史原因遗留的废弃地下空间,当然也不可能在每家每户安装闭路影像。这些角落,依旧可以不受监控地实施各种犯罪行为。监控把这个社会隔离开来,变成一个明社会和一个暗社会。在明社会里,一切看上去都是和谐安宁的景象。居民可以安心在公园里散步,在商城里购物,在街边的咖啡厅闲聊,丝毫不需担心扒手盗走钱包,或者飞车贼拦路抢劫。但是在暗社会里,人们因为过高的犯罪系数无法进入明社会,因为他们会被立即带走接受“治疗”。被束缚了自由的暗社会居民,从此只能在暗社会出没。受束缚和压抑的时间越久,心灵的面目就变得越发狰狞。因此,这种矛盾最小化的社会并没有最小化整体的矛盾,而只是最小化了某些地区的矛盾,也就是把矛盾从一些地方转移到了另一些地方。明社会的居民长期没有得到磨练,已经完全不知如何处置身旁的犯罪时,监控系统的一次失灵,就能引发一场整个暗社会对明社会的吞噬。
本剧的大反派槙岛圣护是一个很厉害的人,他兼具免罪体质和领导才华。他幼年时与同龄人格格不入的创伤,与他成年之后对人性孜孜不倦的探求(多是引诱和观察他人犯罪),催生出他对西比拉的厌恶。关于这样一个人,我既有欣赏的一面,也有鄙视的一面。
我欣赏他不被强权收买的态度。在被捕之后,西比拉想要招安他。这看上去是一个双赢的局面:西比拉的运作需要思维特立独行的人,而圣护也可以借此获得读取每个人意志的机会,成为神明一样的存在。然而看到了西比拉系统真相的圣护拒绝了这次机会,开始了逃亡之旅。圣护崇尚的信念,是鼓励每个人自己做出选择,自己承担风险,自己寻求刺激,而不是由一个体制去安排每个人的生活。如果为西比拉工作,无疑是背弃信念,开始对千千万万人的生活指手画脚。他知道蓝色药丸通往着无上的权威,而红色药丸终究难逃一死,但也几乎没有犹豫地拿走了红色药丸。
同时,我鄙视他不顾后果的破坏行动。只能破坏体制却无力构建体制的人,是无耻的。圣护认为西比拉系统不应该存在,犯罪系数是整个社会的枷锁,他想要通过制造饥荒这样的激进措施去破坏体制。但是,他也从来没有说过没有西比拉的世界会是什么样子。人们确实获取了宝贵的自由,但是然后呢?是谁通过什么重新建立起社会必要的秩序呢?这些后续,他怕是没有想过,可能也不太关心。虽然,在前一个体制倒下之后,下一个未经设计的体制会自然涌现出来,但是历史告诉我们,这样的体制往往会比那个已经倒塌的体制更差。同样是获知了西比拉系统的真相,常守朱监视官的表现就成熟得多:她在感性上极为厌恶,却又在理性上无比赞同。我想她可能是意识到了,现阶段没有什么比西比拉系统更完备的方法来管理整个社会吧。
此刻,我无从知道,对常守朱而言,西比拉的存在是否已经成为了她信念的一部分。西比拉有存在的理由,也有不应存在的理由。当它的存在或者不应存在,变成了信念的一部分,性质就发生了变化。所有的有利证据会得到强化,所有的不利证据会被大脑销毁。信念是不需要证明的,恰恰想法,它是公理,用来指导其他的证明。正因为信念有太过重要的意义,它也理所应当成为了体制重点管控的对象。如同政府会向宪法学教授灌输课堂应当宣扬本国政治制度的信念一样,西比拉系统也会尽力诱导常守朱去相信它是社会统治不可或缺的一部分。但是站在个人的角度,由于信念的存在会阻碍理性的思考,所以尽量少地持有信念才是明智的选择。
]]>因为,设计和实现的信息密度是不一样的,这两种工作的含金量是有差别的。
具体的代码实现属于密度低的信息:实现中有很多细节的魔鬼,与软件的功能并无太大关系,比如为了多线程协调工作而引入某种同步机制。软件的功能设计则属于密度高的信息:你可以用几张图清楚地表达出某个功能的作用原理,却不需要花好多个小时研读它背后的代码。功能设计是代码实现的抽象,正因为它,信息得到了提炼,含金量也因此提高。
从职业发展的角度来讲,程序员无论是成长为技术骨干,还是转型做项目经理,都需要在公司建立越来越大的影响力。影响力在很大程度上是与规模等价的——你带领或者管理的软件规模越大,你的影响力就越大。既然如此,就更应当把精力集中到信息密度更高的地方。当你站到了更高的抽象层次上,大脑会允许你处理更大规模的业务,这就是职业发展迈向下一个台阶的起点。
回到上文提出的例子,自己两个小时的工作比用四个小时教别人工作的效果要好,那为什么还要把任务交给别人来做呢?这是因为,代码并不是写完就万事大吉了,它会不断地被人阅读,被人提问,被人修改。程序员在维护一段代码上花费的时间,会远远超过编写它的时间。你编写并维护这段代码,就意味着今后你需要长期在大脑中存储和运算这些密度较低的信息——具体实现的细节。这些信息会与你的设计工作抢占资源,损耗精力,阻碍你获取更大的影响力。
综上所述,如果有条件,程序员应当多多参与设计工作,同时适当把具体的编码活动外包出去。
]]>判断一张照片的好坏,最基本的维度是曝光。如果照片过亮或是过暗,以至于被拍摄的对象看不清楚,是几乎不可能成为一张好照片的。从相机的角度出发,控制曝光的手段只有三个:快门速度、光圈大小、感光度。快门速度越快、光圈越小、感光度越低,则曝光水平越低;反之,快门速度越慢、光圈越大、感光度越高,则曝光水平越高。
在自动模式(AUTO)下,相机会根据被拍摄的物体自动选择快门速度、光圈大小和感光度。我开始学习摄影的时候,听说大多数场景应当使用光圈优先模式(A),因此我就跳过自动模式,直接从光圈优先模式起步了。这种模式理论上讲是由摄影师来控制光圈的大小,然后相机根据曝光的需要自动选取快门速度和感光度。不过可能是因为我某天手滑取消了自动感光度,用一个较低的固定值 100 代替,所以相机真正能调整的就只剩下快门速度了。在快门速度低于 1/30 秒的时候,即便有光学防抖,放大后的照片也会变成模糊的影像。
在光线昏暗的地方更容易出现照片模糊的问题,对付它的方法有如下几种:
首先,强烈建议开启自动感光度。你可以为自动感光度设置一个合理的上界,比如 3200 或者 6400,这样噪点不至于非常难看。
第二,增大光圈,让更多的光可以进入镜头。在预览时可以看到相机选定的快门速度,这个值不能大于 1/30 秒,最好不大于 1/60 秒。更高的快门速度可以消除手抖对照片的影响。如果最大的光圈依然无法满足需要,你可能需要一个光圈更大的镜头,或者使用闪光灯等方式提供额外的照明。
最后,使用三脚架可以彻底解决静止物体的模糊问题。
]]>