笛卡尔函数
by David Valdman
大卫·瓦尔德曼(David Valdman)
笛卡尔,伯克利和函数式React式编程 (Descartes, Berkeley and Functional Reactive Programming)
Functional reactive programming is laden with unfamiliar terminology to the newcomer: pure functions, immutability, monads, etc. But beneath these concepts is a deeper principle — one debated long before Charles Babbage and the first computers. I argue the difference between object-oriented programming (OOP) and functional reactive programming (FRP) is as much about interpretations of reality as it is about structures of programs.
函数式React式编程对于新来者来说充满了陌生的术语:纯函数,不变性,单子表达式等。但是在这些概念之下是更深层的原理—早在Charles Babbage和第一台计算机之前就已经有人争论过。 我认为面向对象程序设计(OOP)与功能React程序设计(FRP)之间的区别不仅在于对程序的结构的了解,还取决于对现实的解释。
Here’s a thought experiment we’ve all likely heard:
这是我们都可能听到的思想实验:
If a tree falls in the middle of a forest, and no one is there to hear it, does it make a sound?
如果一棵树掉在森林中间,没人在听,它发出声音吗?
There are many of ways to attack this question as ill-posed. Ignoring them for the moment, this question is poking at a fundamental aspect of reality: causality. Is existence dependent on, or independent of, observation? Let’s translate this thought experiment into code. Here’s a tree:
有很多方法可以解决这个问题。 暂时不理会它们,这个问题是关于现实的一个基本方面:因果关系。 存在是依赖于观察还是独立于观察? 让我们将此思想实验转化为代码。 这是一棵树 :
class Tree { constructor(){ this._fell = false; } set fell(state){ this._fell = state; } get fell(){ return this._fell; }}
var tree = new Tree();tree.fell = true;
To make the tree fall we set its fallen state to true. This is textbook object-oriented programming. Its patterns are getters, setters, and state. Simple enough, but in the context of our thought experiment there is a lurking interpretation: if one questions whether the tree fell, even if they didn’t observe it, the answer is a resounding “Yes!” One only needs to check that tree.fell is true. Those that answer yes to our philosophical question do so because they can return to the forest, see the fallen tree, and deduce it fell in the past. Here is the codified equivalent. Looks like we’ve solved that centuries-old riddle.
为了使树倒下,我们将其倒下状态设置为true 。 这是教科书的面向对象程序设计。 它的模式是获取器,设置器和状态。 很简单,但是在我们的思想实验的背景下,存在一个潜伏的解释:如果有人质疑树是否倒下,即使他们没有观察到它,答案都是巨大的“ 是的! 只需检查一棵树即可。 是真的 。 那些对我们的哲学问题回答是的人之所以这样做,是因为他们可以回到森林,看到那棵倒下的树,并推断出它过去倒下了。 这是经过整理的对等物。 看来我们已经解决了这个有着数百年历史的谜语。
Not so fast! Let’s look at a different approach. Here’s another tree:
没那么快! 让我们看看另一种方法。 这是另一棵树 :
class Tree extends EventEmitter {}var tree = new Tree();tree.emit('fall');
This is the “reactive” tree. Its patterns are events and transforms. In its purest form our tree maintains no state. We make it fall by broadcasting a fall event. Alas, the message falls on deaf ears! No state has changed, no evidence is left. There is no way to deduce the past by querying reality. Did the tree fall? *shrug*
这是“React性” 树 。 它的模式是事件和转换。 我们的树以其最纯粹的形式保持任何状态。 我们通过广播坠落事件使其坠落。 las,该消息充耳不闻! 状态没有改变,没有证据。 无法通过查询现实来推断过去。 树倒下了吗? * 耸肩 *
笛卡尔和伯克利 (Descartes and Berkeley)
The object-oriented and reactive approaches give two different answers to our thought experiment because they embody two contradictory philosophies of epistemology: Rationalism, popularized by Descartes in the late 1600s, and Empiricism popularized by Berkeley in the early 1700s.
面向对象的方法和React性方法为我们的思想实验提供了两个不同的答案,因为它们体现了两种相互矛盾的认识论哲学:理性主义在1600年代后期由笛卡尔(Descartes)所普及,而经验主义则在伯克利(Berkeley)在1700年代初所普及。
Descartes, in a streak of fanatical skepticism, found he could only be sure of one thing: his own existence. He came to this conclusion because he couldn’t doubt the existence of his thoughts and concluded there must be some entity doing the thinking, thus coining the phrase, cogito ergo sum: I think therefore I am. In other words: by acknowledging that there is some internal state that is changing, there must be some agent for whom the state belongs. To Descartes, changes of state is proof of existence — just like our first tree.
笛卡尔疯狂地怀疑,发现他只能确定一件事:他自己的存在。 他之所以得出这个结论,是因为他毫不怀疑自己思想的存在,并得出结论认为一定有某个实体在进行思考,因此创造了“ cogito ergo sum ”一词:我想是的。 换句话说:通过确认某些内部状态正在改变,必须确定该状态所属的某些代理。 对于笛卡尔来说,状态的改变就是存在的证明,就像我们的第一棵树一样。
Soon after Descartes comes George Berkeley. Berkeley denounced the realist’s view. To Berkeley, it made no sense for material objects, like trees, to have existence. Existence only comes to us through thoughts (mental as opposed to physical experience), and thoughts must assimilate in the mind to exist. Material objects are deceptions; their essence is not their physicality but their ability to transform the immaterial. If a thought is not assimilated in the mind, it has no existence. Thus he popularized the Latin phrase esse percepi: to be is to be perceived.
笛卡尔之后不久,乔治·伯克利就去了。 伯克利谴责现实主义者的观点。 对于伯克利来说,像树木这样的物质对象存在是没有意义的。 存在只能通过思想(与物理经验相反的思想)来到达我们,思想必须在思想中吸收才能存在。 物质对象是欺骗; 他们的本质不是他们的身体,而是他们改变非物质的能力。 如果思想在思想中没有被同化,那么它就不存在。 因此,他普及了拉丁语esse percepi :be be is be感知到。
Let’s translate Berkeley’s reality into code. For our second tree to make a sound a mind must interpret it. We will create a chain of causality, starting from the tree falling, to the air vibrating, to the ear creating an electrical stimulus, to the brain interpreting it as sound.
让我们将伯克利的现实转化为代码。 为了使我们的第二棵树发出声音,头脑必须对其进行解释。 我们将创建因果关系链,从倒下的树木开始,到空气振动,再到耳朵产生电刺激,再到大脑将其解释为声音。
When the tree falls, the air vibrates.
当树木倒下时,空气会振动。
class Air extends EventEmitter { constructor (){ super(); function mapFall (fall){...} this.on('fall', (fall) => { var vibration = mapFall(fall); this.emit('vibrate', vibration); }; }}
When the air vibrates, the ear converts it to an electrical stimulus.
当空气振动时,耳朵会将其转换为电刺激。
class Ear extends EventEmitter { constructor (){ super(); function mapFrequency (frequency){...} this.on('vibrate', (frequency) => { var stimulus = mapFrequency(frequency); this.emit('stimulus', stimulus); }; }}
When ear creates a stimulus, the brain interprets it as sound.
当耳朵产生刺激时,大脑会将其解释为声音。
class Brain extends EventEmitter { constructor (){ super(); function mapStimulus (signal){...} this.on('stimulus', (signal) => { var sound = mapStimulus(signal); this.emit('sound', sound); }; }}
We have effectively set up a chain of causality, which we make explicit by building a pipeline:
我们已经有效地建立了因果关系链,我们通过建立管道来明确此因果关系链:
tree.pipe(air).pipe(ear).pipe(brain);
Now, when the tree falls it makes an impression on a mind:
现在,当树倒下时,会给人留下深刻的印象:
brain.on('sound', (sound) => { // We exit the system. You have been heard! console.log(sound); });
tree.emit('fall', fallData);
Berkeley called this concept Subjective Idealism. Idealism because it asserts only thoughts or ideas exist, and subjective because reality is dependent on the subjects that perceive it. In my opinion, Subjective Idealism is the philosophy underpinning reactive programming. Berkeley writes,
伯克利将此概念称为主观唯心主义 。 理想主义是因为它断言只有思想或观念存在,而主观是因为现实取决于感知它的主体。 在我看来,主观唯心主义是React式编程的基础哲学。 伯克利写道,
It is indeed an opinion strangely prevailing amongst men, that houses, mountains, rivers, and in a word all sensible objects have an existence natural or real, distinct from their being perceived by the understanding. …For what are the fore-mentioned objects but the things we perceive by sense…and is it not plainly repugnant that any one of these or any combination of them should exist unperceived?
的确,在房屋,山脉,河流等人中普遍存在着一种奇怪的观点,即所有明智的物体都有自然的或真实的存在,这与理解所感知的事物截然不同。 ……对于前面提到的对象是什么,但是我们有意识地感知到的东西……难道这些对象中的任何一个或它们的任何组合都不应该被感知地存在不是难免吗?
I love this quote for its self-assured audacity. Berkeley is essentially calling us crazy for thinking that houses, mountains and rivers exist. In our example, trees, air, ears and brains are false idols; the only reality is mapFall, mapFrequency and mapStimulus. Reality is then never consumed, as it is with objects when they retain state. Reality is merely transformed.
我喜欢这句话,因为它具有自我保证的胆识。 伯克利实质上是因为认为房屋,山脉和河流而疯狂,这使我们发疯。 在我们的例子中,树木,空气,耳朵和大脑是虚假的偶像。 唯一的现实是mapFall , mapFrequency和mapStimulus 。 这样就永远不会消耗现实,就像对象保持状态时那样。 现实仅仅是改变了。
实践中的主观唯心主义 (Subjective Idealism in Practice)
In OOP we create objects that encapsulate some kind of behavior. We then construct programs which are networked relationships of these objects. Our program is structurally a graph.
在OOP中,我们创建封装某些行为的对象。 然后,我们构建程序,这些程序是这些对象的网络关系。 我们的程序在结构上是一个图 。
In FRP we create pipelines of functions that encapsulate causal relationships. Pipelines are then merged and branched to give the graph-like structure of an object-oriented program. However, there is an important limitation on the types of functions. Only pure functions are allowed. That is, functions that cannot effect anything outside themselves. In our example, the Ear object cannot change how the Air object vibrated. This constraint ensures that our pipelines have a well-defined direction from cause to effect. In terms of structure, this means our program is a directed acyclic graph (DAG).
在FRP中,我们创建了封装因果关系的功能流水线。 然后将管道合并并分支,以给出面向对象程序的类似图形的结构。 但是,对功能类型有重要限制。 仅允许使用纯函数 。 也就是说,无法影响自身外部任何功能的功能。 在我们的示例中, Ear对象无法更改Air对象的振动方式。 这种约束确保我们的管道从因果关系到方向都有明确的定义。 就结构而言,这意味着我们的程序是有向无环图 (DAG)。
To reason about software, we must think of it as a sequence of causal relationships. We must be able to order the program. Mathematically, a graph can be ordered if and only if it is a DAG. This is true no matter how you write your program. Whether you choose OOP, FRP or XYZ. What’s special about FRP, though, is that ordering is enforced by the pattern.
要推理软件,我们必须将其视为因果关系的序列。 我们必须能够订购该程序。 从数学上讲,当且仅当它是DAG时,才可以对其进行排序 。 无论您如何编写程序,都是如此。 选择OOP,FRP还是XYZ。 但是,FRP的特殊之处在于排序是由模式强制执行的。
In OOP, ordering is left unspecified. Objects can call methods on other objects. Objects can change the state of other objects. Everything has read and write privileges by default. Specifying an order is done manually by the developer. It is up to them to relate the sequential ordering of lines in a program to an ordering of the objects’ causal relationships.
在OOP中,订购未指定。 对象可以调用其他对象上的方法。 对象可以更改其他对象的状态。 默认情况下,所有内容都具有读取和写入权限。 指定订单是由开发人员手动完成的。 由他们决定将程序中各行的顺序排序与对象因果关系的排序相关联。
Unfortunately, this is all too easy to mess up. Notice that in OOP, when two objects write to the same shared state, you have a race condition. In FRP, when two functions try to affect one another you have an infinite loop. This is but one example of a theoretical pattern enforcing a practical result.
不幸的是,这太容易搞砸了。 请注意,在OOP中,当两个对象写入相同的共享状态时,您就处于竞争状态。 在FRP中,当两个函数试图相互影响时,您将陷入无限循环。 这只是一个强制实际结果的理论模式的例子。
The bottom line is that it is not enough to encapsulate state. A well-written program will also encapsulate dependency.
最重要的是,仅封装状态是不够的。 编写良好的程序还将封装依赖性。
权衡 (Tradeoffs)
“Programmers know the benefits of everything and the tradeoffs of nothing.”— Rich Hickey
“程序员知道一切的好处,而一无所获。” — Rich Hickey
You’d think after all this FRP praising and OOP bashing, I’d be firmly in the FRP camp. You’d be wrong! FRP is a programming pattern, and patterns serve to constrain solutions. If the ideal solution doesn’t satisfy the constraints, you’ll be wasting energy fighting against the pattern.
您会认为,在对FRP赞不绝口和对OOP的抨击之后,我会坚定地进入FRP阵营。 你会错的! FRP是一种编程模式,并且模式用于约束解决方案。 如果理想的解决方案不满足约束条件,那么您将浪费精力在与模式作斗争。
To be concrete, there are a few simple annoyances of FRP. Take immutability — you are always creating more memory. You can never, say, sort a list in place. You will always be creating a new list. In theory, immutability is a good pattern to observe. In practice, you may be memory constrained, and it may be a good idea to swim against the FRP current.
具体来说,FRP有一些简单的烦恼。 保持不变-您总是在创建更多的内存。 您永远无法说要对清单进行排序。 您将始终在创建一个新列表。 从理论上讲,不变性是一个很好的观察模式。 在实践中,您可能会受到内存的限制,并且最好是抵抗FRP电流。
But this is not the glaring problem. The glaring problem is that FRP becomes an anti-pattern when you don’t know when two parts of a codebase will interact. Take, for example, a first person shooter game. Somewhere is a bullet object, and somewhere else is a player object. A bullet may hit a player, but it’s unclear when this will happen. These objects need to retain state (velocity of the bullet, health of the player, etc.) so it is available at the moment they interact. Perhaps in the abstract the entire game can be thought of as one causal pipeline, but that sounds more daunting to me than thinking in terms of decoupled objects and state.
但是,这不是明显的问题。 明显的问题是,当您不知道代码库的两个部分何时交互时,FRP会变成反模式。 以第一人称射击游戏为例。 某处是子弹对象,而某处是玩家对象。 子弹可能会击中玩家,但尚不清楚何时会发生。 这些对象需要保持状态(子弹的速度,玩家的健康状况等),因此在它们互动时就可以使用。 也许从抽象上讲,整个游戏可以被视为一个因果流程,但这对我来说比在考虑对象和状态的分离方面更令人生畏。
To put on my philosophical hat once more, physics may decree that reality is one causal pipeline whose time evolution is governed by deterministic physical laws, and whose initial conditions (or probabilities) are provided by the big bang. But this is hardly how humans reason about cause and effect. We naturally slice up reality into higher-level objects and reason about their inter-relations. It can be simpler that way, even though it’s error prone!
再次戴上我的哲学帽子,物理学可能会宣布现实是一种因果关系,其时间演变受确定性物理定律支配,其初始条件(或概率)由大爆炸提供。 但是,这几乎不是人类对因果进行推理的方式。 我们自然地将现实切成更高层次的对象,并推理它们之间的相互关系。 即使容易出错,也可以这样简单!
I feel this is why FRP hasn’t been been wholly embraced by the programming community, even after seeing its many advantages. The best we should hope for when writing programs is to use FRP principles in places where its patterns fit the solution, and let them inspire OOP patterns where its patterns do not. To me this is a distinction between solutions that can be thought of as pipelines, and solutions that shouldn’t be.
我觉得这就是为什么即使看到了FRP的许多优点之后,编程社区仍未完全将其包含在内。 在编写程序时,我们最好的希望是在其模式适合解决方案的地方使用FRP原理,并让他们激发其模式不适合的OOP模式。 对我来说,这是可以视为管道的解决方案与不应视为管道的解决方案之间的区别。
结论 (Conclusion)
In philosophy the objective is not to solve our deepest problems, but to have a shared language and historical precedent to debate them. So when a new problem emerges, we don’t have to start over. Similarly for programming patterns. They are not used to decide right and wrong, as if these concepts have universal appeal. They are used to classify problems and their approaches.
哲学上的目标不是解决最深层次的问题,而是要有共同的语言和历史先例来辩论它们。 因此,当出现新问题时,我们不必重新开始。 编程模式也是如此。 它们并不用来决定是非,就像这些概念具有普遍吸引力一样。 它们用于对问题及其方法进行分类。
We should also look to other disciplines, much older than computer science, to see whether their shared language and historical precedent can inspire our own. Then we may see that the question, “did the tree fall?” is not answered with a yes or a no. That instead it questions a perspective. One that can frame our philosophy of epistemology or our architecture of programs. And one to which the answer lies between a state of mind, and a flow of thought!
我们还应该研究比计算机科学更古老的其他学科,看看它们的共同语言和历史先例是否可以激发我们自己的学科。 然后我们可能会看到一个问题:“ 树倒下了吗? 回答“ yes”或“ no” 。 相反,它质疑一个观点。 可以构成我们的认识论哲学或程序架构的人。 答案就在于一种状态与思想之间!
Preview of Part II
第二部分预览
There is still a nagging dilemma in our thought experiment. Even if there are no people to experience the sound, surely when the tree falls the air must vibrate! Right? In Part II we will go further down the rabbit hole to see that FRP can give a surprising interpretation here too. We’ll think of data flows in terms of push vs pull and see how this appears in concepts like lazy evaluation, algorithms like ray tracing, and applications like Excel.
在我们的思想实验中,仍然存在一个棘手的难题。 即使没有人可以听到声音,当树木倒下时,空气肯定也会振动! 对? 在第二部分中,我们将进一步深入研究,以了解FRP在这里也可以给出令人惊讶的解释。 我们将根据推与拉的关系来考虑数据流,并观察它在诸如惰性求值,光线跟踪之类的算法以及Excel之类的应用程序中如何出现。
笛卡尔函数