小技巧之函数包装
这段时间在尝试进行R的函数化,但是其中遇到一个问题:
函数层太多,全是if,而且函数体就写在控制流里,非常的不美丽,如果是python,可以用import解决,非常方便地将main部分和功能函数分开,但是R里面,虽然能用source解决,但随着函数量的扩大,势必会重新回到这个问题:
如何针对某个参数,切换对应的功能函数?
听上去实际上挺像python的高阶函数的实现,传的是函数参数。
但现实是,在R里面,总不能每次都用source换吧。这要是万一需要source的文件没了,或者名字换了,整个控制流就直接挂了,因为source里有stop语句,一旦source失败则整个流程直接error终止。而且debug非常困难,R不像python,有些debug的信息简直让人抓狂,毕竟不是专门的程序语言,一旦其中某个函数有点问题导致error,结果却要对整个控制流+函数进行全面debug。明明你只是在对函数进行操作,但关系到了整个控制流,因此,将函数级别的操作与文件本身直接挂钩是一个很危险的行为。顺便,天知道source的文件里面有些啥?万一有恶意代码不是直接遭重?
例如之前的差异分析的脚本,里面有方法参数,这个参数有三个可选的数值:
deseq2,limma和edger
分别对应三个不同的差异分析方法,脚本之间互斥,没有任何数据传输,可以说理论上最好的控制应该是,在控制脚本中选择不同的method,然后分别调用不同的独立脚本,而不是在控制脚本中使用三次if,调用三个写在控制脚本里的函数。
这里在参考别的包的时候,发现了一个小东西:
switch:
1 | #switch |
switch本质上就是根据输入的EXPR计算的值,返回后面…(其实就是个list)中对应的值,如果没有对应的则没有返回值。
因此,我们应当这样控制这个函数:
1 | main <- funxtion(...,method = methodlist,...){ |
由此就能将控制函数和功能函数几乎彻底分开,控制函数只用于控制数据流和传参,功能函数只用于进行数据处理和返回结果。
上面的switch部分看上去有点费解,好像跟一开始的switch说明完全不一样啊?其实是一致的,这里利用了list的存储特性和调用特性,R的list能存放几乎任何东西,以及list既可以用索引值调用,也可以用索引名调用。(听上去就像python的对象的概念)
上面的那一段其实等价于下面的过程:
- 创建匿名的list,list里有三个names,分别是deseq2,limma和edger,这三个names对应的值,就是各自的函数脚本,储存在一个匿名的list里.
- 输入了一个EXPR,直接就是一个值:比如deseq2,switch根据这个值进行list的调用,调用list(“deseq2”),也即对应的function并返回。
其实将method参数转换为数值也是可以的,比如1代表deseq2,2代表limma,因为本质上list[1]就等价于list[“deseq2”],但是,全用数字,一个脚本还行,十个八个一百个?还得分开记什么是什么?也不user friendly,无法做到见文知意。
这样用switch有什么好处呢?
那就是,不创建中间函数,直接将函数作为了返回值返回到控制函数的env里面,而不返回到project的env里面。而且不需要在控制函数中加入功能函数的源代码,让控制流更清晰。(R的env层级自行学习,可以简单理解为一个个隔开的容器。)
说到底就是控制函数和功能函数的解耦。这算是一个比较优雅的方案了。