Написание надежного R-кода: пространства имен, маскирование и использование оператора `::`
Краткая версия
Для тех, кто не хочет читать мой "случай", в этом суть:
- Каков рекомендуемый способ минимизации возможностей новых пакетов, нарушающих существующий код, т.е. создания кода, который вы пишете как можно более надежным?
-
Каков рекомендуемый способ наилучшего использования механизма пространства имен, когда
a) просто используя внесенные пакеты (скажем, только в каком-то R-аналитическом проекте)?
b) в отношении разработки собственных пакетов?
-
Как лучше избегать конфликтов в отношении формальных классов (в основном Reference Classes в моем случае) поскольку нет даже механизма пространства имен, сравнимого с ::
для классов (AFAIU)?
Как работает вселенная R
Это то, что было во рту в моей голове около двух лет, но я не чувствую, что я пришел к удовлетворительному решению. Плюс я чувствую, что все хуже.
Мы видим все большее количество пакетов на CRAN, github, R-Forge и тому подобное, что просто потрясающе.
В такой децентрализованной среде естественно, что база кода, которая составляет R (скажем, что база R и способствовала R, для простоты) будет отклоняться от идеального состояния в отношении надежности: люди следуют различным соглашениям, там S3, S4, S4 и т.д. Вещи не могут быть "выровнены" так, как они были бы, если бы существовал "центральный клиринговый экземпляр", который применял соглашения. Это хорошо.
Проблема
Учитывая вышеизложенное, очень сложно использовать R для написания надежного кода. Не все, что вам нужно, будет в базе R. Для некоторых проектов вы загрузите немало пакетов.
IMHO, самая большая проблема в этом отношении - это то, как концепция пространства имен используется для использования в R: R, позволяет просто писать имя определенной функции/метода, явно не требуя этого пространства имен (т.е. foo
vs. namespace::foo
).
Итак, ради простоты, что все делают. Но таким образом, столкновения имен, сломанный код и необходимость перезаписи/реорганизации кода - это всего лишь вопрос времени (или количества загруженных пакетов).
В лучшем случае вы знаете, о том, какие существующие функции маскируются/перегружаются недавно добавленным пакетом. В худшем случае у вас не будет никакой подсказки, пока ваш код не сломается.
Несколько примеров:
- попробуйте загрузить RMySQL и RSQLite в то же время, они не идут очень хорошо
- также RMongo перезапишет некоторые функции RMySQL
- forecast маскирует много вещей в отношении связанных с ARIMA функций
- R.utils даже маскирует процедуру
base::parse
(Я не могу вспомнить, какие функции, в частности, вызывали проблемы, но я хочу снова просмотреть его, если есть интерес)
Удивительно, но это, похоже, не беспокоит многих программистов. Я пытался несколько раз поднять интерес к r-devel, без каких-либо существенных преимуществ.
Недостатки использования оператора ::
- Использование оператора
::
может значительно снизить эффективность в определенных контекстах, поскольку указал Dominick Samperi .
- Когда разрабатывает собственный пакет, вы даже не можете использовать оператор
::
на своем собственном коде, так как ваш код еще не является реальным пакетом и, тем самым, еще нет пространства имен. Поэтому мне пришлось бы сначала придерживаться метода foo
, строить, тестировать, а затем вернуться к изменению всего на namespace::foo
. Не совсем.
Возможные решения для устранения этих проблем
- Переназначить каждую функцию из каждого пакета в переменную, которая следует за определенными соглашениями об именах, например.
namespace..foo
, чтобы избежать неэффективности, связанной с namespace::foo
(я изложил ее один раз здесь). Плюсы: он работает. Минусы: он неуклюжий, и вы удваиваете используемую память.
- Имитировать пространство имен при разработке пакета. AFAIU, это на самом деле невозможно, по крайней мере, я был так сказал тогда.
- Сделать обязательным для использования
namespace::foo
. ИМХО, это было бы лучше всего. Конечно, мы потеряли бы некоторую часть простоты, но опять же вселенная R теперь просто не проста (по крайней мере, это не так просто, как в начале 00-х годов).
А как насчет (формальных) классов?
Помимо аспектов, описанных выше, способ ::
отлично работает для функций/методов. Но как насчет определений классов?
Возьмите пакет timeDate с его классом timeDate
. Произнесите еще один пакет, который также имеет класс timeDate
. Я не вижу, как я могу явно указать, что мне нужен новый экземпляр класса timeDate
из любого из двух пакетов.
Что-то вроде этого не будет работать:
new(timeDate::timeDate)
new("timeDate::timeDate")
new("timeDate", ns="timeDate")
Это может быть огромной проблемой, поскольку все больше и больше людей переключаются на OOP-стиль для своих R-пакетов, что приводит к множеству определений классов. Если есть способ явного обращения к пространству имен определения класса, я бы очень признателен за указатель!
Заключение
Несмотря на то, что это было немного долго, я надеюсь, что смогу указать основную проблему/вопрос и что я могу повысить понимание здесь.
Я думаю, devtools и mvbutils есть некоторые подходы, которые могут стоить распространения, но я уверен, что еще можно сказать.
Ответы
Ответ 1
БОЛЬШОЙ вопрос.
Проверка
Написание надежного, стабильного и готового к выпуску R-кода трудно. Вы сказали: "Удивительно, но это, похоже, не беспокоит многих программистов". Это потому, что большинство программистов R не пишу production. Они выполняют одноразовые академические/исследовательские задачи. Я бы серьезно поставил под вопрос набор навыков любого кодера, который утверждает, что R легко ввести в эксплуатацию. Помимо моего сообщения о механизме поиска/поиска, с которым вы уже связались, я также написал сообщение об опасностях warning. Эти предложения помогут снизить сложность вашего производственного кода.
Советы по написанию надежного/производственного кода R
- Избегайте пакетов, которые используют зависимости и предпочитают пакеты, использующие импорт. Пакет с зависимостями, забитыми в Импорт, полностью безопасен в использовании. Если вы абсолютно должны использовать пакет, на котором работают Depends, отправьте его автору сразу после вызова
install.packages()
.
Вот что я говорю авторам: "Привет, автор, я поклонник пакета XYZ. Я хотел бы сделать запрос. Не могли бы вы переместить ABC и DEF из Depends to Imports в следующее обновление? Я не могу добавить ваш пакет в мой собственный пакет Импорт до тех пор, пока это не произойдет. С R 2.14, обеспечивающим NAMESPACE для каждого пакета, общее сообщение от R Core состоит в том, что пакеты должны стараться быть" хорошими гражданами ". Если мне нужно загрузить пакет Depends, он добавит Значительное бремя: я должен проверять конфликты каждый раз, когда я беру зависимость от нового пакета. С Imports в пакете нет побочных эффектов. Я понимаю, что вы можете сломать пакеты других людей, сделав это. Я думаю, что это право что нужно продемонстрировать приверженность импорту, и в долгосрочной перспективе это поможет людям создать более надежный R-код".
-
Использовать importFrom. Не добавляйте весь пакет в "Импорт", добавляйте только те конкретные функции, которые вам нужны. Я выполняю это с помощью документации по функциям Roxygen2 и roxygenize(), которая автоматически генерирует файл NAMESPACE. Таким образом, вы можете импортировать два пакета с конфликтами, в которых конфликты не входят в функции, которые вам действительно нужны. Это утомительно? Только до тех пор, пока это не станет привычкой. Преимущество: вы можете быстро определить все ваши сторонние зависимости. Это помогает с...
-
Не обновляйте пакеты вслепую. Прочитайте журнал изменений по очереди и рассмотрите, как обновления будут влиять на стабильность вашего собственного пакета. В большинстве случаев обновления не затрагивают функции, которые вы используете.
-
Избегайте классов S4. Здесь я немного ругаюсь. Я считаю, что S4 является сложным, и для работы с механизмом поиска/поиска на функциональной стороне R. требуется достаточная мощность мозга. Вам действительно нужна эта функция OO? Управление состоянием = управление сложностью - оставьте это для Python или Java =)
-
Напишите модульные тесты. Используйте пакет testthat.
-
Всякий раз, когда вы создаете/проверяете свой пакет R CMD, проанализируйте вывод и найдите ПРИМЕЧАНИЕ, INFO, WARNING. Кроме того, физически сканирование своими глазами. Там часть шага сборки, которая отмечает конфликты, но не прикрепляет WARN и т.д. К ней.
-
Добавьте утверждения и инварианты сразу после вызова стороннего пакета. Другими словами, не полностью доверяйте тому, что дает вам кто-то другой. Извлеките результат немного и stop()
, если результат окажется неожиданным. Вам не нужно сходить с ума - выберите одно или два утверждения, которые подразумевают достоверные/высокие результаты доверия.
Я думаю, что там больше, но теперь это стало мышечной памятью =) Я увеличусь, если еще придет ко мне.
Ответ 2
Я беру на себя это:
Описание: Гибкость поставляется с ценой. Я готов заплатить эту цену.
1) Я просто не использую пакеты, которые вызывают подобные проблемы. Если бы я действительно, действительно нужен функция из этого пакета в своих собственных пакетах, я использую importFrom()
в моем NAMESPACE
файл. В любом случае, если у меня возникли проблемы с пакетом, я связываюсь с автором пакета. Проблема в их стороне, а не в R.
2) Я никогда не использую ::
внутри своего собственного кода. Экспортируя только функции, необходимые пользователю моего пакета, я могу сохранять свои собственные функции внутри NAMESPACE без конфликтов. Функции, которые не экспортируются, не будут скрывать функции с тем же именем, чтобы двойной выигрыш.
Хорошее руководство по тому, как именно среда, пространства имен и подобные работы вы найдете здесь: http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/
Это определенно является обязательным для всех для написания пакетов и подобных. После того, как вы прочтете это, вы поймете, что использование ::
в вашем коде пакета не требуется.