Написание надежного 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-код".

  1. Использовать importFrom. Не добавляйте весь пакет в "Импорт", добавляйте только те конкретные функции, которые вам нужны. Я выполняю это с помощью документации по функциям Roxygen2 и roxygenize(), которая автоматически генерирует файл NAMESPACE. Таким образом, вы можете импортировать два пакета с конфликтами, в которых конфликты не входят в функции, которые вам действительно нужны. Это утомительно? Только до тех пор, пока это не станет привычкой. Преимущество: вы можете быстро определить все ваши сторонние зависимости. Это помогает с...

  2. Не обновляйте пакеты вслепую. Прочитайте журнал изменений по очереди и рассмотрите, как обновления будут влиять на стабильность вашего собственного пакета. В большинстве случаев обновления не затрагивают функции, которые вы используете.

  3. Избегайте классов S4. Здесь я немного ругаюсь. Я считаю, что S4 является сложным, и для работы с механизмом поиска/поиска на функциональной стороне R. требуется достаточная мощность мозга. Вам действительно нужна эта функция OO? Управление состоянием = управление сложностью - оставьте это для Python или Java =)

  4. Напишите модульные тесты. Используйте пакет testthat.

  5. Всякий раз, когда вы создаете/проверяете свой пакет R CMD, проанализируйте вывод и найдите ПРИМЕЧАНИЕ, INFO, WARNING. Кроме того, физически сканирование своими глазами. Там часть шага сборки, которая отмечает конфликты, но не прикрепляет WARN и т.д. К ней.

  6. Добавьте утверждения и инварианты сразу после вызова стороннего пакета. Другими словами, не полностью доверяйте тому, что дает вам кто-то другой. Извлеките результат немного и stop(), если результат окажется неожиданным. Вам не нужно сходить с ума - выберите одно или два утверждения, которые подразумевают достоверные/высокие результаты доверия.

Я думаю, что там больше, но теперь это стало мышечной памятью =) Я увеличусь, если еще придет ко мне.

Ответ 2

Я беру на себя это:

Описание: Гибкость поставляется с ценой. Я готов заплатить эту цену.

1) Я просто не использую пакеты, которые вызывают подобные проблемы. Если бы я действительно, действительно нужен функция из этого пакета в своих собственных пакетах, я использую importFrom() в моем NAMESPACE файл. В любом случае, если у меня возникли проблемы с пакетом, я связываюсь с автором пакета. Проблема в их стороне, а не в R.

2) Я никогда не использую :: внутри своего собственного кода. Экспортируя только функции, необходимые пользователю моего пакета, я могу сохранять свои собственные функции внутри NAMESPACE без конфликтов. Функции, которые не экспортируются, не будут скрывать функции с тем же именем, чтобы двойной выигрыш.

Хорошее руководство по тому, как именно среда, пространства имен и подобные работы вы найдете здесь: http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/

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