ottenere lo stato di navigazione in una funzione

Aug 24 2020

Ho una funzione come questa:

fun <- function() {
  browser()
  is_browsing()
} 

Vorrei sapere quale is_browsing()dovrebbe essere il codice di in modo che ritorni TRUEse la funzione è attualmente in fase di esplorazione, quindi la console sarebbe simile a questa:

> fun()
Called from: fun()
Browse[1]> 
debug at #3: is_browsing()
Browse[2]> 
TRUE

Tuttavia se commento la browser()riga, o interrompo la navigazione premendo c, is_browsing()dovrebbe tornare FALSE, in questo modo:

> fun()
Called from: fun()
Browse[1]> c
FALSE

Ho letto debuggingState()e isdebugged()ma non sembrano essere di grande aiuto nella mia situazione.

Il vero caso FYI riguarda l'aggiornamento di una trama o di una vista mentre navighiamo, ma solo se stiamo navigando, se non lo siamo, voglio solo tracciare / visualizzare una volta alla fine, per risparmiare risorse.

Risposte

4 Waldi Aug 29 2020 at 13:03

Quando si utilizza il browser, il prompt mostra il livello di navigazione:
Sfoglia [1], Sfoglia [2], ...

> browser()
Called from: top level 
Browse[1]> browser()
Called from: top level 
Browse[2]> 

Questo livello di navigazione è calcolato main.Cda:

browselevel = countContexts(CTXT_BROWSER, 1);

Dov'è CTXT_BROWSERuna costante definita in defn.h:

CTXT_BROWSER  = 16

Puoi utilizzare questa countContextsfunzione interna per ottenere le is_browsinginformazioni che stai cercando:

is_browsing.cpp

#include <Rcpp.h>
#include <R.h>
#include <Rinternals.h>
using namespace Rcpp;


// [[Rcpp::export]]
int is_browsing() {
  return Rf_countContexts(16,1);
}

Prova:

library(Rcpp)
sourceCpp('is_browsing.cpp')
test <- function() {
  is_browsing()
}

test()
#> [1] 0

browser()
#> Called from: eval(expr, envir, enclos)

test()
#> [1] 1

Creato il 29/08/2020 dal pacchetto reprex (v0.3.0)

Funziona anche se il browser viene chiamato all'interno della funzione:

test2 <- function() {
  browser()
   is_browsing()
 }
test2()
Called from: test2()
Browse[1]> n
debug à #3 :is_browsing()
Browse[2]> n
[1] 1

Se volessi un ritorno VERO / FALSO, il codice Rcpp sarebbe:

#include <Rcpp.h>
#include <R.h>
#include <Rinternals.h>

// [[Rcpp::export]]
Rcpp::LogicalVector is_browsing() { 
  return Rf_countContexts(16,1) > 0;
}
7 AllanCameron Aug 26 2020 at 23:44

Partendo dalle idee nel codice di Romain, quindi copiando attraverso la struttura RCNTXT (più un paio di altre strutture che utilizza internamente), sono riuscito a ottenere il codice C ++ di cui restituire il contenuto R_GlobalContext.

Il codice C ++ ha questo aspetto:

#include <Rcpp.h>
#include <Rinternals.h>
#include <setjmp.h>

extern void* R_GlobalContext ;

typedef struct {int tag, flags; union {int ival; double dval; SEXP sxpval;} u;
} R_bcstack_t;

typedef struct{jmp_buf jmpbuf; int mask_was_saved, saved_mask;} sigjmp_buf[1];

typedef struct RCNTXT {
    struct RCNTXT *nextcontext;
    int callflag;
    sigjmp_buf cjmpbuf;
    int cstacktop, evaldepth;
    SEXP promargs, callfun, sysparent, call, cloenv, conexit;
    void (*cend)(void *);
    void *cenddata;
    void *vmax;
    int intsusp, gcenabled, bcintactive;
    SEXP bcbody;
    void* bcpc;
    SEXP handlerstack, restartstack;
    struct RPRSTACK *prstack;
    R_bcstack_t *nodestack;
    R_bcstack_t *bcprottop;
    SEXP srcref;
    int browserfinish;
    SEXP returnValue;
    struct RCNTXT *jumptarget;
    int jumpmask;
} RCNTXT, *context;

