Escribir código R robusto: espacios de nombres, enmascaramiento y usar el operador `::`
coding-style namespaces (2)
Version corta
Para aquellos que no quieren leer mi "caso", esta es la esencia:
- ¿Cuál es la forma recomendada de minimizar las posibilidades de que los nuevos paquetes rompan el código existente, es decir, hacer que el código que escriba sea lo más sólido posible ?
¿Cuál es la forma recomendada de hacer el mejor uso del mecanismo del espacio de nombres cuando
a) simplemente usando paquetes contribuidos (digamos en solo algunos R Analysis Project)?
b) con respecto al desarrollo de paquetes propios?
¿Cuál es la mejor manera de evitar conflictos con respecto a las clases formales (principalmente clases de referencia en mi caso) ya que ni siquiera hay un mecanismo de espacio de nombres comparable a
::
for classes (AFAIU)?
La forma en que funciona el universo R
Esto es algo que ha estado molestando en mi mente durante aproximadamente dos años, sin embargo, no siento que haya llegado 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 genial.
En un entorno tan descentralizado, es natural que la base de código que compone R (digamos que es base R y contribuyó R , por simplicidad) se desvíe de un estado ideal con respecto a la solidez: las personas siguen diferentes convenciones, hay S3, S4 , Las Clases de Referencia S4, etc. Las cosas no pueden estar tan "alineadas" como lo estarían si hubiera una " instancia central de compensación " que obligara a las convenciones. Esta bien.
El problema
Dado lo anterior, puede ser muy difícil usar R para escribir código robusto. No todo lo que necesita estará en la base R. Para ciertos proyectos, terminará cargando bastantes paquetes contribuidos.
En mi humilde opinión, el mayor problema al respecto es la forma en que se usa el concepto de espacio de nombres 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
) .
Entonces, en aras de la simplicidad, eso es lo que todos están haciendo. Pero de esa manera, los conflictos de nombre, el código roto y la necesidad de reescribir / refactorizar su código son solo una cuestión de tiempo (o de la cantidad de paquetes diferentes cargados).
En el mejor de los casos, sabrá qué funciones existentes están enmascaradas / sobrecargadas por un paquete recién agregado. En el peor de los casos, no tendrá ni idea hasta que se rompa su código.
Un par de ejemplos:
- intente cargar RMySQL y RSQLite al mismo tiempo, no van 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 enmascara la
base::parse
rutina debase::parse
(No recuerdo 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. Traté de despertar el interés un par de veces en r-devel , sin ningún beneficio significativo.
Desventajas de usar el operador ::
- Usar el operador
::
podría afectar significativamente la eficiencia en ciertos contextos, como señaló Dominick Samperi. - Al desarrollar su propio paquete, ni siquiera puede usar el operador
::
en 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. Así que inicialmente me tengo que apegar al caminofoo
, construir, probar y luego volver a cambiar todo alnamespace::foo
. Realmente no.
Posibles soluciones para evitar estos problemas
- Reasigne cada función de cada paquete a una variable que siga ciertas convenciones de nomenclatura, p. Ej.
namespace..foo
para evitar las ineficiencias asociadas connamespace::foo
(lo describí una vez r-devel ). Pros: funciona Contras: es torpe y duplica la memoria utilizada. - Simule un espacio de nombres al desarrollar su paquete. AFAIU, esto no es realmente posible, al menos eso me dijeron entonces .
- Haz obligatorio usar
namespace::foo
. En mi humilde opinión, eso sería lo mejor que se puede hacer. Claro, perderíamos un poco de simplicidad, pero nuevamente el universo R ya no es simple (al menos no es tan simple como a principios de los 00).
¿Y las clases (formales)?
Además de los aspectos descritos anteriormente, ::
way funciona bastante bien para funciones / métodos. Pero, ¿qué hay de las definiciones de clase?
Tome el paquete timeDate con su clase timeDate
. Diga que viene otro paquete que también tiene un timeDate
clase. No veo cómo podría afirmar explícitamente que me gustaría una nueva instancia de clase timeDate
partir 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 ya que cada vez más personas cambian a un estilo OOP para sus paquetes R, lo que genera muchas definiciones de clases. Si hay una forma de abordar explícitamente el espacio de nombres de una definición de clase, ¡apreciaría mucho un puntero!
Conclusión
A pesar de que esto fue un poco largo, espero haber podido señalar el problema / pregunta principal y que puedo generar más conciencia aquí.
Creo que devtools y mvbutils tienen algunos enfoques que valdría la pena difundir, pero estoy seguro de que hay más que decir.
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". Eso es porque la mayoría de los programadores R no están escribiendo código de producción . Están realizando tareas académicas / de investigación puntuales. Yo cuestionaría seriamente las habilidades de cualquier codificador que afirme que R es fácil de poner en producción. Además de mi publicación en el mecanismo de búsqueda / búsqueda al que ya te has vinculado, también escribí una publicación sobre los peligros de la warning . Las sugerencias ayudarán a reducir la complejidad de su código de producción.
Consejos para escribir código R robusto / de producción
- Evite los paquetes que usan Depends y favorezca los paquetes que usan Imports. Un paquete con dependencias rellenas solo en Imports es completamente seguro de usar. Si es absolutamente necesario utilizar un paquete que emplea Depends, envíele 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ático del paquete XYZ. Quisiera hacer una solicitud. ¿Podrían mover ABC y DEF de Depende de Importaciones en la próxima actualización? No puedo agregar su paquete a Importaciones de mi propio paquete hasta que esto ocurra. Con R 2.14 aplicando NAMESPACE 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, agrega una carga significativa: Tengo que verificar los conflictos cada vez que tomo una dependencia de un nuevo paquete. Con Imports, el paquete no tiene efectos secundarios. Entiendo que podría romper los paquetes de otras personas al hacer esto. Creo que es lo correcto. demostrar un compromiso con las importaciones y, a la larga, ayudará a las personas a producir códigos R más robustos ".
Use importFrom. No agregue un paquete completo a Imports, solo agregue las funciones específicas que necesita. Lo logro con la documentación de la función Roxygen2 y roxygenize () que genera automáticamente el archivo NAMESPACE. De esta manera, puede importar dos paquetes que tienen conflictos donde los conflictos no están en las funciones que realmente necesita usar. ¿Esto es tedioso? Solo hasta que se convierta en un hábito. El beneficio: puede identificar rápidamente todas sus dependencias de terceros. Eso ayuda con ...
No actualice paquetes a ciegas. Lea el registro 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 usa.
Evita las clases S4. Estoy haciendo algunas agitaciones de mano aquí. Encuentro que S4 es complejo y requiere suficiente capacidad cerebral para manejar el mecanismo de búsqueda / búsqueda en el lado funcional de R. ¿Realmente necesita estas características de OO? Gestión del estado = gestión de la complejidad: deja eso para Python o Java =)
Escribir pruebas unitarias Usa el paquete testthat.
Siempre que R CMD construya / pruebe su paquete, analice el resultado 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 conflictos pero no adjunta un WARN, etc. a él.
Agregue aserciones e invariantes inmediatamente después de una llamada a un paquete de terceros. En otras palabras, no confíes completamente en lo que otra persona te da. Pruebe el resultado un poco y
stop()
si el resultado es inesperado. No tiene que volverse loco: elija una o dos afirmaciones que impliquen resultados válidos / de alta confianza.
Creo que hay más, pero ahora esto se ha convertido en memoria muscular =) Aumentaré si recibo más.
Mi opinión sobre esto:
Resumen: la flexibilidad tiene un precio. Estoy dispuesto a pagar ese precio.
1) Simplemente no uso paquetes que causan ese tipo de problemas. Si realmente, realmente necesito una función de ese paquete en mis propios paquetes, utilizo 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á en su lado, no en R''s.
2) Nunca uso ::
dentro de mi propio código. Al exportar solo las funciones que necesita el usuario de mi paquete, puedo mantener mis propias funciones dentro de NAMESPACE sin tener conflictos. Las funciones que no se exportan tampoco ocultan las funciones con el mismo nombre, por lo que se obtiene una doble ganancia.
Una buena guía sobre cómo funcionan exactamente los entornos, los espacios de nombres y los "me gusta" aquí: http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/
Definitivamente es una lectura obligada para todos los paquetes de escritura y similares. Después de leer esto, se dará cuenta de que el uso de ::
en el código de su paquete no es necesario.