如何找到 class 的默认构造方法

How to find the default construstor methods for a class

问题来自试验一个包,发现使用 new(Class = 'ddmatrix', Data = X)ddmatrix(Data = X) 会产生不同的结果,其中 X 是一个矩阵(可以认为 class ddmatrix 是转换后的 Class matrix).

文档

包中定义了一个S4classddmatrixsetGeneric(name = 'ddmatrix') 的通用构造函数。此外,包定义 setMethod('ddmatrix', signature = 'matrix', ...) 如下:

 setMethod("ddmatrix", signature(data="matrix"), 
              function(data, nrow=1, ncol=1, byrow=FALSE, ...
                       bldim=.pbd_env$BLDIM, ICTXT=.pbd_env$ICTXT)
    {
    dim(data) <- NULL
    ret <- ddmatrix(data=data, nrow=nrow, ncol=ncol, byrow=byrow, bldim=bldim, ICTXT=ICTXT)    
    return( ret )
}
)

我很困惑如何在上面的 setMethod('ddmatrix', signature = 'matrix') 步骤中使用方法 ddmatrix。这个 ddmatrix 方法是泛型 ddmatrix 的默认方法吗?

同时,当调用 new('ddmatrix', Data = X) 时,它将调用哪个方法从 matrix 对象构建新的 ddmatrix 对象? new函数为:

function (Class, ...) 
{
    ClassDef <- getClass(Class, where = topenv(parent.frame()))
    value <- .Call(C_new_object, ClassDef)
    initialize(value, ...)
}

问题

要回答new('ddmatrix')ddmatrix() 之间的差异,我认为一种方法是找到默认构造函数。同时,包中还定义了setMethod('ddmatrix', signature = 'vector',...),这是默认的吗?

在某种程度上,这取决于作者。许多人认为 new()@slot()(用于插槽访问)严格针对包开发人员——这些将实现细节直接暴露给用户——并且更喜欢编写构造函数和将接口置于实现之上的访问器。这似乎是您正在考虑的包的情况,其中 ddmatrix() 是面向用户的构造函数。

作者似乎实现了外观模式,其中几种不同的方法在调用另一个函数/方法进行实际对象构造之前进行相对较小的数据转换。从您展示的内容来看,ddmatrix,matrix-method 似乎调用了 ddmatrix,vector-method(因为在 ddmatrix,matrix-method 中函数设置 dim(data) <- NULL,将矩阵转换为向量,然后调用 ddmatrix()现在分派给 vector 方法),并通过 https://github.com/RBigData/pbdDMAT/blob/master/R/constructors.r#L191 处的 new() 构造对象。不同的包作者可能采用不同的设计,其中几个方法分别调用 new().

文档通常也有帮助,例如,?ddmatrix 不讨论通过 new() 直接构建对象。

这是一个更简单的例子。我创建了一个 class "A",其中一个插槽包含一个数字向量

setClass("A", slots=c(x="numeric"))

这里我创建了一个构造函数,因为我希望用户看到 class 的接口,而不是它的实现

A = function(x=numeric())
    new("A", x=x)

至此,A()new("A")return一个结构相同的对象,例如

> new("A")
An object of class "A"
Slot "x":
numeric(0)

> A()
An object of class "A"
Slot "x":
numeric(0)

也许作为 "A" class 的开发者,我希望 class 'A' 的未初始化对象具有 'NA' 作为插槽 x,所以我修改

A = function(x = NA_real_)
    new("A", x=x)

现在直接调用 new() returns 与调用 A()

不同的对象
> new("A")
An object of class "A"
Slot "x":
numeric(0)

> A()
An object of class "A"
Slot "x":
[1] NA

哪个是'correct'?嗯,两者都是正确的,但作为 class 的创建者,我打算让用户通过调用函数 A().[=37 来创建 class "A" 的对象=]

将接口(使用A() 构造对象)与实现(使用new() 构造对象)分开的一个典型原因是实现对用户而言并不明显。 ddmatrix() 函数似乎就是这种情况——出于只有包作者需要知道的原因,将 R 矩阵存储为包含维度信息的向量会很方便。我想一个简单的等价物可能是

setClass("A", slots=c(data="numeric", nrow="integer", ncol="integer"))
A = function(m=matrix(0, 0, 0)) {
    stopifnot(is(m, "matrix"))
    new("A", data=as.vector(m), nrow=nrow(m), ncol=ncol(m))
}

例如

> A(matrix(1:10, 5))
An object of class "A"
Slot "data":
 [1]  1  2  3  4  5  6  7  8  9 10

Slot "nrow":
[1] 5

Slot "ncol":
[1] 2

作者为什么要这样做?作为用户,这对我们来说并不重要。为什么我们不能通过调用 m = matrix(1:10, 5); new("A", data=as.vector(m), nrow=nrow(m), ncol(m)) 创建相同的对象?我们可以,但是当作者决定更改他们的实现以便存储每行开头的偏移量时,我们必须了解作者所做的并更新我们的代码。