最近在群聊的时候,我们聊到了一个观点。
亚里士多德时代的科学不可能容许黑魔法,因为亚式科学的核心是 - 用直觉观察事物,得出物体的本质。黑魔法这种反常识的东西,只可能在可证伪科学体系下出现 - 这不你看,js就是现代产物(
我的一个写C++的朋友趁机黑一把:‘函数式编程也是现代产物’。
我想了想,拿出了收藏已久的The Craft Of Programming。
为什么呢?很简单:这本书是本Gateway Drug。
直觉上还挺好理解的。。。不过为啥越来越学院派了?
到了这一步我们有啥?
program calculation(stepwise refinement)
equivalence law
type derivation
simply typed lambda calculus
environment
denotational semantic
这跟SML还差啥?
HM?(我们可以写generic program,这些program等价于inline,然后因为是inline,所以有equivalence constraint。用这些constraint可以导致不需要写类型,可以去推导)
ADT?(其实这门语言是Algol W,已经有ADT了)
First Class Reference?(且不说Reference怎么算作函数式特性,这本书作者,John Reynold的Essence of Algol里面就很好的引入了Reference。)
Garbage Collection?(试想象下C程序员指着Java程序员说你丫太学院派了)
所以说,你看看,Imperative Programming跟Functional Programming有啥本质区别呢?这些特性,每一个的加入都如此理所当然,但是那一步才算是一个‘函数式编程语言’?
我们早就过了亚里士多德时代,发现了世界并没有这么多‘本质’。无论是那种语言,都是一步步从已有语言摸索出来的,只要你跟着,每次都学一个最小改动,到最后,你也会发现‘就这回事啊’。Lambda Cube是如此,Algol到Prolog是如此,Algol到Haskell更是如此。
既然编程语言之间并没有本质差别,那,什么是编程的流派?什么是面向对象/函数式/过程式?编译语言/解释语言/机器语言/电路描述语言?
答:流派未月亭。没有打错。没有发疯。
在起初,John Mccarthy没搞懂Lambda Calculus,导致JMC Lisp并不对应Lambda Calculus,连Lexical Scope都没有。Lexical Scope是Algol加入的,而编程语言跟Lambda Calculus的对应是Landin发现的。但这不是跟ISWIM对应,是跟Algol!OOP的元老之一,Luca,做过ML Module的奠基性工作,另一个元老,Alan Kay,借助了FExpr(MACLISP)的概念。Backus写出了Fortran后广播了不朽的’Can Programming be liberated from Von Neumann Style?’。借鉴了OO的Actor跟Algol的Scope的Guy Steel,又写出了Scheme,之后本人更是投身于Java跟Fortress这两门OO语言。而Algol,则受到了JMC吹枕边风,加入了if else expression跟recursion。The craft of programming并不是偶然,而是必然:所有流派都在诞生之初互相纠缠,至今从未分开 - 这不你看,ICFP还会接OO paper,OOPSLA不也一样,更不用说Scala这种融合两种范式的语言(当然,这样的work历史上屡见不鲜。)。
另,各种编程语言之间,只要发展到了一定地步,就可以用库实现各种feature。Scheme有lambda the ultimate imperative/goto,说只需要lambda就能实现assignment/dynamic scope/while/goto,Haskell有lazy functional state thread有typing dynamic typing有final tagless,要状态要动态类型要封装要可扩展性都可以自己随手实现,smalltalk能用object代表conditional,scala能用object代表module。OCaml也实现了effect system,发现go full circle,跟Haskell用monad表示的extensible effect殊途同归。而Haskell中通过first class Typeclass(Constraint,Dict)跟Constraint上定义subtyping typeclass,再加上Existential Type,就能随意控制扩展性 - 我就玩过这样的trick刚好3次。只要语言支持一定的功能,什么范式都能加进去。所以你看,所有主流语言都是多’范式‘的。到了这种地步,何必继续拘泥于这种划分?
编译语言/解释语言等更是虚妄,要知道,一个scheme就可以编译成机器码/有直接吃scheme表达式的机器/还有把scheme 程序弄成电路的尝试。连system verilog都有class了,这等划分的意义可在?
如果我想写得好听点,我可以说,因为绝大部分语言都有多种范式,范式定义永不明确,是由很多更小的,正交的feature一个个组成的,所以我们应该求同存异,抛弃各种范式的偏见,就跟过去跟现在一样继续互相学习。。。但是我不。
因为流派未月亭。
这就是编程的本质,编程的流派,编程的范式。流派未月亭。
就是如此荒谬,毫无意义。
只要我们一天还在用着‘范式’去讨论编程语言,我们就会永远陷入这种情况:
如何通俗易懂地举例说明“面向对象”和“面向过程”有什么区别?
如何通俗易懂地举例说明“面向对象”和“面向过程”有什么区别?
永远。大家只是互相竖起远离实际的符号,互相实施稻草人謬誤。Ad hoc polymorphism跟module成为OO的专利(而前者在Algol 68就有了,后者pascal的作者也加入了modula里面)。而需要描述OO缺点的时候,则去称之为‘反模块化’。structural programming最初是为了解决complexity产生的,却称为会造成杂乱的代码。而函数式编程?大家都默认了ref,effect跟extensibility solution(final, row polymorphism, tagless)的不存在。。。
为什么我们不能改变一下呢?比如说,谈论subtyping,我们这样做:
子类型(subtyping)是不是错误(ill-defined)的东西?
程序语言设计界是否开始认为 Subtyping 是 Anti-pattern?
谈论mutability的时候,我们聊gedanken里面的三种variable设计:
假设声明了struct Point { int x, int y },
0:identifier就是指针,所有Point的x跟y都共享,毫无意义(只有snobol这样做)
1:identifier绑定上指针,每一个Point内的x y都是可变的,无法控制mutability(C,Java)
2:identifier绑定上Value,然后有指针类型/值。如果需要任何的可变性,用Point*,然后update x通过构造新的point。然后通过sharing跟编译手段降低开销。(SML,OCaml,Haskell)
又或者,我们也可以谈论各种effect system(MTL,State,ST,IO,Extensible,跟语言内建的effect system)来讨论mutability。。。而不是,谈论到mutability,就只会’immutability是大势所趋,implicit parallelism大法好’,又或者’immutable不符合计算机基本模型’等myth。
这样有啥坏处呢?为啥我们不这样做?我不知道。也许,这样我们就不能喊些看上去很酷的名字,就跟圣斗士不能喊天马流星拳一样。
唉。