从点缀器已经开始
很多年前,我去一间叫“Sorholus”的子公司复试,当她们的辩手晓得我是用Python做为组织工作词汇的这时候,问了我两个难题:小伙,你晓得Python的点缀器是是不是写吗?
虽说,对彼时这个全然要学函数式的我而言,多于满脸懵逼能比喻。
她们先从两个差强人意看似的greeting表达式已经开始。
def greeting(): print(“hello, world!”)这个表达式所做的,而已向萤幕输入句“hello, world!”,就谁知了。所以,假如她们期望在这个表达式继续执行其间,做许多附加的操作方式,比如说打印表达式的费时,能什么样同时实现呢?
用Java的后端爸爸妈妈可能将第三化学反应透过Spring + AOP,在绕来绕去一大圈后,搞掂这个难题。而在Python里,她们能用这种两个“法术”很单纯的搞掂:
def around(fn): def wrapper(*args, **kwargs): print(“before method call, do something…”) result = fn(*args, **kwargs) print(“after method call, do something…”) return result return wrapper @around def greeting(): print(“hello, world”) >>> greeting() before method call, do something… hello, world after method call, do something…呢很单纯?粘冠换两个表达式试试?
@around def incr(n): return n + 1 >>> ret = incr(1) before method call, dosomething… after method call,do something… >>> ret 2OK,也没有难题。
这篇文章不是讨论Python的点缀器的。所以关于点缀器细节就不再展开了。这里她们重点关注一下around这个表达式。在这个表达式的内部又定义了两个wrapper表达式,最终around把这个wrapper给返回了。实际上,这里就用到了“旋量群”的概念。
加法器工厂
这里她们再讲两个小例子。
假设她们有两个加法器工厂,生产的加法器能继续执行这种的操作方式。比如说说adder1,接受两个Int类型的参数,返回两个该参数加1后的值。这次她们换成Scala来写:
val adder1 = (x: Int) => x + 1 >>> adder1(100) Int =101假如她们要粘冠两个adder100加法器呢?
val adder100 = (x: Int) => x + 100实际上,这两者的代码骨架几乎是一模一样的,基本上都是(x: Int) => x + aNum,那何不索性定义两个加法器工厂,把这些逻辑封装起来?比如说像下面这种。
def adderFactory(n: Int) = { (x: Int) => x + n } val adder1 = adderFactory(1) val adder100 = adderFactory(100)为了方便理解,这里给出等价的Python代码是这种婶儿的:
def adderFactory(n): return lambda x: x + n为了更方便理解,再返璞归真一点,用类似上一小节写法,代码是这种婶儿的:
def adderFactory(n): def adder(x): returnx + nreturn adder实际上,上面的addFacory所返回的就是两个“旋量群”。
旋量群的正式定义
这里我给出两个我对旋量群的正式定义:
旋量群是带有自由变量的表达式。
这里面又牵扯到两个新的名词:自由变量(free variable)。
所谓的自由变量是和绑定变量相对应的一种称呼,在加法器工厂的例子里,变量n是自由的,而变量x是绑定(bound)的。
自由和绑定是都是相对加法器内部的这个表达式而言的,无论这个函数是以表达式字面量的形式存在,还是以def adder这种的形式存在。
在表达式定义中,她们能晓得x是来自于表达式的入参,而对n却一无所知,因此她们说n是自由的,x是绑定的。
这里对熟悉Java的爸爸妈妈们,能用匿名内部类近似等价的去理解一下。
class Adder { int n; Adder(n) { this.n = n; } int add(int x) { returnn + x; } }Adder类有两个成员变量n,和两个add方法,工厂方法返回两个旋量群的这时候,约等于new了两个Adder的实例。
为什么需要旋量群
这个难题其实能从理论和工程两个方面来回答。
从工程的角度而言,假如词汇层面有了旋量群这种的好东西,她们能不用再费劲的去同时实现许多设计模式了。关于这件事,在很早的这时候Peter Norvig就说过一段名言:
设计模式某种意义上是为解决面向对象词汇(彼时主要是指 C++ )本身缺陷的一种权宜之计。在 Lisp 这种的动态(表达式)词汇中,由于不需要管理类和对象,不需要解决类给设计上带来的限制, GoF 的 23 种模式中,有 16 种要么用不着,用词汇本身提供的机制就可以了,要么同时实现起来要单纯得多。比如说,Factory 和 Singleton 能用 MetaClass 同时实现, Factory 和 Command 能用旋量群同时实现,等等。
理论底座,没有了它们,表达式式编程会变得寸步难行。
本来还想再写一点关于Scala和Java的旋量群的对比以及其中的坑的,但是文章已经接近2000字了,我估计已经没有人看到最后了。
假如点赞、评论超过个位数,她们下期粘冠填坑!