简介
对于那些不想读完我的“案例”的人,这是要点:
- 在编写代码时,如何最大程度地使您编写的代码尽可能健壮,以最小化新软件包破坏现有代码的机会?
在以下情况下,使用 命名空间机制 的最佳方式是什么:
a)仅在某个 R 分析项目中使用贡献软件包时?
b)在开发自己的软件包方面?
如何避免与正式类(在我的情况下主要是Reference Classes)冲突,因为甚至没有用于类的可比命名空间机制
::
?
R语言的工作方式
这件事情已经在我脑海里萦绕了两年,但我感觉自己还没有得出一个令人满意的解决方案。而且我觉得这种情况正在变得更糟。
我们看到越来越多的CRAN、github和R-Forge等平台上发布的包,这简直太棒了。
在这样一个分散的环境中,R语言代码库(我们称之为base R和contributed R)难以做到与稳健性相关的理想状态:人们遵循不同的约定,有S3、S4、S4参考类等等。如果有一个“中央清算实例”强制执行约定,事情就不可能像现在这样“对齐”。但这没关系。
问题
考虑到上述情况,使用R编写健壮代码可能非常困难。并非所有所需内容都包含在基本R中。对于某些项目,您最终将加载相当多的贡献软件包。在这方面,我认为最大的问题是R中命名空间概念的使用方式:R允许仅写入某个函数/方法的名称而不明确要求其命名空间(即
foo
vs. namespace::foo
)。因此,出于简单起见,每个人都这样做。但是,这种方式会导致名称冲突、代码错误以及需要重写/重构代码只是时间问题(或加载的不同软件包数量问题)。
最好的情况下,您将了解新添加的软件包掩盖/重载了哪些现有函数。最糟糕的情况下,您将毫无头绪,直到您的代码崩溃。
以下是一些例子:
尝试同时加载RMySQL和RSQLite,它们不太兼容。 另外RMongo将覆盖一些RMySQL的函数。 forecast在ARIMA相关函数方面掩盖了很多东西。 R.utils甚至掩盖了
base::parse
例程。
(我无法回忆起具体是哪些函数导致问题,但如果有兴趣,我愿意再查一下。)
令人惊讶的是,这似乎并没有影响到很多程序员。我曾经在r-devel上多次提出过这个问题,但并没有引起多大关注。
::
操作符的缺点
- 使用
::
操作符可能会在某些情况下显著降低效率,正如Dominick Samperi 指出的那样。 - 在开发自己的软件包时,您甚至不能在整个代码中使用
::
操作符,因为您的代码还不是真正的软件包,因此还没有命名空间。所以我必须一开始就坚持使用foo
方式,构建、测试,然后再改回namespace::foo
。这不是很好。
避免这些问题的可能解决方案
- 重新分配每个包中的每个函数到遵循特定命名约定的变量,例如使用
namespace..foo
来避免与namespace::foo
相关的低效率问题(我在这里链接中曾经概述过)。优点:它能够工作。缺点:它很笨拙,并且会使内存使用量加倍。 - 模拟在开发包时使用命名空间。据我所知,这实际上是不可能的,至少当时有人告诉我是这样的链接。
- 强制使用
namespace::foo
是必需的。在我看来,这将是最好的做法。当然,我们可能会失去一些简单性,但是R的世界已经不再像00年代初那样简单了(至少不再像那时那么简单)。
那么(正式的)类呢?
除了上述描述的方面,::
的方式对于函数/方法非常有效。但是类定义呢?以包timeDate及其类
timeDate
为例。假设另一个包也有一个名为timeDate
的类。我不知道如何明确地声明我想要从这两个包中的哪一个获取timeDate
类的新实例。像这样的东西是行不通的:
new(timeDate::timeDate)
new("timeDate::timeDate")
new("timeDate", ns="timeDate")
随着越来越多的人转向面向对象编程风格来开发他们的R包,导致了大量的类定义,这可能会成为一个巨大的问题。如果有一种明确地解决类定义命名空间的方法,我将非常感激您的指引!
结论
尽管这篇文章有点冗长,但我希望能够指出核心问题/疑问,并在此处引起更多关注。
f <- dplyr::filter
”这样的解决方案不能同时获取所需功能并防止掩盖(在这种情况下,是来自stat软件包的filter)? - d8aninja::
运算符会引入太多开销,同时仍然希望绝对确保掩码不会妨碍你。 - Rappster