2011-08-21 17 views
8

Mi stavo chiedendo se esistesse un modo per forzare una funzione ad accettare solo determinati tipi di dati, senza doverli controllare all'interno della funzione; o, questo non è possibile perché il controllo dei tipi di R viene eseguito in fase di runtime (al contrario di quei linguaggi di programmazione, come Java, dove il controllo dei tipi viene eseguito durante la compilazione)?Forzare specifici tipi di dati come argomenti a una funzione

Per esempio, in Java, è necessario specificare un tipo di dati:

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

In R, una funzione simile potrebbe essere

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

ma se un vettore è fornita, un vettore lo faranno (ovviamente) essere restituito. Se si desidera solo un singolo intero di essere accettato, allora è l'unico modo per fare per avere una condizione all'interno della funzione, lungo le linee di

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

Grazie,
Chris

+3

Come punto di stile del codice, nel caso in cui non si abbia un numero scalare, probabilmente si vuole lanciare un errore (con 'stop' o' stopifnot') o dare un avvertimento (con 'warning') piuttosto di una semplice stringa. –

risposta

16

Questo è del tutto possibile usando le classi S3. Il tuo esempio è un po 'forzato nel contesto o R, dal momento che non riesco a pensare a una ragione pratica per cui si vorrebbe creare una classe di un singolo valore. Tuttavia, questo è possibile. Come bonus aggiuntivo, dimostro come la funzione addone può essere utilizzata per aggiungere il valore di uno a vettori numerici (banali) e vettori di caratteri (quindi A diventa B, ecc.):

Inizia creando un S3 generico metodo per addone, utlising il meccanismo dispaccio S3 UseMethod:

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

Avanti, creare la classe artificiosa single, definito come il primo elemento di tutto ciò che è passato a esso:

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

creare ora m etodi per gestire le varie classi. Il metodo di default sarà chiamato a meno che non si definisce una specifica classe:

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

Infine, prova con alcuni dati di esempio:

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

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

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

Alcune informazioni aggiuntive:

  1. Hadley devtools wiki è una preziosa fonte di informazioni su tutte le cose, tra cui the S3 object system.

  2. Il metodo S3 non fornisce una digitazione rigorosa. Può essere facilmente abusato. Per un orientamento degli oggetti più rigido, dare un'occhiata a S4 classes, reference based classes o allo proto package per la programmazione basata su oggetti Prototype.

+0

Le classi S4 sono una buona idea. 'setMethod' in particolare. – Owen

+1

Sì, le classi S3 gestiscono solo il primo argomento. ... e 'single' non era probabilmente il nome migliore - esiste già una classe' single' (float di precisione singola) con 'as.single' ecc. - ma è deprecato. – Tommy

4

Si potrebbe scrivere un wrapper come la seguente:

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)) 
} 

e usarlo come

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 

Realizzato un po 'scomodo da R non avere decoratori. Questa è una specie di hacky e soffre di alcuni gravi problemi:

  1. perdi valutazione pigra, perché è necessario valutare un argomento per determinare suo tipo.

  2. Non è ancora possibile controllare i tipi fino al momento della chiamata; il controllo del tipo statico reale consente di verificare i tipi anche di una chiamata che non si verifica mai effettivamente.

Poiché R usa valutazione pigra, (2) potrebbe rendere tipo non controlla molto utile, perché la chiamata non potrebbe effettivamente verificarsi fino a tardi, o non.

La risposta a (2) sarebbe quella di aggiungere informazioni di tipo statico. Probabilmente potresti fare trasformando le espressioni, ma non credo che tu voglia andare lì.

3

Ho trovato che stopifnot() è molto utile anche per queste situazioni.

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

La ragione per cui è così utile è perché fornisce un messaggio di errore abbastanza chiaro per l'utente se la condizione è falsa.

+2

Si noti che questo potrebbe essere scritto 'stopifnot (is.vector (n), length (n) == 1)' e questo avrebbe il vantaggio che se fallisce allora quale delle due condizioni che falliscono verrebbe mostrata come parte di il messaggio di errore. –

+0

Touche, mi dimentico sempre che si ferma se no ... le condizioni sono vere. –

Problemi correlati