【问题标题】:File and directory structure of a r projectr项目的文件和目录结构
【发布时间】:2013-04-13 19:02:13
【问题描述】:

在像 java 这样的通用编程语言中,每个文件通常对应一个类。

我刚开始使用 R。我想构建一个小程序,我想创建一个像这样的特定文件和目录结构

Main.R # the main control script 
MyClass.R # A class that is referenced from within Main.R 
ProcessData.R # Another class that uses an object of MyClass.R as input

所以我想做这样的事情(伪代码):

Main.R

myc <- new MyClass # create a new instance of MyClass from within Main.R
pd <- new ProcessData 
pd$processMyClass( myc ) # call a method in ProcessData that processes the myc object in some way

所以这是相当抽象的,但我只是想知道这在 R 中是否原则上可行。

更新:我需要更具体。因此问题是:如何通过保持与以下玩具程序相同数量的文件和结构将以下 java 程序转换为 R 程序?

Main.java:

public static void main( String[] args ) {
    MyClass myc = new MyClass("SampleWord");
    ProcessData pd = new ProcessData();
    pd.processData( myc );
}

MyClass.java

class MyClass {

    public String word;

    public MyClass( String word ) {
        this.word = word;
    }
}

ProcessData.java

class ProcessData.java {

    public void processData( MyClass myc ) {
        System.out.println( "pd.processData = " + myc.word );
    }

}

【问题讨论】:

  • “这可能吗?”中的this参考?如果这是问题,当然可以将程序的不同部分放在不同的文件中。如果这是问题,参考类将按照您的伪代码行工作。见?setRefClass
  • 请注意,面向对象的编程风格并不是 R 最自然的风格,它具有更多的函数式编程风格。
  • OO 编程对 R 来说太不自然了,以至于它已经实现了大约四五次,而函数式编程风格只用 S 语言本身实现过一次,从那时起几乎没有改变。

标签: r class pseudocode


【解决方案1】:

类系统

查看 R、S3、S4 和 Reference 类中的三个类系统。

## S3 methods, Section 5 of
RShowDoc("R-lang")

## S4 classes
?Classes
?Methods

## Reference classes
?ReferenceClasses

具有 Java 背景的您会很想使用引用类,但它们具有“引用语义”和远距离操作(更改一个对象会更改另一个引用相同数据的对象),而大多数 R 用户期望“复制更改的语义。 S3 课程可以取得很大进步,但我认为更严格的方法会采用 S4。 S4 的特性会让你大吃一惊,部分原因是类系统更接近于普通的 lisp 对象系统而不是 java。

other opinions and options

基本实现

我不太确定您使用“ProcessData”的设计目标是什么;我会将您的两个类实现为一个类、一个泛型和一个在 MyClass 类上运行的泛型方法。

## definition and 'low-level' constructor
.MyClass <- setClass("MyClass", representation(word="character"))

## definition of a generic
setGeneric("processData", function(x, ...) standardGeneric("processData"))

setMethod("processData", "MyClass", function(x, ...) {
    cat("processData(MyClass) =", x@word, "\n")
})

这是完整且功能齐全的

> myClass <- .MyClass(word="hello world")
> processData(myClass)
processData(MyClass) = hello world 

这三个代码行可能放在两个文件中,“AllGenerics.R”和“MyClass.R”(包括方法)或三个文件“AllGenerics.R”、“AllClasses.R”、“processData-methods。 R"(请注意,方法与泛型相关联,并在类上分派)。

其他实现

通常会添加一个对用户更友好的构造函数,例如,向用户提供有关预期数据类型的提示或执行复杂的参数初始化步骤

MyClass <- function(word=character(), ...)
{
    .MyClass(word=word, ...)
}

通常需要一个插槽访问器,而不是直接插槽访问。这可以是一个简单的函数(如图所示),也可以是一个泛型的 + 方法。

word <- function(x, ...) x@word

如果要更新槽,则编写替换函数或方法。函数或方法通常具有三个参数,要更新的对象、可能的附加参数以及更新对象的值。下面是一个泛型的+方法实现

setGeneric("word<-", function(x, ..., value) standardGeneric("word<-"))

setReplaceMethod("word", c("MyClass", "character"), function(x, ..., value) {
    ## note double dispatch on x=MyClass, value=character
    x@word <- value
    x
})

一个有点棘手的替代实现是

setReplaceMethod("word", c("MyClass", "character"), function(x, ..., value) {
    initialize(x, word=value)
})

它使用initialize 泛型和默认方法作为复制构造函数;如果同时更新多个插槽,这会很有效。

由于用户可以看到该类,因此希望使用“显示”方法以用户友好的方式显示它,为此已经存在泛型 (getGeneric("show"))

setMethod("show", "MyClass", function(object) {
    cat("class:", class(object), "\n")
    cat("word:", word(object), "\n")
})

现在我们的用户会话看起来像

> myClass
class: MyClass 
word: hello world 
> word(myClass)
[1] "hello world"
> word(myClass) <- "goodbye world"
> processData(myClass)
processData(MyClass) = goodbye world

效率

R 在向量上有效地工作; S4课程也不例外。所以设计是一个类的每个槽代表一个跨越多行的列,而不是单行的元素。我们期望 slot 'word' 通常包含一个长度远大于 1 的向量,并且操作要作用于向量的所有元素。因此,人们会考虑到这一点来编写方法,例如,将 show 方法修改为

setMethod("show", "MyClass", function(object) {
    cat("class:", class(object), "\n")
    cat("word() length:", length(word(object)), "\n")
})

这里是更大的数据对象(使用我的 Linux 系统上的文件)

