2011-08-21 22 views
8

Me preguntaba si había alguna manera de forzar a una función a aceptar solo ciertos tipos de datos, sin tener que verificarlo dentro de la función; o, ¿esto no es posible porque la comprobación de tipos de R se realiza en tiempo de ejecución (a diferencia de los lenguajes de programación, como Java, donde se realiza la verificación de tipos durante la compilación)?Forzar tipos de datos específicos como argumentos de una función

Por ejemplo, en Java, tiene que especificar un tipo de datos:

class t2 { 
    public int addone (int n) { 
     return n+1; 
    } 
} 

En R, una función similar podría ser

addone <- function(n) 
{ 
    return(n+1) 
} 

pero si se suministra un vector, un vector será (obviamente) ser devuelto. Si sólo desea un solo número entero de ser aceptado, a continuación, es la única manera de hacer para tener una condición dentro de la función, a lo largo de las líneas de

addone <- function(n) 
{ 
    if(is.vector(n) && length(n)==1) 
    { 
    return(n+1) 
    } else 
    { 
    return ("You must enter a single integer") 
    } 
} 

Gracias,
Chris

+3

Como punto de estilo de código, en el caso de que no tenga un número escalar, probablemente quiera lanzar un error (con 'stop' o' stopifnot') o dar una advertencia (con 'warning') en lugar que simplemente devolver una cadena. –

Respuesta

16

Esto es totalmente posible usando clases S3. Su ejemplo está algo inventado en el contexto o en R, ya que no puedo pensar en una razón práctica por la que uno quiera crear una clase de un solo valor. Sin embargo, esto es posible. Como una ventaja adicional, demuestro cómo la función addone se puede usar para agregar el valor de uno a vectores numéricos (triviales) y vectores de caracteres (por lo que A gira a B, etc.):

Comience creando un S3 genérico método para addone, utlising el mecanismo de despacho S3 UseMethod:

addone <- function(x){ 
    UseMethod("addone", x) 
} 

a continuación, crear la clase artificial single, definido como el primer elemento de lo que se pasa a la misma:

as.single <- function(x){ 
    ret <- unlist(x)[1] 
    class(ret) <- "single" 
    ret 
} 

Ahora crear m métodos para manejar las diversas clases. El método por defecto será llamado a no ser que se define una clase específica:

addone.default <- function(x) x + 1 
addone.character <- function(x)rawToChar(as.raw(as.numeric(charToRaw(x))+1)) 
addone.single <- function(x)x + 1 

Finalmente, haga la prueba con algunos datos de muestra:

addone(1:5) 
[1] 2 3 4 5 6 

addone(as.single(1:5)) 
[1] 2 
attr(,"class") 
[1] "single" 

addone("abc") 
[1] "bcd" 

Alguna información adicional:

  1. Hadley's devtools wiki es una valiosa fuente de información sobre todas las cosas, incluyendo the S3 object system.

  2. El método S3 no proporciona un tipado estricto. Puede ser fácilmente abusado. Para obtener una orientación más estricta de los objetos, consulte S4 classes, reference based classes o proto package para la programación basada en objetos Prototype.

+0

clases S4 es una buena idea. 'setMethod' en particular. – Owen

+1

Sí, las clases S3 solo manejan el primer argumento. ... y 'single' probablemente no era el mejor nombre, ya existe una clase' single' (flotantes de precisión simple) con 'as.single' etc., pero está en desuso. – Tommy

4

Se podría escribir un envoltorio como el siguiente:

check.types = function(classes, func) { 
    n = as.name 

    params = formals(func) 
    param.names = lapply(names(params), n) 

    handler = function() { } 
    formals(handler) = params 

    checks = lapply(seq_along(param.names), function(I) { 
     as.call(list(n('assert.class'), param.names[[I]], classes[[I]])) 
    }) 
    body(handler) = as.call(c(
     list(n('{')), 
     checks, 
     list(as.call(list(n('<-'), n('.func'), func))), 
     list(as.call(c(list(n('.func')), lapply(param.names, as.name)))) 
    )) 

    handler 
} 

assert.class = function(x, cls) { 
    stopifnot(cls %in% class(x)) 
} 

y utilizarlo como

f = check.types(c('numeric', 'numeric'), function(x, y) { 
    x + y 
}) 

> f(1, 2) 
[1] 3 

> f("1", "2") 
Error: cls %in% class(x) is not TRUE 

Hecho algo incómodo por R no tener decoradores. Esto es un poco hacky y que adolece de algunos problemas graves:

  1. Pierdes la evaluación perezosa, ya que debe evaluar un argumento para determinar su tipo.

  2. Aún no puede verificar los tipos hasta la hora de la llamada; Verificación de tipo estático real le permite verificar los tipos de una llamada que en realidad nunca sucede.

Desde R utiliza la evaluación perezosa, (2) podría hacer que el tipo no comprobar muy útil, porque la llamada podría no ocurrir realmente hasta muy tarde, o nunca.

La respuesta a (2) sería agregar información de tipo estático. Probablemente podrías hacer esto mediante la transformación de expresiones, pero no creo que quieras ir allí.

3

He encontrado que stopifnot() es muy útil para estas situaciones también.

x <- function(n) { 
stopifnot(is.vector(n) && length(n)==1) 
print(n) 
} 

La razón es tan útil es porque proporciona un mensaje de error bastante claro para el usuario si la condición es falsa.

+2

Tenga en cuenta que esto podría escribirse 'stopifnot (is.vector (n), length (n) == 1)' y que tendría la ventaja de que si falla, ¿cuál de las dos condiciones que fallaron se mostraría como parte de el mensaje de error –

+0

Touche, siempre me olvido de que es una detención si no ... las condiciones son ciertas. –

Cuestiones relacionadas