diff --git "a/2024.2.3-Matrller-\346\224\276\345\234\250unit\344\270\255\347\232\204\345\207\275\346\225\260.md" "b/2024.2.3-Matrller-\346\224\276\345\234\250unit\344\270\255\347\232\204\345\207\275\346\225\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..36699461ebefea05585886f121164cca5697dfe1 --- /dev/null +++ "b/2024.2.3-Matrller-\346\224\276\345\234\250unit\344\270\255\347\232\204\345\207\275\346\225\260.md" @@ -0,0 +1,332 @@ +# 放在unit中的函数 + +## 前置知识 + +从调用作用域来看,洛书有2类函数,一类在全局作用域中被调用,一类在unit中被调用。这两者的行为并不相同。 + +如下面代码所示 + +```c +import "stdlib" + +def fun1(arg1, arg2): + print("arg1:", arg1) + print("arg2:", arg2) +; + +var u = {} + +def u.fun2(arg1, arg2): + print("arg1:", arg1) + print("arg2:", arg2) +; + +print("使用fun1") +fun1(1, 2) +print() + +print("使用fun2") +u.fun2(1, 2) +print() + +""" + 至此,fun1和fun2的行为都相同 + 现在将fun1和fun2交换 +""" + +var mid = u.fun2 +var u.fun2 = fun1 +fun1 = mid + +print("使用交换后fun1") +fun1(1, 2) +print() + +print("使用交换后fun2") +u.fun2(1, 2) +print() + +""" + 发现了吗,参数错位了 +""" + +``` + +对于在unit中定义的函数,如函数二,它其实有一个隐藏参数:this。同样的,调用在unit中的函数,它的第一个参数会被赋this,即函数所在unit。 + +## 分析 + +我们改写一下上面的两个函数,就可以明白这个原理。 + +```c +import "stdlib" + +""" + fun2被改写过了 +""" + +def fun1(arg1, arg2): + print("arg1:", arg1) + print("arg2:", arg2) +; + +var u = {} + +u.fun2 = def(this, arg1, arg2): + """ + 使用def直接在unit中定义,过程等价于这种赋值方式 + """ + print("arg1:", arg1) + print("arg2:", arg2) +; + +print("使用fun1") +fun1(1, 2) +print() + +""" + 调用的时候,fun2中定义的this参数会被传入函数所在unit + 也就是说,下面两种写法是等价的 + 1: + u.fun2(1, 2) + + 2: + var f = u.fun2 + f(u, 1, 2) + f = null + 不管fun2的功能如何,这两个函数在结果上是等价的, + 但是第二种写法性能要低些。此处举例是为了辅助理解。 +""" + +print("使用fun2") +u.fun2(1, 2) +print() + +""" + 至此,fun1和fun2的行为都相同 + 现在将fun1和fun2交换 +""" + +var mid = u.fun2 +var u.fun2 = fun1 +fun1 = mid + +print("使用交换后fun1") +fun1(1, 2) +print() + +print("使用交换后fun2") +u.fun2(1, 2) +print() + +""" + 发现了吗,参数错位了 +""" + +``` + +## 在全局或局部作用域中使用unit中函数 + +了解这个原理,不仅可以在定义函数时避免混淆,引起不易排查的bug,还可以使用点魔法: + +```c +import "stdlib" + +var clock = time.clock + +""" + 测试time.clock的性能 +""" + +var t = time.clock() + +for i=1, 10000000: + time.clock() +; + +print("time.clock:", time.clock() - t) + +""" + 测试clock的性能 +""" + +var t = time.clock() + +for i=1, 10000000: + clock() +; + +print("clock:", time.clock() - t) + +""" + 在本人的机子上,clock比time.clock少用约0.2秒 + 虽然确实微不足道(狗头) +""" + +``` + +这个例子其实不太恰当,但是stdlib里面这个例子暂时是最好的。因为clock比time.clock少了unit的访问时间,所以性能有所提高。对于放在unit中的深层函数,可以用这种原理来传参提速。 + +看一个自己写的例子 + +```c +import "stdlib" + +var a = { + b = { + c = { + d = { + e = { + f = def(this, arg1, arg2): + return arg1 + arg2 + ; + } + } + } + } +} + +var f = a.b.c.d.e.f + +""" + 测试a.b.c.d.e.f的性能 +""" + +var t = time.clock() +var result1 + +for i=1, 100000000: + result1 = a.b.c.d.e.f(1, 2) +; + +print("a.b.c.d.e.f:", time.clock() - t) + +""" + 测试f的性能 +""" + +var t = time.clock() +var result2 + +for i=1, 100000000: + result2 = f(null, 1, 2) +; + +print("f:", time.clock() - t) +print("result1:", result1, " result2:", result2) + +""" + 在本人的机子上,f比a.b.c.d.e.f少用约6秒 + 并且两者的结果是一样的。 +""" + +``` +对于放在深层unit中的函数,如果不使用this指针,可以放入全局或局部作用域中, +然后通过手动占用this指针确保传参正确。 + +但是对于使用this指针的函数,这个方法就不奏效了。当然,也可以将unit的层数降低: + +```c +import "stdlib" + +var a = { + b = { + c = { + d = { + e = { + sum = 0, + f = def(this, arg1, arg2): + this.sum = this.sum + arg1 + arg2 + return this.sum + ; + } + } + } + } +} + +var e = { + sum = 0, + f = a.b.c.d.e.f +} + +""" + 测试a.b.c.d.e.f的性能 +""" + +var t = time.clock() +var result1 + +for i=1, 100000000: + result1 = a.b.c.d.e.f(1, 2) +; + +print("a.b.c.d.e.f:", time.clock() - t) + +""" + 测试e.f的性能 +""" + +var t = time.clock() +var result2 + +for i=1, 100000000: + result2 = e.f(1, 2) +; + +print("e.f:", time.clock() - t) +print("result1:", result1, " result2:", result2) + +""" + 在本人的机子上,f比a.b.c.d.e.f少用约6秒 + 并且两者的结果是一样的。 +""" + + +``` + +这种方法应用条件有点苛刻,因为用户并不知道代码底层是怎么运作的, +这样写容易造成难以排查的bug。不过,如果是自己的项目,由于对代码了解充分, +倒是可以使用一下这种魔法。 + +## 在unit中使用全局或局部作用域中函数 + +从性能的角度上,不太推荐。不过把函数放到unit中,确实有利于变量名管理。 + +在unit中使用全局或局部作用域中函数,其基本方法就是套壳。即再写一个函数,调用这个函数。 + +```c +import "stdlib" + +var u = {} + +def fun1(arg1, arg2): + print(arg1 + arg2) +; + +def u.fun2(arg1, arg2): + fun1(arg1, arg2) +; + +``` + +这样是其中一种写法,如果想废弃全局或局部作用域中的函数名,可以直接把该函数放到unit中。 + +```c +import "stdlib" + +var u = {} + +def fun1(arg1, arg2): + print(arg1 + arg2) +; + +u.__fun1__ = fun1 + +def u.fun2(arg1, arg2): + this.fun1(arg1, arg2) +; + +fun1 = null #这行代码是为了展示fun1已经可以废弃,可以删去 + +```