> amer <- MyClass(readLines("/usr/share/dict/american-english"))
> brit <- MyClass(readLines("/usr/share/dict/british-english"))
> amer
class: MyClass 
word() length: 99171 
> brit
class: MyClass 
word() length: 99156 
> sum(word(amer) %in% word(brit))
[1] 97423
> amer_uc <- amer  ## no copy, but marked to be copied if either changed
> word(amer_uc) <- toupper(word(amer_uc))  ## two distinct objects

所有这些都非常高效。

参考类“远距离行动”的危害

让我们回到 S4 类的一个更简单的实现,它具有直接的插槽访问,没有花哨的构造函数。这是美国字典和一个副本,已转换为大写

.MyClass <- setClass("MyClass", representation(word="character"))
amer <- .MyClass(word=readLines("/usr/share/dict/american-english"))
amer_uc <- amer
amer_uc@word <- toupper(amer_uc@word)

请注意,我们将amer_uc 大写但不是amer

> amer@word[99 + 1:10]
 [1] "Adana"      "Adar"       "Adar's"     "Addams"     "Adderley"  
 [6] "Adderley's" "Addie"      "Addie's"    "Addison"    "Adela"     
> amer_uc@word[99 + 1:10]
 [1] "ADANA"      "ADAR"       "ADAR'S"     "ADDAMS"     "ADDERLEY"  
 [6] "ADDERLEY'S" "ADDIE"      "ADDIE'S"    "ADDISON"    "ADELA"     

这确实是 R 用户所期待的——我创建了一个单独的对象并对其进行了修改;原始对象未修改。这是我的一个断言;也许我不知道 R 用户期望什么。我假设 R 用户并没有真正注意这是一个引用类这一事实,而是认为它只是另一个 R 对象,例如 integer() 向量或 data.framelm() 的返回值。

相比之下,这是一个引用类的最小实现,以及类似的操作

.MyRefClass <- setRefClass("MyRefClass", fields = list(word="character"))
amer <- .MyRefClass(word=readLines("/usr/share/dict/american-english"))
amer_uc <- amer
amer_uc$word <- toupper(amer_uc$word)

但是现在我们已经更改了ameramer_uc!完全符合 C 或 Java 程序员的期望,但不是 R 用户。

> amer$word[99 + 1:10]
 [1] "ADANA"      "ADAR"       "ADAR'S"     "ADDAMS"     "ADDERLEY"  
 [6] "ADDERLEY'S" "ADDIE"      "ADDIE'S"    "ADDISON"    "ADELA"     
> amer_uc$word[99 + 1:10]
 [1] "ADANA"      "ADAR"       "ADAR'S"     "ADDAMS"     "ADDERLEY"  
 [6] "ADDERLEY'S" "ADDIE"      "ADDIE'S"    "ADDISON"    "ADELA"     

【讨论】:

  • 您能否用一些实际代码扩展此评论:具有 Java 背景的您会很想使用引用类,但这些具有“引用语义”和远距离操作(更改一个对象会更改另一个引用相同数据的对象),而大多数 R 用户期望“更改时复制”语义。
  • @G.Grothendieck 我在最后添加了一个部分。
  • 谢谢。我明白你现在指的是什么。从 R 用户的角度来看,引用类被实现为环境并且行为类似于环境,因此将一个引用类对象分配给另一个引用类对象只会使第二个点指向第一个点,并且不涉及复制对象中的值(如果它是通过列表实现的)。 proto 包也基于环境并且工作方式相同。
【解决方案2】:

参考类 下面我们尝试使用 R 尽可能接近地复制问题中的 java 代码。在这方面,三个内置的 R 类系统(S3、S4、参考类)参考类似乎最接近这种风格。 Reference Classes 是最近添加到 R 中的类系统,它的迅速普及可能是由于熟悉 R 风格的 Java 程序员。

(如果您以此创建一个包,则省略所有源语句。)

Main.R 文件:

source("MyClass.R")
source("ProcessData.R")

main <- function() {
    myc <- new("MyClass", word = "SampleWord")
    pd <- new("ProcessData")
    cat("pd$processData =", pd$processData(myc), "\n")
}

MyClass.R 文件:

setRefClass("MyClass", 
    fields = list(word = "character")
)

ProcessData.R 文件:

setRefClass("ProcessData",
    fields = list(myc = "MyClass"),
    methods = list(
        processData = function(myc) myc$word
    )
)

运行:

source("Main.R")
main()

proto包 proto package实现了面向对象编程的原型模型,它起源于Self编程语言,在某种程度上存在于javascript、Lua中,尤其是io language的基础。 proto 可以很容易地模仿这种风格(如proto vignette 的 Traits 部分所述):

Main.R 文件:

source("MyClass.R")
source("ProcessData.R")  

library(proto)

main <- function() {
    myc <- MyClass$new("SampleWord")
    pd <- ProcessData$new()
    cat("pd$processData =", pd$processData(myc), "\n")
}

MyClass.R 文件:

MyClass <- proto(
    new = function(., word) proto(word = word)
)

ProcessData.R 文件:

ProcessData <- proto(
    new = function(.) proto(.), 
    processData = function(., myc) myc$word
)

运行:

source("Main.R")
main()

更新:添加了原型示例。

更新 2:改进了引用类示例中的 mainMyClass

【讨论】:

    猜你喜欢
    • 2018-11-21
    • 2018-12-26
    • 2016-06-18
    • 1970-01-01
    • 1970-01-01
    • 2013-12-15
    • 1970-01-01
    • 2013-07-11
    • 1970-01-01
    相关资源
    最近更新 更多