2015-07-17 16 views
17

[[<- si comporta in modo diverso per le liste e gli ambienti quando utilizzato su oggetti non locali:assegnazione per sostituire il valore nella lista non locale

lst = list() 
env = new.env() 

(function() lst[['x']] = 1)() 
(function() env[['x']] = 1)() 
lst 
# list() 

as.list(env) 
# $x 
# [1] 1 

In altre parole, se il bersaglio di [[<- è un ambiente, modifica l'ambiente (non locale), ma se si tratta di un vettore/elenco, crea un nuovo oggetto locale.

vorrei sapere due cose:

  1. Perché questa differenza di comportamento?
  2. Esiste un modo per raggiungere lo stesso risultato per le liste come per gli ambienti, senza utilizzando <<-?

Per quanto riguarda (1), io sono a conoscenza di molteplici differenze tra liste e gli ambienti (in particolare, so che gli ambienti non vengono copiati), ma la documentazione non citare il motivo per cui la semantica di [[<- differire tra i due - in particolare, perché l'operatore avrebbe operato su un campo di applicazione diverso. è un insetto? È contro-intuitivo almeno e richiede alcuni shenanigans di implementazione non banali.

Per quanto riguarda (2), la soluzione più ovvia è, naturalmente, di utilizzare <<-:

(function() lst[['x']] <<- 1)() 

Tuttavia, preferisco capire la differenza con rigore piuttosto che lavorare intorno a loro. Inoltre, ho finora usato assign invece di <<- e preferisco questo, in quanto mi permette un maggiore controllo sulla portata della cessione (in particolare da quando posso specificare inherits = FALSE. <<- è troppo voodoo per i miei gusti.

Tuttavia, la direttiva non possono essere risolti (per quanto ne so) utilizzando assign perché assign funziona solo su ambienti, non elenchi. In particolare, mentre assign('x', 1, env) opere (e fa lo stesso come sopra), assign('x', 1, lst) non funziona.


Per elaborare, è naturalmente previsto che R faccia una cosa diversa per diversi tipi di oggetto usando la spedizione dinamica (ad es. via S3). Tuttavia, questo è non il caso qui (almeno non direttamente): la distinzione nella risoluzione dell'ambito avviene prima che il tipo di oggetto dell'obiettivo di assegnazione sia noto - altrimenti il ​​precedente opererebbe sul globale lst, invece di creare un nuovo locale oggetto. Così internamente [[<- ha a che fare l'equivalente di:

`[[<-` = function (x, i, value) { 
    if (exists(x, mode = 'environment', inherits = TRUE)) 
     assign(i, value, pos = x, inherits = FALSE) 
    else if (exists(x, inherits = FALSE) 
     internal_assign(x, i, value) 
    else 
     assign(x, list(i = value), pos = parent.frame(), inherits = FALSE) 
} 
+1

Questa è una grande domanda, ma volevo solo sottolineare che il comportamento sopra è altrettanto vero per '$ <-' (come probabilmente ti aspetteresti) oltre a '[[<-', molto probabilmente a causa della semantica di riferimento inerente agli 'ambienti', come coperta da @Roland qui sotto. – nrussell

+1

@nrussell Sì, ho contemplato di menzionare '$' ma secondo la documentazione, la sua semantica è comunque definita come equivalente a '[[i, exact = FALSE]]', e lo stesso per l'assegnazione. –

risposta

8

La R-language definition (sezione 2.1.10) dice:

A differenza della maggior parte degli altri oggetti R, gli ambienti non vengono copiati quando passano alle funzioni o utilizzati nelle assegnazioni.

Sezione "6.3 Altro su valutazione" dà anche un suggerimento un po 'rilevante:

Si noti che la valutazione in un dato ambiente può realmente cambiare la situazione ambiente, il più evidente nei casi che comportano l'operatore di assegnazione , come ad esempio

eval(quote(total <- 0), environment(robert$balance)) # rob Rob 

Questo vale anche quando si valuta nelle liste, ma l'elenco originale non non cambia perché uno è realmente funzionare su una copia.

Quindi, la risposta alla prima domanda è che le liste devono essere copiate per assegnarle, ma gli ambienti possono essere modificati sul posto (che ha enormi implicazioni sulle prestazioni).

quanto riguarda la tua seconda domanda:

Se si sta lavorando con una lista, l'unica opzione sembra essere quella di

  • copiare l'elenco in ambito locale (usando get),
  • assegnare nell'elenco,
  • utilizzare assign per copiare nuovamente l'elenco modificato nell'ambiente originale.
+0

Quindi, per capire, ciò che succede internamente è che (1) l'oggetto viene passato a '[[<--come argomento, che crea una copia (che, nel caso degli ambienti, non esegue una copia, come non lo fa mai). Finora, questa è solo una chiamata alla funzione vaniglia e lo capisco. (2) E poi alcuni "R magic" causano il risultato della '[[<--call che deve essere assegnata ad un oggetto locale con lo stesso nome del suo argomento (che è presumibilmente lo stesso indipendentemente dal tipo di oggetto, e succede per tutti gli operatori di assegnazione). Questo ha un senso. –

+0

Per inciso, ecco una soluzione alternativa alla mia seconda domanda, ispirata al brano che hai citato: 'eval.parent (bquote ({lst [['x']]. (Valore)}))'. Non preoccuparti, non lo userò. : p –

+1

In caso di liste, si può pensare a '[[<--come una funzione con valori di input (che vengono ricercati seguendo le solite regole di scoping) e l'elenco modificato come valore di ritorno, che viene perso perché non si puoi restituirlo dalla tua funzione. – Roland