Структура файлов и каталогов проекта r


В распространенных языках программирования, таких как 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

Поэтому я хотел бы сделать что-то вроде этого (псевдокод):

Главная.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, сохранив такое же количество файлов и структуру следующей игрушечной программы?

Главная.java:

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

Микласс.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 );
    }

}
2 5

2 ответа:

Ссылочные классы ниже мы попытаемся воспроизвести код java в вопросе, используя R настолько близко, насколько это возможно. В этом отношении из трех встроенных в R-класс систем (S3, S4, Reference Classes) референсные классы кажутся наиболее близкими к этому стилю. Reference Classes - это самая последняя система классов, добавленная в R, и ее быстрое освоение может быть связано с тем, что в R приходят Java-программисты, знакомые с этим стилем.

(Если вы создаете пакет из этого, то опустите все исходные заявления.)

Главная.Файл R:

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

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

Микласс.Файл 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 package пакетproto package реализует прототипную модель объектно-ориентированного программирования, которая возникла из языка самопрограммирования и существует в некоторой степени в javascript, Lua и, в частности, является основой языкаio . прото может легко подражать этому стилю (как обсуждалось в разделе черт протовиньетки):

Главная.Файл 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")
}

Микласс.Файл 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()

Обновление: добавлен пример proto.

Обновление 2: улучшено main и MyClass в Примере ссылочного класса.

Системы

Проверьте три системы классов в R, S3, S4 и ссылочных классах.

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

## S4 classes
?Classes
?Methods

## Reference classes
?ReferenceClasses

С фоном Java вы будете испытывать искушение пойти с ссылочными классами, но они имеют "ссылочную семантику" и действие на расстоянии (изменение одного объекта изменяет другой, который ссылается на те же данные), в то время как большинство пользователей R ожидают семантику "копировать при изменении". Можно сделать большой прогресс с классами S3, но более дисциплинированный подход, на мой взгляд, принял бы S4. Особенности S4 удивит вас, отчасти потому, что система классов ближе к общей объектной системе lisp, чем к java.

Есть другие мнения и варианты.

Базовая реализация

Я не совсем уверен, какова ваша цель проектирования с помощью 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 "(включая метод) или три файла " AllGenerics.R", " Все Классы.R", " processData-методы.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
})

Несколько хитрая альтернативная реализация is

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

, который использует универсальный метод initialize и метод по умолчанию в качестве конструктора копирования; это может быть эффективно, если обновить несколько слотов одновременно.

Поскольку класс виден пользователям, его нужно отобразить удобным для пользователя способом с помощью метода "show", для которого уже существует универсальный (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 не являются исключением. Так что ... дизайн заключается в том, что каждый слот класса представляет собой столбец, охватывающий несколько строк, а не элемент одной строки. Мы ожидаем, что слот "слово" обычно содержит вектор длины, намного превышающей 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.frame или возвращаемое значение lm().

Напротив, здесь минимальная реализация ссылочного класса и аналогичные операции

.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)
Но теперь мы изменили и amer, и amer_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"