在我人生的旅途中,一开始我并不喜欢哲学,因为中国将学校课程划分为文科和理科之后,我自然而然地开始站队理科,讨厌文科。然而机缘巧合之下我在一档财经节目上看到了台湾大学哲学系苑举正教授使用哲学对财经新闻的解释,开始对哲学产生了兴趣,遂开始了哲学的学习。
从人类文明发源至今,有很多哲学思想被创造而流传于世。在这里我无法一一举例,仅就一些自己了解过的,能够运用在工作中的哲学思想作一些讲解。非哲学科班出身,若有纰漏请谅解。
存在主义
Swift 中有一种容器,叫「存在主义容器 (existential container)」,其对开发者并不可见,就跟 NSGlobalBlock, NSMallocBlock 和 NSStackBlock 一样——除非你去拆编译器,不然很难发现这玩意儿的存在。
在下列代码中,编译后的 Swift 代码就会为变量 foo 和 bar 构造出存在主义容器用以存储其背后的类型实例:
protocol Foo {
var foo: Int { get }
}
protocol Bar: Foo {
var bar: Int { get }
}
struct Fee: Bar {
var foo: Int
var bar: Int
}
let fee = Fee(foo: 0, bar: 0)
let foo: Foo = fee
let bar: Bar = foo as! Bar
我们可以看到,如果我们按照 Foo 的成员在栈上分配内存,那么 fee 被赋值到 foo 上的时候,必然会因为其有两个 Int 成员,而 Foo 不存在实例变量成员,最后产生储存空间上分配的矛盾。在 C++ 中有一个类似的问题叫 object slicing 和这种情况就非常类似,Effective C++ 直截列出了这种行为属于应当避免的行为。Swift 中则使用了「存在主义容器」来解决这种空间分配上的矛盾——在栈上构建一个统一大小的小缓冲区来存放小内存布局的类型实例,在类型实例的内存布局非常大的时候换成在这个缓冲区内存储一个引用再把实例数据扔到堆上;而针对不同的类型,同时也会在这个缓冲区后构建一个用来做函数派发的虚表和 Swift 中比较特殊的 value witness table 和 protocol witness table。
Swift 的「存在主义容器」意味着不论里面储存的数据是什么,当使用这个容器的代码看到这个容器中存放的是什么类型的数据时候,那么这个容器中存放的就是什么类型的数据—— 变量 foo 绑定的数据储存在「存在主义容器」中,使用他的代码 foo 这个变量是 Foo 类型的,那么 foo 看到这个容器内部的数据就是 Foo 类型的,缓冲区后的虚表存放的就是 Foo 的虚表;变量 bar 绑定的数据储存在「存在主义容器」中,使用他的代码 bar 这个变量是 Bar 类型的,那么 foo 看到这个容器内部的数据就是 Bar 类型的,缓冲区后的虚表存放的就是 Bar 的虚表。
这就是存在主义,强调应用存在主义的一方不使用理性思考,而使用自己的主观经验作判断。换到 Swift 代码中就是绑定到「存在主义容器」的变量使用变量自身被指定的类型来看待容器内部数据的类型——当然,类型强转还是会做运行时检查。
另外,在团队作业中,我也开始学会用存在主义来审视队友的作业。
很多时候我们都要图快而写不那么漂亮的代码,我也不例外。这种代码往后会被称为技术债。其实用经济学的眼光看「技术债」,那么我觉得和「资产负债」一样,其实「技术债」是中性词。如果我们站在批判的角度,我们可能会选择从队友的技能水平、工作态度中拿出一项来苛责他。然而从存在主义的角度出发——当一个项目组成员的工作在那里,并且最后项目成功了的时候,那么这个项目组成员的工作便是导致项目成功的工作——在这里我学会了拒绝理性思考,因为人类所谓的理性思考基本都基于「分析思维」——即将事物的发展总结出几个条件,然后像中学做单一变量实验一样分析一个条件放大或者缩小后的情况。然而人类的思维永远不可能洞悉事物发展的一切细节,将事物视为一个不可拆分的整体来进行判断不失为针对复杂过程进行案例学习的一个有效手段。在这里,使用「分析思维」对单个条件进行放大缩小,再去推演事态可能的发展在我看来反而显得非常的历史虚无主义。而且我觉得对一个人的表现持「存在主义」的态度是一个组织论功行赏的基本准则。
亚里士多德的本质主义
本质,英文是 essence。其词根 esse 来自拉丁文,在拉丁文中是英语 be 的意思——即存在的意思。「亚里士多德的本质主义」讨论的就是导致一件事物脱离上下文以后依然存在的属性。
我经常在进行系统设计时使用「亚里士多德的本质主义」。
比如,在我最近参与的一个视频编辑类 app 的非线编业务架构设计过程中,我就碰到了这样一个问题:
副片(clip)在轨道中的时间位置信息应不应该是副片的一个属性(property)?
我相信对于很多人在碰到这个问题时一定会选择把副片在轨道中的时间位置信息放到「副片」中去。但是实际上我们如果套用了「亚里士多德的本质主义」来看这个问题,我们马上就会发现:如果我们将「轨道中的时间位置信息」移除出一个「副片」,那么这个「副片」依然还是一个「副片」——也就是说在「副片」这个概念脱离上下文后,如果我们从中剥离掉「轨道中的时间位置信息」,「副片」这个概念是依然存在的。所以「轨道中的时间位置信息」就不属于「副片」的本质。
于是我意识到,「副片」在加入轨道后就不再是「副片」了,而变成了其他的东西,我们需要一个新的概念来指涉它。最后我根据文字排版中 framesetting 这个环节发明了一个术语叫 ClipFrame 来专门指代加入轨道中的「副片」,而 ClipFrame 上就记录了一个「副片」在轨道上的时间位置信息。而轨道则提供添加、删除、移动「副片」的业务——他们有可能操作起来像一个数组,因为有些轨道操作起来就像数组;也有可能操作起来要使用时间位置来作为参数,因为有些轨道允许「副片」间有时间间隙。轨道加入「副片」后会分配一个 ClipFrame 来存储「副片」,在 Swift 中进行 Sequence 和 Collection 操作时则吐出一个包含「副片」及其轨道中时间位置信息的 tuple。
是的,熟悉数据库的同学一定感觉出来了,我在进行 normalization。然而有时候进行系统设计的时候是要进行 denormalization 的,这样子系统的性能会更高。不过具体怎么做还是需要进行系统设计时具备性能容量思维,用性能容量是否能够承载相应设计来进行取舍。
客观唯心主义
怎样理解客观唯心主义?首先要澄清,「唯心」是一个错误的翻译。「唯心主义」的英文是 idealism,根据其词根 idea,中文翻译成「观念主义」比较好——其强调「物质」依托「观念」而存在。
这种想法在我们看来似乎有一点反直觉——因为人作为一堆「物质」,肯定是先存在了,才能思考出「观念」。但是,每一个「物质」其本身却又是一个「观念」,不发明每一个物质对应的观念,那么我们是没有办法识别出这个物质的。这就有点类似于「鸡生蛋、蛋生鸡」的问题。而「唯心主义」站在了「观念」先于「物质」这边。「客观唯心主义」则更加强调识别出有关某个「物质」的「观念」时应当秉持某个特定的客观原则。
如果我们能够理解客观唯心主义,那么我们也就能理解为什么有人会说「面向对象/物件导向」是一个骗局,以及 is-a 指针这个设计的可笑。
让我们想象,为一个猪肉贩子以及生物学家设计软件系统。里面都有「猪」这个 class。
根据「面向对象/物件导向」的原则,那么对于猪肉贩子而言,「猪」这个 class 可能拥有以下类型的 properties:
- 猪耳朵
- 猪大肠
- 猪蹄子
- 猪肩肉
- 猪里脊
- ……
而对于生物学家而言,「猪」这个 class 可能拥有以下类型的 properties:
- DNA
- RNA
- 细胞
- 组织
- 器官
- ……
然而当我们看到一块猪皮的时候,我们会发现,同样的成分,在猪肉贩子那叫「猪皮」,在生物学叫那叫「猪身上最大的器官——皮肤」。或者不用将视角放到如此细小,光看「猪」这个 class 下不同的 properties,我们就应该知道,我们针对物理世界中的一个东西建立了两个不同的「观念」。在「面向对象/物件导向」的世界里,我们要用两个 class 来表述这两个概念,而这两个 class 的 is-a 指针不可能相同——虽然其背后对应的现实世界的东西是一样的。
面对这个问题,业界有一种叫 entity-component-system 的架构可以完全规避这种困境。在这种架构中,任意数据都被视为是一个 entity;而一个 entity 又由多个 components 组成;最后由定期运行的 systems(或者说函数)来选取要关心的 components,依次遍历所有的 entities,在逐个的 system 调用中来进行计算。这样,万物皆 entities,而导致他们不同的仅仅只是他们的 components,就避免了我们站在不同的立场,为现实世界中同样的东西建立不同概念的问题。