Escribir código R robusto: espacios de nombres, enmascarar y usar el operador `::`


Versión corta

Para aquellos que no quieren leer a través de mi "caso", esta es la esencia:

  1. ¿Cuál es la forma recomendada de minimizar las posibilidades de que nuevos paquetes rompan el código existente, es decir, de hacer que el código que escribe sea lo más robusto posible?
  2. ¿Cuál es la forma recomendada de hacer el mejor uso del mecanismo de espacio de nombres cuando

    A) solo usando paquetes contribuidos (digamos en algunos análisis de R Project)?

    B) con respecto a desarrollando paquetes propios?

  3. ¿Cuál es la mejor manera de evitar conflictos con respecto a clases formales (principalmente Clases de referencia en mi caso) ya que ni siquiera hay un mecanismo de espacio de nombres comparable a :: para las clases (AFAIU)?


La forma en que funciona el universo R{[18]]}

Esto es algo que ha estado molestando en el fondo de mi mente durante unos dos años, sin embargo, no siento como si llegar a una solución satisfactoria. Además siento que está empeorando.

Vemos un número cada vez mayor de paquetes en CRAN, github, R-Forge y similares, lo cual es simplemente fantástico.

En un entorno tan descentralizado, es natural que la base de código que compone R (digamos que es base R y contribuido R , por simplicidad) se desvíe de un estado ideal con respecto a la robustez: la gente sigue convenciones, hay Clases de Referencia S3, S4, S4, etc. Las cosas no pueden estar tan "alineadas" como lo estarían si hubiera una "instancia central de compensación" que hiciera cumplir las convenciones. Está bien.

El problema

Dado lo anterior, puede ser muy difícil usar R para escribir código robusto. No todo lo que necesitas estará en base R. Para ciertos proyectos terminarás cargando bastantes paquetes contribuidos.

En mi humilde opinión, el mayor problema en ese sentido es la forma el concepto de espacio de nombres se usa en R: R permite simplemente escribir el nombre de una determinada función / método sin requerir explícitamente su espacio de nombres (es decir, foo vs.namespace::foo).

Así que en aras de la simplicidad, eso es lo que todos están haciendo. Pero de esa manera, los conflictos de nombres, el código roto y la necesidad de reescribir/refactorizar su código son solo cuestión de tiempo (o del número de paquetes diferentes cargados).

En el mejor de los casos, sabrá sobre qué funciones existentes están enmascarados / sobrecargados por un paquete recién agregado. En el peor de los casos, no tendrás ni idea hasta que tu código se rompa.

Un par de ejemplos:

  • intente cargar RMySQL y RSQLite, al mismo tiempo, no ir muy bien
  • también RMongo sobrescribirá ciertas funciones de RMySQL
  • forecast enmascara muchas cosas con respecto a las funciones relacionadas con ARIMA
  • R. utils incluso máscaras la rutina base::parse

(No puedo recordar qué funciones en particular estaban causando los problemas, pero estoy dispuesto a buscarlo de nuevo si hay interés)

Sorprendentemente, esto no parece molestar a muchos programadores por ahí. Traté de aumentar el interés un par de veces en r-devel, sin ningún beneficio significativo.

Desventajas de usar el operador ::

  1. El uso del operador :: podría dañar significativamente la eficiencia en ciertos contextos como Dominick Samperi señaló.
  2. Cuando desarrolla su propio paquete, ni siquiera puede usar el operador :: a lo largo de su propio código, ya que su código aún no es un paquete real y, por lo tanto, tampoco hay espacio de nombres todavía. Así que tendría que atenerme inicialmente a la forma foo, construir, probar y luego volver a cambiar todo a namespace::foo. En realidad no.

Posibles soluciones para evitar estos problemas

  1. Reasignar cada función de cada paquete a una variable que sigue ciertas convenciones de nomenclatura, por ejemplo, namespace..foo para evitar las ineficiencias asociadas con namespace::foo (lo describí una vez aquí). Ventajas: funciona. Desventajas: es torpe y doblas la memoria utilizada.
  2. Simule un espacio de nombres al desarrollar su paquete. AFAIU, esto no es realmente posible, al menos me lo dijeron en ese entonces.
  3. Hacer obligatorio para usar namespace::foo. En mi humilde opinión, eso sería lo mejor cosas que hacer. Claro, perderíamos algo de simplicidad, pero de nuevo el universo R ya no es simple (al menos no es tan simple como a principios de los 00).

¿Y qué pasa con las clases (formales)?

Aparte de los aspectos descritos anteriormente, :: way funciona bastante bien para funciones/métodos. Pero, ¿qué pasa con las definiciones de clase?

Tome el paquete TimeDate con su clase timeDate. Digamos que viene otro paquete que también tiene una clase timeDate. Me no veo cómo puedo declarar explícitamente que me gustaría una nueva instancia de class timeDate de cualquiera de los dos paquetes.

Algo como esto no funcionará:

new(timeDate::timeDate)
new("timeDate::timeDate")
new("timeDate", ns="timeDate")

Eso puede ser un gran problema a medida que más y más personas cambian a un estilo OOP para sus paquetes R, lo que lleva a muchas definiciones de clase. Si existe una forma de abordar explícitamente el espacio de nombres de una definición de clase, ¡apreciaría mucho un puntero!

Conclusión

