功能编程不鼓励可变状态,并将数据结构限定为永久值。可以为变量分配一个新值(从纯函数派生的新数据结构),但是现有的数据结构永远不应该变异。范式已从程序状态机转变为将功能建模为连接在一起的纯功能的长链。因此减少了系统中的耦合,理论上应该更容易推理和跟踪错误。
但是我们在Web开发中试图解决的问题的业务领域自然是面向对象的。我们谈论的是实体及其属性。身份和它的状态。状态存储在数据库中。当我们进行货币交易时,我们正在改变特定的银行账户以取款/存钱。我们不考虑为每笔交易更换新的银行账户。它们本身就是可变对象。某个地方必须以某种方式改变这种状态。我意识到函数式语言并不否认状态(毕竟这是不可避免的),但却支持副作用自由语法,这使得变异操作感觉很尴尬。
现在,函数式语言的目标是什么方法来定位现实世界的面向对象的本质?例如 clojure 没有类的概念,因此我猜它没有ORM。它如何与企业业务的关系世界保持一致?范式甚至会知道称为实体的概念吗?是否允许改变这样的实体?或者函数式编程是否需要改变我们存储数据的方式?
这整个功能方法似乎是理论上的,与现实世界相矛盾。如何才能更好地了解所有这些在Web开发的真实场景中是如何工作的?
答案 0 :(得分:8)
从Stuart Halloway关于Clojure's Time Model的演讲开始可能是值得的。
不承诺做谈话正义:实体有国家。注意,有-A,而不是-A;所以我们应该考虑构图。从概念上讲,我们认为对象是对不可变状态的可变引用。
现在,如果你通过Bertrand Meyer CQS的镜头看这个;查询支持很简单 - 查询不会修改对象的状态,因此它们实际上是一个纯函数
result = query(this.currentState)
另一方面,命令支持可以简单地分为两部分,一个产生新状态的查询,以及对可变引用的更改。
State nextState = query(this.currentState)
{ref-set} this.currentState = nextState
李在他的评论中指出,当前状态和历史是相互对立的,也就是说,我们可以轻易地转变上面的例子
// conceptually, this is still a _query_; we aren't mutating current state
// but are instead calculating a new value.
State nextState = this.currentState.apply(command)
{ref-set} this.currentState = nextState
在eventsourcing中,我们对当前模型的命令输出与更改历史之间的区别更加谨慎,因此拼写看起来更像是
// conceptually, this is still a _query_; we aren't mutating current state
// but are instead calculating a new value.
Events changes = command(this.currentState)
State nextState = this.currentState.apply(changes)
{ref-set} this.currentState = nextState
了解实体的整个历史,你得到了
State state = fold(history, State.SEED)
{ref-set} this.currentState = state
状态存储在数据库中。
几乎:州存储在数据库中。
State nextState = database.currentState.apply(command)
{ref-set} database.currentState = nextState
例如,Greg Young将Event Store描述为只有32个字节的mutable state。其他一切都是写一次。
这整个功能性方法似乎是理论上的,与现实世界相矛盾。
完全没有 - 记住,过去是不可变的;允许你改变过去的模型不符合现实世界的约束。
如何在Web开发的真实场景中更好地了解所有这些是如何工作的?
首先,请查看Greg Young关于事件采购的讨论(特别是他的新事件;随着时间的推移,他的功能越来越强),以及Mark Seemann's blog。除了Stuart或Rich Hickey所能找到的一切。