编写健壮的R代码:命名空间,屏蔽和使用`::`运算符

时间:2012-06-08 10:30:01

标签: r coding-style namespaces package masking

短版

对于那些不想阅读我的"案例"的人来说,这就是本质:

  1. 最小化新软件包破坏现有代码的可能性的建议方法是什么,即使您编写的代码尽可能健壮
  2. 什么是在

    时充分利用命名空间机制的推荐方法

    a)只是使用贡献的软件包(比如在一些R分析项目中)?

    b)关于开发自己的包裹?

  3. 如何避免与正式类(在我的情况下大多数是Reference Classes)发生冲突,因为甚至没有与{{1相似的命名空间机制对于类(AFAIU)?


  4. R宇宙的运作方式

    这种情况在我脑海中唠叨了大约两年了,但我并不觉得我已经找到了令人满意的解决方案。而且我觉得情况越来越糟。

    我们在CRANgithubR-Forge等广告中看到了越来越多的软件包,这简直太棒了。

    在这样一个分散的环境中,组成R的代码库很自然(让我们说基础R 贡献R ,为简单起见,将在健壮性方面偏离理想状态:人们遵循不同的惯例,那里有S3,S4,S4参考类等。事情不能像#34;对齐&# 34;因为如果有一个" 中央清算实例"强制执行的惯例。没关系。

    问题

    鉴于上述情况,使用R编写健壮的代码可能非常困难。并非您需要的所有内容都在基础R中。对于某些项目,您最终会加载相当多的贡献包。

    恕我直言,在这方面最大的问题是命名空间概念在R中使用的方式:R允许简单地写出某个函数/方法的名称而不明确要求它的命名空间(即::foo

    因此,为了简单起见,这是每个人都在做的事情。但是这样,名称冲突,破坏代码以及重写/重构代码的需要只是时间问题(或者加载的不同包的数量)。

    充其量,您将知道有关新添加的软件包屏蔽/重载哪些现有功能。在最坏的情况下,在您的代码中断之前,您将不知道。

    几个例子:

    • 尝试同时加载RMySQLRSQLite,他们不能很好地进行
    • RMongo也会覆盖RMySQL
    • 的某些功能
    • forecast掩盖了与ARIMA相关的功能的许多内容
    • R.utils甚至会屏蔽namespace::foo例程

    (我无法回想起哪些功能特别导致了这些问题,但如果有兴趣,我愿意再次查询)

    令人惊讶的是,这似乎并没有给很多程序员带来麻烦。我试图在r-devel几次提高兴趣,但没有显着效果。

    使用base::parse运算符

    的缺点
    1. 使用::运算符可能会在某些情况下严重影响效率,如Dominick Samperi pointed out
    2. 开发您自己的软件包时,您甚至无法在自己的代码中使用::运算符,因为您的代码还没有真正的软件包,因此还有还没有命名空间。因此,我必须首先坚持::方式,构建,测试,然后返回将所有内容更改为foo。不是真的。
    3. 避免这些问题的可能解决方案

      1. 将每个包中的每个函数重新分配给符合某些命名约定的变量,例如: namespace::foo以避免与namespace..foo相关的效率低下(我概述了here)。优点:它的工作原理。缺点:它笨拙,你使用的内存加倍。
      2. 在开发包时模拟命名空间。 AFAIU,这是不可能的,至少我是told so back then
      3. 强制强制使用namespace::foo。恕我直言,这将是最好的事情。当然,我们会失去一些简单性,但R宇宙再也不再那么简单了(至少它不像00年代早期那么简单)。
      4. 那些(正式)课程呢?

        除了上述方面之外,namespace::foo方式对于函数/方法也很有效。但是类定义呢?

        将程序包timeDate与其::类一起使用。说另一个包也有一个类timeDate。我不知道如何从两个包中的任何一个明确声明我想要一个新的类timeDate实例。

        这样的东西不起作用:

        timeDate

        这可能是一个巨大的问题,因为越来越多的人为他们的R包切换到OOP风格,导致许多类定义。如果一种显式地解决类定义的命名空间的方法,我非常感谢指针!

        结论

        虽然这有点冗长,但我希望我能够指出核心问题/问题,并且我可以在这里提高认识。

        我认为devtoolsmvbutils确实有一些值得传播的方法,但我确信还有更多话要说。

2 个答案:

答案 0 :(得分:31)

很棒的问题。

验证

编写健壮,稳定且适合生产的R代码很难。你说:“令人惊讶的是,这似乎并没有打扰很多程序员。”那是因为大多数R程序员都没有编写生产代码。他们正在进行一次性的学术/研究任务。我会严重质疑任何声称R很容易投入生产的编码员的技能。除了我已经链接到的关于搜索/查找机制的帖子,我还写了一篇关于warning的危险的帖子。这些建议有助于降低生产代码的复杂性。

编写健壮/生产R代码的提示

  1. 避免使用Depends的包,并使用Imports包。只有插入Imports的依赖包才能完全安全使用。如果您绝对必须使用使用Depends的套餐,请在致电install.packages()后立即通过电子邮件发送给作者。
  2. 以下是我告诉作者的内容:“嗨,作者,我是XYZ包的粉丝。我想提出请求。你能否在下次更新时将ABC和DEF从Depends转移到Imports?我无法添加你的软件包到我自己的软件包的Imports,直到这种情况发生.R 2.14为每个软件包强制执行NAMESPACE,来自R Core的一般信息是软件包应该尝试成为“好公民”。如果我必须加载一个Depends软件包,它会添加一个重要的负担:每次我依赖一个新包装时都要检查是否存在冲突。使用Imports,包装没有副作用。我知道你可能会破坏其他人的包装。我认为它是正确的为证明对Imports的承诺而做的事情,从长远来看,它将帮助人们生成更强大的R代码。“

    1. 使用importFrom。不要将整个包添加到Imports,只添加您需要的那些特定功能。我用Roxygen2函数文档和roxygenize()自动生成NAMESPACE文件。通过这种方式,您可以导入两个冲突不存在于实际需要使用的函数中的包。这很乏味吗?直到它成为一种习惯。好处:您可以快速识别所有第三方依赖项。这有助于......

    2. 不要盲目升级软件包。逐行阅读更改日志,并考虑更新将如何影响您自己的软件包的稳定性。大多数情况下,更新不会触及您实际使用的功能。

    3. 避免使用S4课程。我在这里挥手。我发现S4很复杂,它需要足够的脑力来处理R功能方面的搜索/查找机制。你真的需要这些OO功能吗?管理状态=管理复杂性 - 留给Python或Java =)

    4. 编写单元测试。使用testthat包。

    5. 无论何时R CMD构建/测试您的包,解析输出并查找NOTE,INFO,WARNING。另外,用自己的眼睛进行物理扫描。构建步骤的一部分记录了冲突,但没有附加WARN等。

    6. 在调用第三方软件包后立即添加断言和不变量。换句话说,不要完全相信别人给你的东西。如果结果是意外的,请稍微探查一下结果stop()。你不必发疯 - 选择一两个暗示有效/高可信度结果的断言。

    7. 我认为还有更多,但现在已成为肌肉记忆=)如果有更多的话,我会增加。

答案 1 :(得分:15)

我接受它:

总结:灵活性需要付出代价。我愿意支付这个价格。

1)我根本不使用会导致这类问题的软件包。如果我真的需要在我自己的包中使用该包中的函数,我在importFrom()文件中使用NAMESPACE。无论如何,如果我遇到包装问题,我会联系包裹作者。问题出在他们身边,而不是R's。

2)我从不在自己的代码中使用::。通过仅导出我的包的用户所需的函数,我可以将自己的函数保存在NAMESPACE中而不会遇到冲突。未导出的函数也不会隐藏具有相同名称的函数,因此这是双赢。

有关您在此处找到的确切环境,命名空间和工作方式的详细指南: http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/

对于每个人都在编写包等等,这绝对是必读的。阅读本文后,您将意识到在包裹代码中使用::是不必要的。