Incluso aunque esto fue un poco largo, espero haber sido capaz de señalar el problema/pregunta central y que puedo crear más conciencia aquí.

Creo que devtoolsy mvbutils tienen algunos enfoques que podrían valer la pena difundir, pero estoy seguro de que hay más que decir.

Author: Rappster, 2012-06-08

2 answers

GRAN pregunta.

Validación

Escribir código R robusto, estable y listo para producción ES difícil. Usted dijo: "Sorprendentemente, esto no parece molestar a muchos programadores por ahí". Esto se debe a que la mayoría de los programadores de R no están escribiendo código de producción. Están realizando tareas académicas/de investigación únicas. Cuestionaría seriamente el conjunto de habilidades de cualquier codificador que afirme que R es fácil de poner en producción. Aparte de mi post en buscar/encontrar mecanismo que usted ya han vinculado a, También escribí un post sobre los peligros de advertencia . Las sugerencias ayudarán a reducir la complejidad en su código de producción.

Consejos para escribir código R robusto/de producción

  1. Evite los paquetes que usan Depends y favorezca los paquetes que usan Imports. Un paquete con dependencias rellenas solo en Importaciones es completamente seguro de usar. Si es absolutamente necesario utilizar un paquete que emplea Depends, envíe un correo electrónico al autor inmediatamente después de llamar a install.packages().

Esto es lo que les digo a los autores: "Hola Autor, soy un fan del paquete XYZ. Me gustaría hacer una petición. ¿Podrías mover ABC y DEF de Depends a Imports en la próxima actualización? No puedo añadir su paquete a las importaciones de mi propio paquete hasta que esto suceda. Con R 2.14 haciendo cumplir el ESPACIO de nombres para cada paquete, el mensaje general de R Core es que los paquetes deben tratar de ser "buenos ciudadanos". Si tengo que cargar un paquete Depends, añade una carga significativa: tengo que comprobar si hay conflictos cada vez que tomo una dependencia de un nuevo paquete. Con las importaciones, el paquete está libre de efectos secundarios. Entiendo que podrías romper los paquetes de otras personas haciendo esto. Creo que es lo correcto para demostrar un compromiso con las importaciones y, a largo plazo, ayudará a la gente a producir un código R más robusto."

  1. Utilice importFrom. No agregue un paquete completo a las importaciones, agregue solo las funciones específicas que necesite. Logro esto con la función Roxygen2 documentation and roxygenize () which automatically generates the NAMESPACE file. De esta manera, puede importar dos paquetes que tienen conflictos donde los conflictos no están en las funciones que realmente necesita usar. ¿Es tedioso? Sólo hasta que se convierta en un hábito. El beneficio: puede identificar rápidamente todas sus dependencias de terceros. Eso ayuda...

  2. No actualice los paquetes ciegamente. Lea la lista de cambios línea por línea y considere cómo las actualizaciones afectarán la estabilidad de su propio paquete. La mayoría de las veces, las actualizaciones no tocan las funciones que realmente usas.

  3. Evite las clases S4. Estoy saludando con la mano. Me parece que S4 es complejo y se necesita suficiente potencia cerebral para lidiar con el mecanismo de búsqueda/búsqueda en el lado funcional de R. ¿Realmente necesita estas características de OO? Administrar estado = administrar complejidad-deje eso para Python o Java =)

  4. Escribe pruebas unitarias. Usa la prueba que paquete.

  5. Cada vez que R CMD compile / pruebe su paquete, analice la salida y busque NOTA, INFORMACIÓN, ADVERTENCIA. Además, escanee físicamente con sus propios ojos. Hay una parte del paso de compilación que señala los conflictos, pero no adjunta una ADVERTENCIA, etc. a ella.

  6. Agregue aserciones e invariantes justo después de una llamada a un paquete de terceros. En otras palabras, no confíes plenamente en lo que otra persona te da. Probe el resultado un poco y stop() si el resultado es inesperado. Usted no tiene que volverse loco: elija una o dos afirmaciones que impliquen resultados válidos / de alta confianza.

Creo que hay más, pero esto se ha convertido en memoria muscular ahora =) Aumentaré si más viene a mí.

 29
Author: SFun28,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-28 14:09:41

Mi opinión al respecto:

Resumen : La flexibilidad viene con un precio. Estoy dispuesto a pagar ese precio.

1) Simplemente no uso paquetes que causen ese tipo de problemas. Si realmente necesito una función de ese paquete en mis propios paquetes, utilizo el importFrom() en mi archivo NAMESPACE. En cualquier caso, si tengo problemas con un paquete, me pongo en contacto con el autor del paquete. El problema está a su lado, no a R.

2) Nunca uso :: dentro de mi propio código. Exportando solo las funciones necesarias por el usuario de mi paquete, puedo mantener mis propias funciones dentro del ESPACIO de NOMBRES sin tener conflictos. Las funciones que no se exportan tampoco ocultarán funciones con el mismo nombre, por lo que es una ganancia doble.

Una buena guía sobre cómo funcionan exactamente los entornos, los espacios de nombres y los similares que encontrará aquí: http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff /

Esto definitivamente es una lectura obligada para todos los que escriben paquetes y similares. Después de leer esto, te darás cuenta de que no es necesario usar :: en el código del paquete.

 15
Author: Joris Meys,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-27 01:36:26