Как предотвратить использование функций, загрязняющих глобальное пространство имен?

Мой проект R становится все более сложным, и я начинаю искать какую-либо конструкцию, эквивалентную классам в Java/С#, или модули на python, так что мое глобальное пространство имен не будет завалено функциями, которые никогда не будут используется вне одного файла .r.

Итак, я думаю, мой вопрос: насколько можно ограничить объем функций в пределах определенного файла .r или аналогичного?

Я думаю, что могу просто сделать весь файл .r в одну гигантскую функцию и поместить туда функции, но это бесполезно с эхом:

myfile.r:

myfile <- function() {
    somefunction <- function(a,b,c){}
    anotherfunction <- function(a,b,c){}

    # do some stuff here...
    123
    456
    # ...
}
myfile()

Вывод:

> source("myfile.r",echo=T)

> myfile <- function() {
+     somefunction <- function(a,b,c){}
+     anotherfunction <- function(a,b,c){}
+ 
+     # do some stuff here...
+     # . .... [TRUNCATED] 

> myfile()
> 

Вы можете видеть, что "123" не печатается, хотя мы использовали echo=T в команде source.

Мне интересно, есть ли какая-то другая конструкция, которая более стандартная, поскольку помещение всего внутри одной функции не звучит как что-то действительно стандартное? Но возможно? Кроме того, если это означает, что echo=T работает, это для меня определенный бонус.

Ответ 1

Во-первых, как сказал @Spacedman, вам будет лучше всего подан пакет, но есть и другие варианты.

Методы S3

R оригинальная "объектная ориентация" называется S3. Большая часть базы кода R использует эту конкретную парадигму. Это то, что делает работу plot() для всех видов объектов. plot() является общей функцией, а команда R Core Team и разработчики пакетов могут и написали свои собственные методы для plot(). Строго эти методы могут иметь имена типа plot.foo(), где foo - это класс объекта, для которого функция определяет метод plot(). Красота S3 заключается в том, что вам (вряд ли) когда-либо нужно знать или вызывать plot.foo(), вы просто используете plot(bar), а R выдает метод plot() для отправки на основе класса объекта bar.

В ваших комментариях к вашему Вопросу вы упомянули, что у вас есть функция populate(), которая имеет методы (по существу) для классов "crossvalidate" и "prod", которые вы храните в отдельных файлах .r. Способ S3 для этого:

populate <- function(x, ...) { ## add whatever args you want/need
    UseMethod("populate")
}

populate.crossvalidate <-
    function(x, y, z, ...) { ## add args but must those of generic
    ## function code here
}

populate.prod <-
    function(x, y, z, ...) { ## add args but must have those of generic
    ## function code here
}

Данный некоторый объект bar с классом "prod", вызывающий

populate(bar)

приведет к вызову R, вызывающему populate() (общий), затем он ищет функцию с именем populate.prod, потому что это класс bar. Он находит наш populate.prod() и так отправляет, что функция передает ему аргументы, которые мы изначально указали.

Итак, вы видите, что вы ссылаетесь только на методы, используя имя общего, а не полное имя функции. R выдает вам, какой метод нужно вызывать.

Два метода populate() могут иметь очень разные аргументы, за исключением того, что строго они должны иметь те же аргументы, что и общая функция. Поэтому в приведенном выше примере все методы должны иметь аргументы x и .... (Существует исключение для методов, которые используют объекты формулы, но здесь не нужно беспокоиться об этом.)

Пространства имен пакетов

Так как R 2.14.0, все пакеты R имели собственное пространство имен, даже если один из них не был предоставлен автором пакета, хотя пространства имен были вокруг намного дольше в R, чем это.

В вашем примере мы хотим зарегистрировать общий набор populate() и два метода с системой S3. Мы также хотим экспортировать общую функцию. Обычно мы не хотим или не должны экспортировать отдельные методы. Итак, поместите свои функции в файлы .r в папке R источников пакетов, а затем на верхнем уровне источников пакетов создайте файл с именем NAMESPACE и добавьте следующие инструкции:

export(populate) ## export generic

S3method(populate, crossvalidate) ## register methods
S3method(populate, prod)

Затем, как только вы установили свой пакет, вы заметите, что вы можете вызвать populate(), но R будет жаловаться, если вы попытаетесь вызвать populate.prod() и т.д. напрямую по имени из приглашения или другой функции. Это связано с тем, что функции, которые являются отдельными методами, не были экспортированы из пространства имен, а оттуда не видны вне его. Любая функция в вашем пакете, вызывающая populate(), сможет получить доступ к указанным вами методам, но любые функции или код вне вашего пакета вообще не могут видеть методы. Если вы хотите, вы можете вызывать неэкспортируемые функции с помощью оператора :::, т.е.

mypkg:::populate.crossvalidate(foo, bar)

будет работать, где mypkg - это имя вашего пакета.

Честно говоря, вам даже не нужен файл NAMESPACE, так как R будет автоматически генерировать его при установке пакета, который автоматически экспортирует все функции. Таким образом, ваши два метода будут видны как populate.xxx() (где xxx является конкретным методом) и будут работать как методы S3.

Прочитайте Раздел 1 Создание R-пакетов в руководстве по написанию R-расширений для получения подробной информации о том, что происходит, но yuo не потребуется делать половину этого, если вы Также не хочу, особенно если пакет предназначен для вашего собственного использования. Просто создайте соответствующие папки папок (т.е. R и man), вставьте файлы .r в R. Напишите один .Rd файл в man, где вы добавляете

\name{Misc Functions}
\alias{populate}
\alias{populate.crossvalidate}
\alias{populate.prod}

в верхней части файла. Добавьте \alias{} для любых других функций, которые у вас есть. Затем вам нужно будет создать и установить пакет.

Альтернативное использование sys.source()

Хотя я не могу (не могу!) действительно рекомендовать то, что я упоминаю ниже, как долгосрочный жизнеспособный вариант здесь, есть альтернатива, которая позволит вам изолировать функции от отдельных файлов .r, поскольку вы изначально просил. Это достигается за счет использования окружений, а не пространств имен, и не требует создания пакета.

Функция sys.source() может использоваться для источника R-кода/функций из файла .r и оценивать его в среде. Поскольку вы .r файл создаете/определяете функции, если вы отправляете его в другую среду, тогда эти функции будут определены там, в этой среде. Они не будут отображаться на стандартном пути поиска по умолчанию, и, следовательно, функция populate(), определенная в crossvalidate.R, не будет сталкиваться с populate(), определенной в prod.R, если вы используете две отдельные среды. Когда вам нужно использовать один набор функций, вы можете назначить среду пути поиска, после чего она будет чудесным образом видна всем, и когда вы закончите, вы сможете отделить ее. Прикрепите другую среду, используйте ее, отсоедините и т.д. Или вы можете организовать, чтобы R-код оценивался в определенной среде, используя такие вещи, как eval().

Как я уже сказал, это не рекомендуемое решение, но оно будет работать по моде, как вы описываете. Например

## two source files that both define the same function
writeLines("populate <- function(x) 1:10", con = "crossvalidate.R")
writeLines("populate <- function(x) letters[1:10]", con = "prod.R")

## create two environments
crossvalidate <- new.env()
prod <- new.env()

## source the .R files into their respective environments
sys.source("crossvalidate.R", envir = crossvalidate)
sys.source("prod.R", envir = prod)

## show that there are no populates find-able on the search path

> ls()
[1] "crossvalidate" "prod" 
> find("populate")
character(0)

Теперь присоедините одну из сред и вызовите populate():

> attach(crossvalidate)
> populate()
 [1]  1  2  3  4  5  6  7  8  9 10
> detach(crossvalidate)

Теперь вызовите функцию в другой среде

> attach(prod)
> populate()
 [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j"
> detach(prod)

Ясно, что каждый раз, когда вы хотите использовать определенную функцию, вам нужно attach() ее среду, а затем вызвать ее, а затем вызов detach(). Это боль.

Я сказал, что вы можете организовать для R-кода (выражения действительно) для оценки в заявленной среде. Вы можете использовать eval() для with() для этого, например.

> with(crossvalidate, populate())
[1]  1  2  3  4  5  6  7  8  9 10

По крайней мере, теперь вам нужен только один вызов для запуска версии populate() по вашему выбору. Однако, если вы вызываете функции по их полному имени, например. populate.crossvalidate() - это слишком много усилий (согласно вашим комментариям), то осмелюсь сказать, что даже идея with() будет слишком сложной? И вообще, почему бы вам использовать это, когда у вас вполне может быть свой собственный пакет R.

Ответ 2

Не беспокойтесь о сложности "создания пакета". Перестань думать об этом так. Что вы собираетесь делать, так это:

  • в папке, где вы работаете над вашим проектом, создайте папку под названием "R"
  • введите здесь свой R-код, одну функцию на файл
  • создайте файл DESCRIPTION в каталоге проекта. Проверьте существующие примеры для точного формата, но вам нужно всего несколько полей.
  • Получить devtools. install.packages("devtools")
  • Используйте devtools. library(devtools)

Теперь напишите свои функции в ваших R файлах в вашей папке R. Чтобы загрузить их в R, DONT их исправить. Сделайте load_all(). Ваши функции будут загружены, но НЕ в глобальную среду.

Отредактируйте один из ваших R файлов, затем снова load_all(). Это загрузит любые измененные файлы в папку R, обновив вашу функцию.

Что это. Изменить, load_all, полоскать и повторить. Вы создали пакет, но его довольно легкий вес, и вам не нужно иметь дело с рабством и дисциплиной инструментов сборки R-пакета.

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

Все Приветствую Хэдли!

Ответ 3

Возможно, вы захотите рассмотреть вопрос о создании пакета. В качестве альтернативы вы можете посмотреть на среду. Наконец, проекты RStudio могут быть ближе к тому, что вам подходит.