这个公式很简单,写成函数的话,用最简单的一个return即可。然而,如果我想要让他推广,输入华氏度也能求出摄氏度,甚至更广,一个公式里,只要其他的n-1个变量已知,就能自动补全公式,该怎么做呢?代码非原创,我只能对着大佬源码膜拜一番

运行范例运行结果

在上述代码中,对celsius的改变使得华氏度也发生了改变。

我们首先划分代码的架构,一共三个模块

connector(连接器) 高级的变量,可以通过约束发出通知和接受通知。

由于全程使用FP,因此OOP中的方法目前使用字典key进行调用。如celsius['forget']意为celsius.forget。

contraint (约束) 公式的约束,可以对connector之间进行连接,连接之后通过约束就能对connector进行通知了。

converter (转换器)负责装配公式中的所有连接器和约束

Constraint Part

def make_ternary_constraint(a,b,c,ab,ca,cb):

#三元约束
def new_value():
#通用计算公式,求出第三者连接器
av,bv,cv=[connector['has_val']()for connector in(a,b,c)]
if av and bv:
c['set_val'](constraint,ab(a['val'],b['val']))
elif av and cv:
b['set_val'](constraint,ca(c['val'],a['val']))
elif bv and cv:
a['set_val'](constraint,cb(c['val'],b['val']))
def forget_value():
#对abc三个连接器均使用forget函数
for connector in(a,b,c):
connector['forget'](constraint)
#constraint为调用dictionary,传递message给连接器
constraint={'new_val':new_value,#表示连接到约束的连接器有了新的值
'forget':forget_value}#表示连接到约束的连接器需要忘掉它的值
for connector in(a,b,c):
#令abc三个连接器均加入约束
connector['connect'](constraint)
#返回三元约束
return constraint

在上述函数中,a,b,c是参与约束的三个连接器,ab,ca,cb则是操作符,意思是知道其中两者的情况下,使用对应的操作符可以得到第三者。

Constraint Method
new_value():

先通过has_val观察是否存在已知变量,如果已知变量为n-1个,就能求出未知变量。

forget_value():

调用连接器中的forget函数清空其值,此后forget会对所有参与的约束都发出清空通知(因为一个连接器可能参与多个约束,例如方程组),相当于链式反应。

此后,所有连接器都加入这个约束,最后返回。

from operator import add,sub
def adder(a,b,c):

#加法公式的三元约束

return make_ternary_constraint(a,b,c,add,sub,sub)
from operator import mul,truediv
def multiplier(a,b,c):
#乘法公式的三元约束
return make_ternary_constraint(a,b,c,mul,truediv,truediv)

通过上面的高阶函数,我们可以建立这样的约束工具不断地减少参数数目,从而实现更好的抽象层次划分。

Connector Part
#Connector part
def make_connector(name=None):

#接受来自约束的消息,name为变量连接器的名称

informant=None#存储此connector的用户设置约束

constraints=[]#存储连接器所参与的所有约束(包含公式得出的匿名约束)

def set_value(source,value):
nonlocal informant
val=connector['val']
if val is None:

#若不存在值,则设置其为value,informant设置为约束(通过用户设置的约束)

informant,connector['val']=source,value
if name is not None:
print(name,'=',value)
#对所有约束均调用new_value
inform_all_except(source,'new_val',constraints)
else:
#若传入了不同的值,则发生矛盾
if val!=value:
print('Contradiction detected:',val,'vs',value)
def forget_value(source):
nonlocal informant
if informant==source:
#若informant和约束名称相同(即密码正确且此前未被forget),则将informant和值归0
informant,connector['val']=None,None
if name is not None:
print(name,'is forgotten')
#对所有约束调用forget_value
inform_all_except(source,'forget',constraints)
connector ={'val':None,#当前值
'set_val':set_value,#表示source请求连接器将当前值设置为该值
'forget':forget_value,#告诉连接器,source请求它忘掉当前值
'has_val':lambda: connector['val'] is not None,#返回连接器是否已经有了一个值
'connect':lambda source:constraints.append(source)}#告诉连接器参与新的约束source
#连接器 字典 key:string literal, value: function
return connector
def inform_all_except(source,message,constraints):
#通知约束来改变所有连接器
for c in constraints:
if c!=source:
c[message]()
在上述函数中,我们为一个连接器取名为name(或者匿名)
Connector Method
set_val(source,value)

由某个来源(这个来源可以是任何东西,比如上文中的‘user’手动添加,也可以是约束本身自动添加),设置连接器变量的值,并且存储来源(目的是使得forget必须要由设置者来进行)

如果先前已经有值,则发生冲突不予改变。

如果先前为空,则调用new_value尝试对整个公式进行补全。

forget(source)

如果来源正确,那么就清空connector的值,并且向所有参与的约束都发出清空通知

has_val()

返回当前是否有值

connect(source)

连接器参与约束

Connector Data Member
informant

存储此connector的用户设置约束(如果是人工设置的话,只要是身份标识即可)

constraints

存储连接器所参与的所有约束(包含公式得出的匿名约束)

Normal Function
inform_all_except(source,message,constraints)

链式反应,发出信息,注意source,不能回发给自己

Converter Part
def make_converter(c,f):
#装配连接器和约束条件
u,v,w,x,y=[make_connector()for _ in range(5)]#五个匿名连接器,9*c=5*(f-32)
multiplier(c,w,u)#c*9=u
multiplier(v,x,u)#v*5=u
adder(v,y,f)#v+32=f
#根据此连接器,c,f可相互转换
constant(w,9)#w:9
constant(x,5)#x:5
constant(y,32)#y:32
def constant(connector,value):
#不施加约束,仅设置连接器的值
constraint={}
connector['set_val'](constraint,value)
return constraint

我们这里把不施加约束({})的connector作为常量,因为这样在forget时就不受影响了。因此我们特意用了一个函数来设置。


这个公式需要翻译一下才能变成我们已经有的三元约束。(9和5分开可能是因为作者想增加一点难度)

multiplier(c,w,u)#c*9=u
multiplier(v,x,u)#v*5=u



这两个公式,实际上就是使得v代表9/5Celsius,从而让两个connector连接到一个上。

adder(v,y,f)


这个公式实际上就是最后的公式。

通过这样几个约束,我们成功搭建了最后的公式。

总结Converter 网络

通过构建的约束网络,我们使得C上的信息通过三层约束的传播到达F,反之亦然,这样子的架构比起单纯的函数映射提供了更多的便利性,同时改变公式的复杂度也只需要构造更大的网络即可。

这一段代码我认为是SICP第二章最精彩的部分,因此复习了一番,也全了我心中的困惑。