函数式特征
- Functions as first-class values, 函数一等公民, 可以把函数作为参数传递, 从而构造出高阶函数各种用法. 这个用法各种语言都支持了: Lua 支持, Python 似乎也支持, Java 也开始支持了, 我会的语言少都举不出来不支持传函数的流行语言.
-
Pure functions, 纯函数. 可以写, 但也有很大区别. JavaScript 没限制, 从而不能预判函数纯或者不纯. Clojure 遵循 Lisp 风格的约定, 带副作用的函数一般用
f!
这种叹号结尾的写法命名, 而编译器没有约束. Haskell 是严格约束的, 出了名的 IO Monad 就是因为遵循纯函数导致副作用难以直接用数学函数表达出来, 最终精心设计出一个概念. -
Referential transparency, 引用透明, 所以表达式可以被其运算结果完全替换掉, 也就是要求控制甚至避免副作用.
-
Controlled effects, 受控的副作用, 主要手段是隔离. JavaScript 需要人为地去隔离, 语言层面完全没有限制. Clojure 也需要人为隔离, 就像前面说的
f!
那样的约定, 同时规定了数据不可变, 再加上作者有意在语言中强调控制副作用, 实际上副作用少得多. Haskell 通过类型系统限定, 不隔离副作用无法通过编译的. -
Everything is an expression, 一切皆是表达式. JavaScript 做不到, 导致设计 DSL 时候很头疼, 倒是 CoffeeScript 做到了. Clojure 继承了 Lisp, 很明显一切皆是表达式. Haskell 代码里都是函数, 除了类型声明和语法糖部分, 也是一切皆是表达式.
-
No loops, 换句话说, 不能用 for/while, 因为这两个写法当中的
i++
依赖可变数据. JavaScript 经常使用 for/while. Clojure 当中的循环基本上用尾递归实现, 同时也提供了 doseq 之类的 Macro 让循环过程很好写. Haskell 就是完全尾递归的写法了. -
Immutable values. JavaScript 默认可变, 仅有的手段用
Object.freeze
可以强行锁定对象或者 const 锁定变量本身, 另外就是 immutable-js 那样的共享结构的不可变数据作为类库来实现. Clojure 是把不可变数据和结构共享作为语言的基础, 专门设计了 Atom 类型用于模拟共享的可变状态, 也不排除某些场景和宿主语言的互操作还是会有可变数据. Haskell 默认就是不可变数据, 也有 IORef 相关的代码可以模拟可变状态, 但在教程里几乎看不到. -
Algebraic Datatypes, 代数类型系统. JavaScript 没有静态类型系统, TypeScript 有类型, 但和代数类型还不一样. Clojure 没有静态类型系统, 就算有而只是很基础的类型检查, 或者用 Specs 做详细运行时检查. Haskell 有强大的代数类型系统, 即便是副作用也被涵盖在类型系统当中.
-
Product types. Haskell 通过代数类型系统支持. The ability to form type pairs, in an ad-hoc fashion. Generalizes to records.
-
No Null. JavaScript 当中有 undefined 和 null. Clojure 当中只有 nil. Haskell 里没有 null 也没有 nil, 而是用了 Maybe Monad 这样的概念, 通过类型系统进行了抽象和限制. null 的问题很深, 网上找解释吧, 我还没理解清楚, 只了解到满足了方便却造成了意料之外的复杂度.
-
A function always returns a value, 函数永远都有返回值, 类似一切皆是表达式那个问题. 比如 Haskell 里会有的叫做 Unit 的
()
空的值. 这个有点费解… -
A function takes exactly one value, 函数只接收一个参数。多参函数通过tuple来模拟。
-
Currying, 柯理化. JavaScript 和 Clojure 也能模拟, 而在 Haskell 当中是默认行为.
-
Lexical scoping, 词法作用域. 三者都支持.
-
Closures, 闭包, 都支持.
-
Pattern matching, 模式匹配. 类似解构赋值之类的在 JavaScript 和 Clojure 当中通过语法糖也算有这个功能, 但是跟 Haskell 以及 Elixir 当中的用法对比起来差距很大. 比如说 Haskell 甚至能定义
let 1 + 1 = 3
来覆盖+
的行为, 虽然是奇葩的现象, 但这就是一个定义的 pattern, 在 JavaScript 和 Clojure 都没有这种情况. -
Lack of subtyping。无子类型概念,因此没有通俗的’OO’概念。In it’s place, other type constructions are used, herein row-types.
-
Safety, The language is safe in the notion a program cannot segfault.
-
Type Safety, the idea that well-typed programs can’t go wrong.
-
No Globals, 没有全局变量。
-
α-conversion — The language has the property of alpha conversion.
-
β-reduction — The β reduction rule plays a central part in the evaluation strategy of the language.
-
η-conversion — The η-conversion rule holds.
-
Substitution as a principle — Substitution holds as a central principle throughout the language.
-
Lazy evaluation, 惰性计算. JavaScript 是严格求值的, 不支持惰性计算. Clojure 支持 Lazy, 然而由于 Clojure 又允许了一些副作用, 实际上某些特殊场景会需要手动 force 代码执行, 也就是说不完美. Haskell 采用惰性计算. 惰性计算就是说代码里的表达式被真正使用来才会真正执行, 否则就像是个 thunk, 继续以表达式存储着. 我印象里 Elm 社区说过, 对于图形界面来说 Lazy 反而是多余的.
-
Programmer defined infix and mixfix operators — The ability to extend the language with new fixity operators as the programmer needs them.