// [[Rcpp::export]]
Rcpp::List get_RCNTXT(int level){
  RCNTXT* res = (RCNTXT*)R_GlobalContext;
  if (level > 1) res = res->nextcontext;
  return Rcpp::List::create(Rcpp::Named("call_flag") = res->callflag,
                            Rcpp::Named("c_stack_top") = res->cstacktop,
                            Rcpp::Named("call_depth") = res->evaldepth,
                            Rcpp::Named("call_fun") = res->callfun,
                            Rcpp::Named("sys_parent") = res->sysparent,
                            Rcpp::Named("call") = res->call,
                            Rcpp::Named("cloenv") = res->cloenv,
                            Rcpp::Named("conexit") = res->conexit,
                            Rcpp::Named("promargs") = res->promargs,
                            Rcpp::Named("intsusp") = res->intsusp,
                            Rcpp::Named("gcenabled") = res->gcenabled,
                            Rcpp::Named("bcintactive") = res->bcintactive,
                            Rcpp::Named("handlerstack") = res->handlerstack,
                            Rcpp::Named("restartstack") = res->restartstack,
                            Rcpp::Named("srcref") = res->srcref,
                            Rcpp::Named("browserfinish") = res->browserfinish);
}

Ciò ci consente di rivedere i contenuti di R_Globalcontext:

get_RCNTXT(1)
#> $call_flag #> [1] 12 #> #> $c_stack_top
#> [1] 4
#> 
#> $call_depth #> [1] 1 #> #> $call_fun
#> function (level) 
#> .Call(<pointer: 0x0000000071282ff0>, level)
#> <bytecode: 0x00000174169448d0>
#> 
#> $sys_parent #> <environment: R_GlobalEnv> #> #> $call
#> get_RCNTXT(1)
#> 
#> $cloenv #> <environment: 0x0000017416c52a08> #> #> $conexit
#> NULL
#> 
#> $promargs #> $promargs[[1]]
#> NULL
#> 
#> 
#> $intsusp #> [1] 0 #> #> $gcenabled
#> [1] 1
#> 
#> $bcintactive #> [1] 0 #> #> $handlerstack
#> NULL
#> 
#> $restartstack #> NULL #> #> $srcref
#> NULL
#> 
#> $browserfinish
#> [1] 0

Sfortunatamente, il browserfinishcampo restituisce solo uno 0 sia che venga chiamato da browsero meno. Tuttavia, se la get_RCNTXTfunzione viene chiamata dal browserprompt, restartstackmostra che è stata chiamata da browser. Ciò consente di definire la seguente funzione R una volta che il codice C ++ è stato originato:

is_browser <- function()
{
  R <- get_RCNTXT(1)$restartstack
  if(is.null(R)) return(FALSE)
  class(R[[1]]) == "restart"
}

Ciò consente di interrogare lo stato del browser dal prompt dei comandi:

is_browser()
#> [1] FALSE

> browser()
#> Called from: top level 
Browse[1]> is_browser()
#> [1] TRUE

Tuttavia, questo non è così utile come sembra. In primo luogo, ha lo stesso effetto del seguente codice in base R:

is_browser <- function() {
  !is.null(findRestart("browser"))
}

In secondo luogo, quando browserviene chiamato dall'interno di una funzione, il codice che esegue viene valutato nel proprio contesto anziché nel browsercontesto, il che significa is_browserche restituirà FALSE. Il codice C per browser, (la funzione effettiva è chiamata do_browserin main.c) scrive un nuovo contesto che viene rimosso dopo che la funzione è uscita, e questo contesto non è apparentemente puntato da nessun'altra struttura per la durata della funzione, quindi è difficile vedere come is_browserpotrebbe essere scritto per consentire l'accesso a questo contesto.

Sembra quindi che sia necessario scrivere una nuova implementazione di browserper consentire al contesto esplorato di sapere che è stato esplorato e non vogliamo davvero andare lì.

D'altra parte, il contesto del browser ha pieno accesso al contesto esplorato e poiché il tuo obiettivo finale è quello di consentire l'esecuzione di codice condizionale come i grafici solo in modalità browser, penso che la soluzione migliore sia usare il browser stesso per dire al contesto esplorato in cui si sta esplorando.

Quindi, ad esempio, se fai:

browser_on <- function() {
  options(I_am_browsing = TRUE)
}

browser_off <- function() {
  options(I_am_browsing = FALSE)
}

is_browser <- function() {
  b <- getOption("I_am_browsing")
  if(is.null(b)) FALSE else b
}

Ora hai la possibilità durante la navigazione di eseguire in modo condizionale il codice protetto da if(is_browser()).

Quindi se ti funpiace questo (con browser()commentato):

fun <- function() {
  #browser()
  if(is_browser()) plot(1:10)
  if(!is_browser()) "I didn't plot anything"
}

Otterrete:

fun()
#> [1] "I didn't plot anything"

Ma, se esegui fun()dall'interno di un browser, ottieni:

