本集简介
双语字幕
仅展示文本字幕,不包含中文音频;想边听边看,请使用 Bayt 播客 App。
欢迎来到《函数式设计与闭包》
Welcome to Functional Design and Closure.
我是内特·琼斯
I am Nate Jones.
我是克里斯托弗·纽曼
And I'm Christophe Newman.
每周我们会讨论一个软件设计问题,以及如何运用函数式原则和Clojure编程语言来解决它
Each week, we discuss a software design problem and how we might solve it using functional principles and the Clojure programming language.
那么克里斯托弗,这周我们要讨论什么话题?
So Christophe, what are we gonna talk about this week?
嗯,我一直在回想最初开始用Clojure编程的时候
Well, I have been thinking about way back when I first started programming in Clojure.
你知道,我那时整天都在摆弄REPL
You know, I was messing around with the REPL a bunch.
所有这些例子,其实都是在熟悉语法
All these examples, it's really just getting a handle on the syntax.
基本上就是把REPL当作高级计算器来用
Basically treating like the REPL as a glorified calculator.
来做些乘法运算
Let's do some multiplications.
对
Yeah.
小表达式,玩具式的表达式
Little expressions, little toy expressions.
你知道的,跑个小map操作,小filter操作
You know, run a little map, a little filter.
是啊
Yeah.
需要反复回顾你的历史记录,尝试不同的函数——抱歉,是相同函数的不同参数,学习词汇和语言。
A lot a lot of going back in your history and and and trying different functions or sorry, different arguments to the same functions, learning the vocabulary, the language.
对。
Right.
其实就是随便摆弄一下,找找感觉。
Just just kinda fiddling around really to just get a feel for it.
于是我到了想要做些更有实质性内容的阶段。
And so I got to the point where I wanted to do something a little more substantive.
我想,应该做个封装良好的小东西。
And I thought, well, I should make a nice little encapsulated thing.
因为学习面向对象时,核心就是封装。
Cause you learn OO, it's all about encapsulation.
你创建一个类,它代表某个问题的解决方案。
You make a class, it represents a solution to a problem.
都是封装好的。
It's all encapsulated.
我过去做过类似小游戏的实现,比如游戏状态跟踪器,比如井字棋游戏。
Something I've done before in the past is implementing like a little game, like a little game state tracker, like tic tac toe, like like a tic tac toe game.
是啊。
Yeah.
游戏确实是构建软件和尝试设计的好练习,因为它不只是一个能用单次函数调用完成的复杂计算。
It's a a game is a really good exercise for building software and trying to design something because, you know, it's not just, you know, a very complex calculation that you can have in one function call.
你需要从一个已知初始状态开始,然后逐步推进。
It's something you you kind of you start off with a known start state and then you have to, like, move it forward.
但推进的方式有无限可能。
But the way you move forward, there's, like, endless possibilities.
你不知道谁会先走,谁会后走,谁会赢。
You don't know who's gonna go first, who's gonna go second, who's gonna win.
你知道,就像井字棋是个很好的例子,因为大家都知道井字棋的规则。
And, you know, so it's like Tic Tac Toe is a good example because there's everybody knows the rules of Tic Tac Toe.
它有明确的语义规则。
There are well defined semantics around that.
对。
Right.
这是个非常简单的游戏。
It's a real simple game.
它相当普遍。
It's pretty universal.
但没错,这是个简单的游戏。
But yeah, it's a simple game.
说到底,这就是个简单的游戏。
At the end of the day, it's a simple game.
所以过去我会想,好吧,我要创建一个类。
And so in the past, I'd be like, okay, I'm going to make a class.
我要把这个类命名为井字棋,它将负责记录游戏状态。
I'm going to call the class Tic Tac Toe, and it's going to keep track of the game.
我要编写一些推动游戏进展的小方法。
I'm going to write little methods that are going to advance the game.
我会设置一些数据成员。
I'm going to have some data members.
我会设置一个叫board的成员,它将是个二维数组。
I'm going to have one called board, and it's going to be a two d array.
也就是说,三行三列的数组结构。
So three rows inside of an array of three columns, so to speak.
然后还有个标志表示当前轮到谁。
And then a flag for whose turn it is.
轮到O下棋了(用数字0表示)。
Zero is O's turn.
0代表O,明白吗?
Zero, o, get it?
好的。
Okay.
然后1代表X。
And then one is x.
接着我会写一个名为turn的函数。
And then I'd write a function called turn.
调用turn函数时,你需要传入行索引和列索引,比如用1表示X想下在左上角(位置0,0)。
And so you'd call the turn function and you'd give it like a row index and column index, and then a number one for like x wants to go in the upper left hand corner, zero zero.
没错,完全正确。
Yeah, totally.
对,我操作后棋盘状态就会翻转。
Yeah, I'd reach in and it would just like flip.
初始化游戏棋盘时全设为-1,当X下棋时就把二维数组中对应位置翻转为1。
So you'd initialize a game board to, like, a bunch of negative ones, and then it'd go in there and it would flip that spot in the two d array and turn it into a one because x is gonna go in there.
是的。
Yeah.
如果想记录游戏过程中谁下了什么位置,可以维护一个plays数组,按顺序记录每位玩家的落子位置。
And then if you wanted to, like, track, you know, who played what throughout the game, you'd have, like, a, you know, a plays array and it would just have in it, you know, who went first, who went second, who went third, and and what where the what location in the array that they that they chose.
这样游戏结束时就能打印对战记录,包括获胜者和完整的走棋顺序。如果想开新局,就不能复用当前游戏数据。
So that, you know, at the end of the game, you can you can print out what what happened in the game, like who won, here's the sequence of moves, and, you know, and then if you wanted to make a new game, you'd have to you couldn't reuse that same game.
必须创建新的游戏引擎类或新引擎对象。
You'd have to make a new game, a new engine class or a new engine object.
对吧?
Right?
对。
Right.
是的。
Yeah.
所以你看,如果你真的在搞C++那套,你就会觉得,好吧。
And so that plays you know, if you're really doing the c c plus plus thing, you'd be like, okay.
最多有九步。
There's nine moves maximum.
然后你有个回合计数器,接着你从那里归零,把操作写入索引0的位置,然后在第一回合写入索引1的位置,以此类推。
And then you have a turn counter, and then you reach into that to turn zero, and then you write the play into index zero, and then turn one, you write the play into turn one, index one, etcetera, etcetera.
对。
Yeah.
然后你就,哦,我们开新局了。
And then you just, Oh, we have a new game.
直接新建一个那个类的实例就行。
Just make a new instance of that class.
就这样。
There you go.
对吧?
Right?
所以我就想,我要做个井字棋封装器。
So then I'm like, I'm going to make tic tac toe enclosure.
这肯定会很棒。
This is going to be great.
于是我就想,我要建个映射表,然后在表里设个棋盘键。
So I'd be like, I'm gonna have a map and then I'm gonna have this board key in the map.
然后对应的值会是个二维列表,就是列表套列表那种。
And then the value is gonna be like a two d list, like a list inside of a list.
同样的结构,一堆负值然后另一个标志表示轮到谁了。
Same structure, bunch of negative ones and then another flag like whose turn it is.
然后我去写个函数就完全卡住了,因为就像,等等,我不能直接在这个二维数组的零零点设为一。
And then I go to write a function and then I'd be totally stuck because it's like, wait, I can't just reach into index zero zero in this two d array and set it to one.
是啊。
Yeah.
完全同意。
Totally.
没有现成的函数可以设置闭包里的值。
There is no function, you know, set value in closure.
所以你四处查找然后发现,哦,有个叫assoc的函数。
And so you look around and you find, oh, there's this this one called assoc.
知道吗,就觉得,哦,这个不错。
You know, it's like, oh, it's good.
这就是设置的方法。
This is this is how I set.
你调用它之后查看原值却发现什么都没变。
So you call that on it, and then you look at the original value and nothing's changed.
你就会想,等等,
You're like, wait,
发生什么了?
what happened?
然后你查看函数签名才恍然大悟,哦,它会返回结果。
Then you go look at the function signature and you go, oh, it gives me something back.
然后你就明白,它创建了副本,这就是不可变数据结构的核心理念。
And, you know, it's like, oh, it made a copy of it, you know, which is the whole idea behind, you know, the immutable data structures, you know, the big the big concept.
但实际效果是你得到数据的新版本作为独立副本,原始数据不会被修改。
But the practical reason so the practical effect is that you get a new new version of that data as a as a as a distinct copy that and the original one is not modified.
对。
Right.
所以你看,我的整个世界都在改变,对吧?
And so I this this whole like, my whole world was changing, right?
因为与其让这个类直接操作二维数据结构并旋转它,然后这个二维数据结构会随时间改变,我不得不重新思考如何实现这个功能。
Because instead of just having this class that would reach into this two d data structure and just pivot, and then this two d data structure gets mutated over time, I had to rethink how that was done.
所以现在我必须编写这个回合函数,它接收这个地图——我们称之为游戏状态地图,里面包含棋盘键和对应的二维列表。
So now this I have to write this turn function that takes like this map, this game we'll call it the game state map that had, you know, the board key in it with this like two d list.
然后它还有个回合键。
And then it had like the turn key in it.
接着它需要使用soc-in函数操作索引0 0的位置。
And then and then it would have to use a soc in with like index zero zero.
就像这样:冒号board然后是0 0,再是值。
You know, it'd be like colon board and then zero zero, and then the value.
对吧?
Right?
我们要用soc-in操作的地方。
That we're gonna soc in.
然后我就有了这个新版本棋盘的引用。
And and then I have a reference to this new version of the board.
就像这样,用let代码块,把新棋盘绑定到旧棋盘的关联上。
So it's like, got this let block and it's like, okay, new board is bound to this association on the old board.
然后回合必须切换。
And then the turn has to flip.
对吧?
Right?
因为之前回合设为1表示X的回合,现在需要设为0。
So the turn was set to to one because it was x's turn, and now the turn needs to be set to zero.
所以就像我有了,好吧,这个新的变量轮次。
So then it's like I have, okay, this variable new turn.
对吧?
Right?
然后我基本上,你知道,就随便设置它。
And then I basically, you know, set it to whatever.
然后这个东西必须返回。
And then this thing has to return.
它必须返回一个全新的地图,包含新轮次和新棋盘。
It has to, like, return in a brand new map with the new turn and the new board.
然后
And
是的。
Yeah.
完全正确。
Totally.
所以现在你有了两个值。
And then so now you have now you have two values.
现在你有了两个状态。
Now you have two states.
如果你想进行第三次或第二次轮次,现在你就有三个状态了。
And if you wanna take a a third turn or a second turn, now you have three states.
我记得在REPL里,我会先定义状态一,然后调用函数,再定义状态二,这样就会把状态一传进去调用函数,接着定义状态三。
And so I remember in the REPL, I'd have, like, you know, def state one and then call the function, and then def state two, and that would call the function passing state one into it, and then def state three.
最后我会得到一堆变量或值,就像散落在地上一样。
So, I'd end up with a bunch of variables or a bunch of values, like, laying on the floor.
所以这就像,你该怎么处理这个新值,它几乎是从机器里蹦出来的,你得接住它,否则它就会掉到地上。
And so, it's like, how do you handle like, it's almost like the new value, pops out of the machine and you gotta grab it or else it's gonna fall on the floor.
那么你把它放在哪里呢?
And so where do you put it?
因为以前我用面向对象编程时,我会写一个玩具小程序,把类定义放在一个文件里。
Because back when I was doing this in OO, I would write really a toy little program and it would have the class definition in a file.
假设我现在用Java。
And then let's say I'm in Java.
于是我会写一个井字棋类,然后另一个文件放main函数,或者有时直接把main函数放在井字棋类里,因为它是静态的。
And so I'd write my tic tac toe class and then I have another file that has my main or I just put my main in my tic tac toe class sometimes because it's static.
然后我会创建它的一个实例。
And then I'd make an instance of it.
接着我会写一堆命令式代码,比如拿着那个引用——那个game对象,我会写:ticTacToeGame等于new井字棋。
And then I would just have a bunch of these like imperative lines where, you know, I would take that reference that like game, you know, it's like, okay, I'd make tic tac toe game equals new tic tac toe.
然后我会调用game.turn,接着我会写个打印函数。
And then I'd have game dot turn, you know, and then I'd I'd have a print function.
然后game.print就会打印出状态。
Then game dot print and it print out the state.
接着game.turn,然后game.print,再game.turn,再game.print。
And then game dot turn and then game dot print and game dot turn and game dot print.
我就这样把所有步骤列出来。
And I just list all these out.
但现在用闭包就有这个问题,因为game.turn不会改变任何东西。
But but now in closure, have this problem because like game dot turn doesn't change anything.
它只是给我返回这个新引用。
It gives me this new reference.
最后我就不得不写这些let代码块,比如game等于这个新映射表。
So I end up with these like let blocks where it's like, okay, game equals, you know, this this this new this map.
然后我会调用turn方法,得到比如game1。
And then I would call turn on it and be like, okay, you know, game one.
然后我会调用那个,然后游戏二。
And then I'd I'd call turn on that and then game two.
我会启动那个并分配给游戏三。
And I'd turn on that and assign that to game three.
然后然后我会像,你知道的,打印打印行,你知道的,游戏一,打印行游戏二,打印行游戏三。
And then and then I'd have like, you know, print print line, you know, game one, print line game two, print line game three.
是的。
Yeah.
完全正确。
Totally.
但如果你不需要任何中间状态,我记得就像我最终会得到这些函数,你知道的,九行let块然后一行实际函数。
But then if you didn't if you didn't need any of those intermediate states I remember it was like I I would end up with these functions where that, you know, it's like nine nine lines of let block and then one line of actual function.
对。
Right.
但如果你不关心任何中间状态,只想让它们流过,你可以直接嵌套函数。
But then, if I don't care about any of those intermediate states, I just want them to flow through, you can just nest the function.
所以,你会有,比如,函数开头九个左括号,然后如果你看最右边,最末端,那是初始状态,你可以看到向左回溯时的中间值。
So, you you have, like, nine open parentheses at the beginning of your function, and it's like, if look at at the far right end, at the far tail end, there's your initial state, and you can see the intermediate values as you go back to the left.
对。
Right.
有那么一刻你会觉得,哦,好吧。
You have this moment where you're like, oh, okay.
这个回合函数接收一个游戏状态并返回一个游戏状态。
This turn function takes a game state and returns a game state.
所以我可以再次把它传入回合函数。
So I can pass that into the turn function again.
于是你就有了括号,括号,括号,括号,回合,你知道的。
And so you have like parenthesis, parenthesis, parenthesis, parenthesis, turn, you know.
嗯,就像是括号、转向、括号、转向、括号、转向这样的模式。
Well, it's like parenthesis, turn, parenthesis, turn, parenthesis, turn type thing.
对。
Yeah.
完全正确。
Totally.
而且它都是嵌套的。
And it's like all nested.
对吧?
Right?
然后你就能逐步构建起来。
And then and then you like build it up.
后来在我的Closure编程经历中,我了解到了线程宏,当时就觉得,哦,我明白了。
And so then some somewhere in my closure experience, I learned about the threading macro and it's like, oh, I get it.
对吧?
Right?
我有这个东西。
I I have this thing.
它非常对称。
It's like really symmetric.
它接收一个游戏状态映射,并返回一个游戏状态映射。
It like takes a game state map and it returns a game state map.
我要把初始状态放在线程顶部,就是单箭头线程。
I'm gonna, like, put the initial state at the top of the, you know, thread, like single arrow thread.
然后在线程宏里,我会放转向、转向、转向、转向、转向。
And then I'm gonna in the threading macro, have turn, turn, turn, turn, turn.
最后在这个东西的末尾,就能得到经过这么多转向后的游戏状态。
And then at the end of this thing, have the game state after that many turns.
那对我来说简直是件大事。
That was like a huge for me.
是啊。
Yeah.
只要里面的一切都遵循接收一个状态并返回一个状态的规则,你就可以做各种事情。
And as long as everything inside of there does obeys the rule of taking taking a state and returning a state, you can do all kinds of things.
你甚至可以把回合拆开做不同的事,比如可以设置跳过——我是说,虽然不明白为什么井字棋里要有跳过选项,
You can even break apart the turn and do different things where like you can have a pass you can have a I mean, I don't know why you'd have a pass in in tic tac toe,
但嘿,我们正在弥补所有...就当可以跳过吧,毕竟这是我们的井字棋游戏。
but hey, we're making up for all the Let's just say you have a pass because this is our tic tac toe game.
所以我们就这么定了。
So we're just gonna do it.
对。
Yeah.
对吧?
Right?
所以你写个叫pass的函数,它唯一的作用就是切换回合。
So you write a function that's pass and all it does is flip the turn.
对吧?
Right?
没错。
Yeah.
完全同意。
Totally.
你甚至可以添加类似打印输出的功能。
And then you can even have something that does something like print it out.
它会接收状态,打印出来——哦,但必须返回状态以遵循那个模式。
So it it would take the state in, print it out, oh, but it needs to return the state because it needs to obey that that pattern.
所以即使它没有改变原值或创建新值,仍需遵循线程宏的工作模式。
So even if it doesn't change it and make a new value, it still needs to obey the pattern for the thread macro to work.
但这样你就能清晰直观地看到整个流程是如何进行的。
But then you have this nice, clean, you can see how things go through.
表面看是命令式的,实际上你正在沿途创建新的状态。
It looks imperative, but in reality, you're actually creating new states along the way.
对。
Right.
这就像...就像个引用传递的接力赛对吧?
It's like a it's like a reference bucket brigade, right?
你带着原始状态的引用调用函数,函数生成新对象后返回其引用,这个新引用又需要像接力棒一样传递给下一次函数调用。
You call a function with a reference to the original state and then it makes a new thing and returns a reference to this new thing, which you then need to bucket brigade into the next call of the function.
所以线程机制本质上就是引用接力赛——因为每个操作都会返回新引用。
And then, you know, it's like so thread is just like the reference bucket brigade since everything returns a new reference.
对吧?
Right?
嗯。
Yeah.
完全正确。
Totally.
那如果...如果我们想把回合表示成独立数据结构呢?
Well, what what if we what if we wanted to represent the turns as their own data structure?
你看,一个回合不仅仅是参数集合。
We we you know, a turn is not just a set of arguments.
它本质上就是个独立的数据结构。
It's actually like its own data structure.
这样我们就能带着状态,可以传入单个回合,或者要处理三个回合怎么办?
And so, we can take the the state and we can we can we can pass in one turn, or what if we have three turns to do?
我们有一个接收三个回合对象的函数,还是应该按顺序对每个对象调用三次回合函数?
We have a function that takes three turn objects, or would we call the turn function three times with each one of those in sequence?
比如,怎么操作
Like, how
对。
Right.
对。
Right.
所以你的回合可能只是一对坐标。
So you have turn being maybe just pair of coordinates.
对吧?
Right?
因为游戏本身知道当前是谁的回合。
Because the game the game kinda knows whose turn it is.
就像它知道是X,也知道是O。
It's like it knows it's x, it knows it's o.
所以你只需要把它当作一个坐标对的列表。
So so you just it's like a it's like a list of coordinate pairs.
对吧?
Right?
这可以看作是一局游戏的记录,一个坐标对的列表。
That could be that could be like a transcript of a game, a list of coordinate pairs.
没错,完全正确。
Yeah, totally.
比如说你有一个列表先是零零,接着是零一。
And so so like if you have let's just say you have a list of like zero zero followed by a list of zero one.
这就相当于X落在左上角,第零行第零列。
So it's like X went in the top left corner, row zero, column zero.
然后O放在第零行第一列的位置。
And then O goes next to that in row zero, column one.
所以现在你可以通过列出走棋序列来表示井字棋的游戏状态。
And so now you can represent a state of a tic tac toe game by just listing a sequence of turns.
这让我有点联想到国际象棋的记谱法。
It kinda reminds me of like chess notation.
哦,是的。
Oh, yeah.
没错。
Yeah.
但然后你...这就像是...呃...对。
But then you you it's like a it's like a a yeah.
就像,我喜欢你说的'记录'这个词,它展示了人类或玩家的操作过程,而不仅仅是展示九种不同的状态表示,这样理解起来更直观。
Like, I like the word you said, a transcript, how it it shows this is the this is this this is kind of what the humans did or the what what the players did instead of just showing you here's the the the nine different state representations because it kinda makes more sense that way.
对。
Right.
你有一系列走棋记录,可以从中推导出——如果你愿意的话——推导出这些走棋之后的棋盘状态。
You have like the list of terms and you can kind of derive, if you will, derive the board after that list of turns.
是的。
Yeah.
完全同意。
Totally.
那么你如何利用这组走棋记录来生成最终的游戏状态呢?
So then how would you take that list of turns and and and use it to create the final game state?
这是
This is
嗯,我觉得你现在...
Well, I think you're you're moment.
对吧?
Right?
比如,与其只是把参数传给turn函数,我是说,你也可以这样做。
Like, instead of just passing arguments into the turn function, I mean, you could.
你可以把这个——如果你愿意的话——这个元组,这个包含两样东西的列表,当作你的turn表示。
You you could pat you make this this tuple, if you will, this list of two things as like your turn representation.
对吧?
Right?
嗯。
Mhmm.
然后你让你的turn函数接收一个状态,再接收这个turn对象(或随便你怎么称呼它),最后返回一个新状态?
And then you make your turn function take a state and then take this turn object or whatever you wanna call it and then give you a new state back?
对。
Yeah.
没错。
Definitely.
所以接下来就像是要把一堆turn传进去处理。
And so then it's like you wanna send a bunch of turns through it.
你会怎么做?
What would you do?
是的。
Yeah.
这是我很早以前就学过的一个函数,但一直没搞懂,直到后来才明白它本质上就是在执行我们刚才讨论过的线程宏——接收一个值,然后反复将其应用到状态上,这个函数叫reduce。
So that's one it's a function that I learned about pretty early on, but always had always struggle with until I kind of understood that it's just taking it's basically doing the threading macro that we just talked about earlier where it takes something and then is basically applying it again and again to the state, and it's the function called reduce.
所以reduce是你可以使用的方法。
So you'd you reduce is something you can do.
你可以设定一个初始状态,然后列出你想对该状态执行的操作(或者至少是一组参数),接着用reduce将这些组合起来,它会依次用这些参数对状态应用函数,直到完成所有操作后返回最终结果。
You can take an initial state and then a list of things you wanna do to that state, or at least maybe a list of arguments, and then, you can you can combine that together in such a way that that reduce will actually apply each the function to that state with those arguments in sequence until it's done and then they'll give you the result back.
对。
Right.
所以如果你看到有一个线程块,就像单箭头线程块那样。
So if you like see you have a threading block, like single arrow threading block.
对吧?
Right?
然后它就像转啊转啊转啊转。
And and it's just like turn, turn, turn, turn.
是啊。
Yeah.
都是一回事。
It's all the same thing.
你每次都在运行相同的函数来推进这个游戏。
You're just running the same function every time to just advance this game.
所以你可以这样,好吧,我们来写个reduce函数,把turn函数作为所谓的reducer函数传进去,它接收当前状态和下一个回合,然后生成新状态。
So you could be like, okay, let's write reduce, let's give it the turn function as a so called reducer function that takes the current state and then the next turn, and then it produces a new state.
然后你传入初始状态,也就是全新的游戏,再传入一个回合列表。
And then you feed in your initial state, which would just be the brand new game, and then you feed in a list of turns.
你在这个东西上运行reduce,然后会得到什么?
So you run reduce on this thing, and then what do you get out?
得到执行完所有这些回合后的游戏状态。
Get the game after all those turns have been taken.
没错。
Yeah.
确实。
Definitely.
还有个特别酷的事是我之前发现的,其实还有个叫reductions的函数,它能给出所有九个(或任意多个)回合的完整列表。
One thing that's really cool that I found a while ago too is that there's actually another function called reductions, which will actually give you a list of all nine or however many turns you had.
所有的状态,中间状态。
All of the states, the intermediate states.
你实际上可以
You can actually
回溯那些。
go back and that.
没错。
Right.
归约操作简直颠覆认知。
Reductions is mind bending.
对吧?
Right?
因为它做的恰恰相反——不是直接给你最终结果,而是提供过程中所有中间状态的列表。
Because it's like it does the opposite of like instead of just giving you the last thing, like gives you a list of all the things that were made along the way.
对我来说这简直震撼三观。
So for me, was like a mindblower.
就像我只需要编写这个小游戏逻辑。
Like all I had to do is write this like little game logic thing.
现在我就能通过归约查看游戏过程中出现过的任何状态。
And now I can literally see any state that ever existed over the course of that game using reductions.
是啊。
Yeah.
太酷了。
So cool.
然后你可以把它们记录下来或打印输出之类的。
And then you can write them down or log them out or whatever.
这在处理更业务化的场景时特别有用——比如当你想追踪某些重要事项时,这可比玩井字棋有意义多了。
And it's really useful when you have things that are a lot more, we'll call them business y where you you wanna know something that, you know, you want something that where you're you know, where you're trying to to accomplish something more more significant than play tic tac toe.
所以,在井字棋游戏中,你可能只想知道最终结果。
So, in tic tac toe, you probably only wanna know the end result.
但是对的。
But Right.
是啊。
Yeah.
没错。
Right.
但这就像是,哇哦。
But it's just it was just like, oh, wow.
你知道,用命令式编程的话,我会每隔一行打印一次,这样就能看到所有状态。
I can you know, in the imperative way, I would have print every other line so I could see the the state of all.
但现在在REPL里,我只需调用reductions函数,传入这个回合函数和初始游戏状态,再加上回合列表,砰的一下就能看到游戏过程。
But now in the REPL, I can just call reductions with this turn function and this, you know, game initial state and then a list of turns and boom, I can see the game get played.
对吧?
Right?
我能看到棋盘被打印出来,你知道的,针对每一步。
I see the board get printed out, you know, for for each of those.
是啊。
Yeah.
这就是使用像Closure这样拥有不可变数据结构的语言时,你能获得的好处之一——所有历史版本的游戏状态都能以最小冗余保存下来。
That's and that's one of the, like, you know, the things you get when you start using a language like Closure that has immutable data structures is that you get that all the previous versions of the game, you you get that with as little as little duplication as possible.
你知道,在面向对象编程中,一旦进入新回合,之前的那个‘宇宙’实例就消失了,你只能活在当前版本里。
You know, in object oriented programming, you know, as soon as you play a new turn, that previous instance of the previous, you know, universe is now gone, and and and you're only living in the current one.
但在Closure里,所有已存在的‘宇宙’都能以数据形式同时存在于内存中,方便你随时检查、打印或进行任何操作。
But but in Closure, you can have all the existing all the preexisting universes all at once in data, in in memory, so that you can actually inspect them or print them or do whatever you want with them.
确实。
Right.
你只需要编写核心逻辑,现在就可以决定——你想看到所有可能的宇宙吗?
Like, all you had to do was write the core logic, and then now you get to decide, do you wanna see all the universes?
你只想看看最终的宇宙吗?
Do you wanna just see the final universe?
你愿意一步步慢慢来吗?
Do you wanna just take one step at a time?
在面向对象中,由于存在可变性,除非你编写更多代码(比如克隆函数)来捕获每个中间状态,否则就失去了观察所有可能宇宙的机会。
In OO, because it mutates, you lose the option of seeing all the universes along the way unless you write even more code to capture each of those universes like a clone function or something.
是啊。
Yeah.
天啊。
Oh, man.
我在Java里写克隆函数时经常遇到意外结果,因为可变性导致你永远不知道哪些对象是相互关联的。
So many times I'm writing a clone function in Java and getting unexpected results because you never know what is linked because of because of mutability.
不可变性就能让你彻底摆脱这个问题。
So immutability just you get out of that whole thing.
没错。
Right.
对。
Yeah.
Sudet,我觉得讨论这个话题真的很有意思。
So I think it's been it's been a lot of fun talking about this, Sudet.
说真的,通过游戏来讨论可变性与不可变性对设计的影响真是个好方法。
I think, man, talking about a game is a really good way of talking of discussing mutability and immutability and how they impact your design.
确实。
Yeah.
游戏必须改变状态。
Games have to change state.
这就好比游戏如果没有进展就会很无聊。
That's like Games are boring if they don't progress.
对吧?
Right?
所以它就像是在解决那个问题。
So it's it like forces out that problem.
是啊。
Yeah.
确实是啊。
It really Yeah.
嗯,不错。
Well, cool.
那真是——真是段非常非常非常有趣的对话。
That was that was a really really really a really fun conversation.
感谢收听。
Thank you for listening.
您可以在closuredesign.club网站上找到我们的节目笔记和往期内容。
You can find our show notes and past episodes on the web at closuredesign.club.
我们也在Twitter上,账号是closure design。
And we're also on Twitter at closure design.
我们很期待听到您的反馈。
And we would love to hear from you.
欢迎发送邮件至feedbackclosuredesign。
Go ahead and send us an email at feedbackclosuredesign.
club。
Club.
我们很乐意收到您的问题。
We would love to get your questions.
我们很想听听您的反馈,只需将您的故事发送给我们。
We'd love to hear your feedback, and just send us your stories.
我们也非常喜欢这些故事。
We love those too.
我们下周会回来。
We'll be back next week.
在此期间,请尽量减少您的突变操作。
Until then, try to reduce your mutations.
关于 Bayt 播客
Bayt 提供中文+原文双语音频和字幕,帮助你打破语言障碍,轻松听懂全球优质播客。