Эффективный способ определить класс с несколькими, необязательно пустыми слотами в S4 of R? - PullRequest
4 голосов
/ 01 декабря 2011

Я создаю пакет для обработки данных, поступающих с 4 различными типами.Каждый из этих типов является допустимым классом в форме матрицы, data.frame или дерева.В зависимости от того, как обрабатываются данные и других экспериментальных факторов, некоторые из этих компонентов данных могут отсутствовать, но все же чрезвычайно полезно иметь возможность хранить эту информацию как экземпляр специального класса и иметь методы, которые распознают другой компонентdata.

Подход 1:

Я экспериментировал со структурой инкрементного наследования, которая выглядит как вложенное дерево, где каждая комбинация типов данных имеет свой явно определенный класс.Кажется, что в будущем это будет сложно расширить для дополнительных типов данных, а также для новых разработчиков будет сложно выучить все имена классов, как бы хорошо они ни были организованы.

Подход 2:

Второй подход заключается в создании единого «мастер-класса», который включает в себя слот для всех 4 типов данных.Чтобы позволить слотам иметь значения NULL для экземпляров отсутствующих данных, необходимо сначала определить объединение виртуального класса между классом NULL и новым классом типа данных, а затем использовать объединение виртуального класса в качестве ожидаемого класса.за соответствующий слот в мастер-классе.Вот пример (при условии, что каждый класс типов данных уже определен):

################################################################################
# Use setClassUnion to define the unholy NULL-data union as a virtual class.
################################################################################    
setClassUnion("dataClass1OrNULL", c("dataClass1", "NULL"))
setClassUnion("dataClass2OrNULL", c("dataClass2", "NULL"))
setClassUnion("dataClass3OrNULL", c("dataClass3", "NULL"))
setClassUnion("dataClass4OrNULL", c("dataClass4", "NULL"))
################################################################################
# Now define the master class with all 4 slots, and 
# also the possibility of empty (NULL) slots and an explicity prototype for
# slots to be set to NULL if they are not provided at instantiation.
################################################################################
setClass(Class="theMasterClass", 
    representation=representation(
        slot1="dataClass1OrNULL",
        slot2="dataClass2OrNULL",
        slot3="dataClass3OrNULL",
        slot4="dataClass4OrNULL"),
    prototype=prototype(slot1=NULL, slot2=NULL, slot3=NULL, slot4=NULL)
)
################################################################################

Таким образом, вопрос можно перефразировать следующим образом:

Существуют ли более эффективные и / или гибкие альтернативы какому-либо изэти подходы?

Этот пример изменен с ответа на ТАК вопрос об установке значения слота по умолчанию равным NULL .Этот вопрос отличается тем, что мне интересно знать лучшие варианты в R для создания классов со слотами, которые могут быть пустыми при необходимости, несмотря на то, что во всех других непустых случаях требуется определенный сложный класс.

1 Ответ

2 голосов
/ 01 декабря 2011

По моему мнению ...

Подход 2

Это своего рода побеждает цель принять формальную систему классов, а затем создать класс, который содержит плохо определенные слоты ('A 'или NULL).Как минимум, я бы попытался сделать так, чтобы DataClass1 имел значение, подобное NULL.В качестве простого примера здесь по умолчанию используется числовой вектор нулевой длины.

setClass("DataClass1", representation=representation(x="numeric"))
DataClass1 <- function(x=numeric(), ...) {
    new("DataClass1", x=x, ...)
}

Тогда

setClass("MasterClass1", representation=representation(dataClass1="DataClass1"))
MasterClass1 <- function(dataClass1=DataClass1(), ...) {
    new("MasterClass1", dataClass1=dataClass1, ...)
}

Одним из преимуществ этого является то, что методы не должны проверять, является лиЭкземпляр в слоте имеет значение NULL или 'DataClass1'

setMethod(length, "DataClass1", function(x) length(x@x))
setMethod(length, "MasterClass1", function(x) length(x@dataClass1))

> length(MasterClass1())
[1] 0
> length(MasterClass1(DataClass1(1:5)))
[1] 5

В ответ на ваш комментарий о предупреждении пользователей при доступе к «пустым» слотам и о том, что пользователи обычно хотят, чтобы функции что-то сделали, а не сказали им, что ониесли вы делаете что-то не так, я бы, вероятно, вернул пустой объект DataClass1(), который точно отражает состояние объекта.Возможно, метод show предоставит обзор, который укрепит статус слота - DataClass1: нет.Это представляется особенно уместным, если MasterClass1 представляет собой способ координации нескольких различных анализов, из которых пользователь может выполнять только некоторые.

Ограничением этого подхода (или вашего подхода 2) является то, что вы не получаете диспетчеризацию метода- вы не можете написать методы, которые подходят только для экземпляра с DataClass1 экземплярами, которые имеют ненулевую длину и вынуждены выполнять какую-то ручную диспетчеризацию (например, с if или switch).Это может показаться ограничением для разработчика, но это также относится и к пользователю - пользователь не понимает, какие операции являются уникально подходящими для экземпляров MasterClass1, имеющих экземпляры DataClass1 ненулевой длины.

Подход 1

Когда вы говорите, что имена классов в иерархии вводят в заблуждение вашего пользователя, кажется, что это, возможно, указывает на более фундаментальную проблему - вы тоже пытаетесьтрудно сделать всестороннее представление типов данных;пользователь никогда не сможет отслеживать ClassWithMatrixDataFrameAndTree, поскольку он не отображает способ просмотра данных.Это, возможно, возможность уменьшить свои амбиции, чтобы по-настоящему заняться только наиболее заметными частями области, которую вы исследуете.Или, возможно, возможность переосмыслить, как пользователь может думать и взаимодействовать с собранными им данными, и использовать отделение интерфейса (то, что видит пользователь) от реализации (как вы решили представлять данные вклассы), предоставляемые системами классов для более эффективной инкапсуляции того, что пользователь, вероятно, будет делать.

Если оставить в стороне наименование и количество классов, когда вы говорите «трудно расширять для дополнительных типов данных в будущем», это делаетмне интересно, возможно, некоторые нюансы классов S4 сбивают вас с толку?Короткое решение состоит в том, чтобы избежать написания собственных методов initialize и полагаться на конструкторы для выполнения хитрой работы в соответствии с

setClass("A", representation(x="numeric"))
setClass("B", representation(y="numeric"), contains="A")

A <- function(x = numeric(), ...) new("A", x=x, ...)
B <- function(a = A(), y = numeric(), ...) new("B", a, y=y, ...)

, а затем

> B(A(1:5), 10)
An object of class "B"
Slot "y":
[1] 10

Slot "x":
[1] 1 2 3 4 5
...