browser()
Called from: top level 
Browse[1]> browser_on()
Browse[1]> fun()

E funziona ancora se browserviene chiamato all'interno fun:

fun <- function() {
  browser()
  if(is_browser()) plot(1:10)
  if(!is_browser()) "I didn't plot anything"
}

fun()
#> Called from: fun()
Browse[1]> browser_on()
Browse[1]> 
#> debug at #3: if (is_browser()) plot(1:10)
Browse[2]> 
#> debug at #3: plot(1:10)
Browse[2]> 
#> debug at #4: if (!is_browser()) "I didn't plot anything"
Browse[2]>

Non è una soluzione perfetta perché richiede un comando aggiuntivo durante l'esecuzione nel browser e salva lo stato tramite options. Dovrai tenerne traccia se chiami browserpiù volte dallo stesso ambito. In particolare, devi fare attenzione a chiamare browser_off()prima di uscire dal browser se stai chiamando browserdall'ambiente globale.

1 RahulIyer Aug 26 2020 at 20:19

È descritto nella documentazione per browser , browseText e browseCondition :

Invece di chiamare solo browser (), chiamalo e imposta l'argomento per browseText o browseCondition.

browser(text="foo")

Quindi puoi verificare la condizione per determinare se il browser è in esecuzione:

is_browsing<-function(n)
{
    result = FALSE
 result = tryCatch({
    browserText(n=1)
     result = TRUE
}, warning = function(w) {
    #warning-handler-code
}, error = function(e) {
   # error-handler-code
}, finally = {

    #code you always want to execute
 })
   return (result)
}

Il n = 1 in browseText si riferisce al contesto da cui recuperare il valore.

Se non stai navigando, la chiamata a browseText () genera un errore -> Questo è il motivo per cui lo abbiamo inserito in un tentativo di cattura. Quindi, se viene generato un errore, sappiamo che il browser non è in esecuzione. Se non viene generato alcun errore, il risultato è impostato su true ed è possibile eseguire la propria logica personalizzata.

Per provare, prova:

browser(text="foo")
if(isTRUE(is_browsing())){
    print("is browsing!!!")
}else{
    print("is not browsing!!!");
}

Quindi commenta la chiamata al browser (text = "foo") e osserva la differenza.

MODIFICA: se non puoi passare un argomento a browser () per qualsiasi motivo, puoi utilizzare il debug invece:

https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/debug

Oppure puoi impostare il valore usando qualche altro debugger esterno.

Christoph Aug 28 2020 at 22:38

Questo non è al 100% quello che stai cercando, ma forse hai un'idea di come risolvere il tuo problema? Non ho familiarità con le nozioni di base di C / C ++ R, ma forse puoi sovraccaricare base::browser()?

Spero che possa aiutare:

list.parent_env <- function() {
  ll <- list()
  n <- 1
  while (!environmentName(.GlobalEnv) %in% 
         environmentName(parent.frame(n))) {
    ll <- c(ll, parent.frame(n))
    n <- n + 1
  }
  return(ll)
}

listofenv2names <- function(env_list) {
  names <- unlist(lapply(c(1:length(env_list)), function(i) {
    attributes(env_list[[i]])$name
  }))
  return(names)
}

# https://stackoverflow.com/a/23891089/5784831
mybrowser <- function() {
  e <- parent.frame()
  attr(e, "name") <- "mybrowser_env"
  assign("mybrowser_env", 1,
         envir = parent.frame(),
         inherits = FALSE, immediate = TRUE)
  return(eval(quote(browser()), parent.frame()))
}

is_browsing <- function() {
  env_list <- list.parent_env()
  r <- "mybrowser_env" %in% listofenv2names(env_list)
  print(r)
  return(r)
}

subsubfun <- function() {
  print("subsubfun")
  b <- 2
  is_browsing()
  return(NULL)
}

subfun <- function() {
  print("subfun")
  a <- 1
  is_browsing()
  subsubfun()
  return(NULL)
}

fun1 <- function() {
  print("fun1")
  is_browsing()
  mybrowser()
  for (i in 1:10) {
    is_browsing()
  }
  is_browsing()
  subfun()
  
  return(NULL)
} 

fun2 <- function() {
  print("fun2")
  is_browsing()
  return(NULL)
}

fun1()
fun2()

L'output sembra buono:

[1] "fun1"
[1] FALSE
Called from: eval(quote(browser()), parent.frame())
Browse[1]> c
[1] TRUE
[1] "subfun"
[1] TRUE
[1] "subsubfun"
[1] TRUE
[1] "fun2"
[1] FALSE