【问题标题】:Reactive object bindings in a non-shiny context非闪亮上下文中的反应性对象绑定
【发布时间】:2014-11-12 02:00:00
【问题描述】:

实际问题

您如何近似由shiny 函数建立的reactive environment/behavior,或者甚至可能在非闪亮上下文中使用这些函数来创建“反应性”变量?

背景

我对@9​​87654323@ 及其基本范式非常着迷。特别是关于已建立的整体反应环境。只是为了纯粹的乐趣,我想知道是否可以将这种反应式编程范式转移到非闪亮的上下文中 - 即常规的 R 应用程序/项目/包,或者您想调用它。

也许认为选项:您可能希望option_2 依赖于option_1 的值以确保 一致的数据状态。如果option_1 改变,option_2 也应该改变。

我想我想寻找尽可能高效的东西,即option_2 应该仅在必要时更新,即当option_1 实际发生变化时(而不是计算当前状态) option_2 每次我都会查询该选项)。

尽职调查

我玩了一下以下函数:

  • shiny::reactiveValues
  • shiny::reactive
  • shiny::observe
  • shiny::isolate

但是 AFAIU,当然,它们是为闪亮的环境量身定制的。

自己的原型

这是一个基于environments 的非常简单的解决方案。它有效,但是

  1. 我会对不同/更好的方法感兴趣,并且
  2. 我想也许真的可以以某种方式重用闪亮的代码。

set函数的定义

setValue <- function(
  id,
  value,
  envir,
  observe = NULL,
  binding = NULL,
  ...
) {

  ## Auxiliary environments //
  if (!exists(".bindings", envir, inherits = FALSE)) {
    assign(".bindings", new.env(), envir)
  }    
  if (!exists(".hash", envir, inherits = FALSE)) {
    assign(".hash", new.env(), envir)
  }
  if (!exists(".observe", envir, inherits = FALSE)) {
    assign(".observe", new.env(), envir)
  }
  if (!exists(id, envir$.hash, inherits = FALSE)) {
    assign(id, new.env(), envir$.hash)  
  }

  ## Decide what type of variable we have //
  if (!is.null(observe) && !is.null(binding)) {
    has_binding <- TRUE
  } else {
    has_binding <- FALSE
  }

  ## Set //
  if (has_binding) {
  ## Value with binding //
    ## Get and transfer hash value of observed variable:
    assign(id, get(observe, envir$.hash[[observe]]), envir$.hash[[observe]])
    ## Compute actual value based on the binding contract/function:
    out <- binding(x = get(observe, envir))
    ## Store actual value:
    assign(id, out, envir)
    ## Store hash value:
    assign(id, digest::digest(out), envir$.hash[[id]])
    ## Store binding:
    assign(id, binding, envir$.bindings)    
    ## Store name of observed variable:
    assign(id, observe, envir$.observe)    
  } else {
  ## Regular variable without binding //
    ## Store actual value:
    out <- assign(id, value, envir)
    ## Store hash value:
    assign(id, digest::digest(value), envir$.hash[[id]])
  }

  return(out)

}

get函数的定义

getValue <- function(
  id,
  envir,
  ...
) {

  ## Check if variable observes another variable //
  observe <- envir$.observe[[id]]

  ## Get //
  if (!is.null(observe)) {
  ## Check if any of observed variables have changed //
  ## Note: currently only tested with bindings that only 
  ## take one observed variable 
    idx <- sapply(observe, function(ii) {
      hash_0 <- get(ii, envir$.hash[[ii]], inherits = FALSE)
      hash_1 <- get(id, envir$.hash[[ii]], inherits = FALSE)
      hash_0 != hash_1
    })

    ## Update required //
    if (any(idx)) {
      out <- setValue(
        id = id, 
        envir = envir, 
        binding = get(id, envir$.bindings, inherits = FALSE),
        observe = observe
      )
    } else {
      out <- get(id, envir, inherits = FALSE)
    }
  } else {
    out <- get(id, envir, inherits = FALSE)
  }

  return(out)

}

申请:

##------------------------------------------------------------------------------
## Apply //
##------------------------------------------------------------------------------

require("digest")
envir <- new.env()  

## Set regular variable value //
setValue(id = "x_1", value = Sys.time(), envir = envir)
[1] "2014-09-17 23:15:38 CEST"
getValue(id = "x_1", envir = envir)
# [1] "2014-09-17 23:15:38 CEST"

## Set variable with binding to observed variable 'x_1' //
setValue(
  id = "x_2", 
  envir = envir,
  binding = function(x) {
    x + 60*60*24
  }, 
  observe = "x_1"
)
# [1] "2014-09-18 23:15:38 CEST"

## As long as observed variable does not change, 
## value of 'x_2' will also not change
getValue(id = "x_2", envir = envir)
# [1] "2014-09-18 23:15:38 CEST"

## Change value of observed variable 'x_1' //
setValue(id = "x_1", value = Sys.time(), envir = envir)
# [1] "2014-09-17 23:16:52 CEST"
## Value of 'x_2' will change according to binding contract/function:
getValue(id = "x_2", envir = envir)
# [1] "2014-09-18 23:16:52 CEST"

分析:

##------------------------------------------------------------------------------
## Profiling //
##------------------------------------------------------------------------------

require(microbenchmark)

envir <- new.env()  
binding <- function(x) {
  x + 60*60*24
}

microbenchmark(
  "1" = setValue(id = "x_1", value = Sys.time(), envir = envir),
  "2" = getValue(id = "x_1", envir = envir),
  "3" = setValue(id = "x_2", envir = envir,
    binding = binding, observe = "x_1"),
  "4" = getValue(id = "x_2", envir = envir),
  "5" = setValue(id = "x_1", value = Sys.time(), envir = envir),
  "6" = getValue(id = "x_2", envir = envir)
)

# Unit: microseconds
#  expr     min       lq   median       uq      max neval
#     1 108.620 111.8275 115.4620 130.2155 1294.881   100
#     2   4.704   6.4150   6.8425   7.2710   17.106   100
#     3 178.324 183.6705 188.5880 247.1735  385.300   100
#     4  43.620  49.3925  54.0965  92.7975  448.591   100
#     5 109.047 112.0415 114.1800 159.2945  223.654   100
#     6  43.620  47.6815  50.8895 100.9225  445.169   100

【问题讨论】:

  • 如果您使用图形界面语言(例如 tcl/tk),这几乎已经存在。例如,看一下playwith 绘图 GUI。在 GUI 方法之外,makeActiveBinding 可能值得研究。
  • @Thomas:谢谢,我去看看!
  • 我不确定它是否可以用来真正模仿响应式功能,但我有时会使用(滥用?)eval 与表达式、调用和引号来避免额外的复制操作。

标签: r shiny reactive-programming


【解决方案1】:

对于那些感兴趣的人:这个周末一直困扰着我,所以我整理了一个名为reactr 的小包,它基于通过makeActiveBinding 定义绑定的方式。你可以找到基本的想法here

主要特点

  • 支持的监控场景:该包允许定义简单监控场景以及更复杂的场景,例如任意功能关系相互绑定 以及“源”和“目标”变量的不同环境(参见参数wherewhere_watch)。
  • 缓存:这种创建绑定的方式出于效率考虑尽可能使用缓存值(如果监控的变量没有改变,可以使用缓存值而不是每次都重新运行绑定函数)。
  • 作为参考,我仍然保留了基于上述问题中的概念的解决方案。可通过binding_type = 2 获得。但是,它不支持使用 assign()get()&lt;-&lt;obj-name&gt;$&lt;obj-name&gt;)的语法糖来保持哈希值同步 - 所以我不会使用它我猜测。

缺点

我不太喜欢它的是我需要一个辅助环境来存储比较的哈希值,以便做出“更新缓存或返回缓存”的决定。它在where 中浮动,默认情况下当前在where$._HASH 中(请参阅ensureHashRegistryState(),但至少您可以将名称/ID 更改为您更喜欢或需要的名称/ID(请参阅参数.hash_id)。

如果有人对如何摆脱它有任何想法,将不胜感激! :-)


示例

README.md

加载:

require("devtools")
devtools::install_github("Rappster/classr")
devtools::install_github("Rappster/reactr")
require("reactr")

使用示例环境,这样我们就不会弄乱我们的.GlobalEnv

where <- new.env()

绑定场景一:简单监控(相同值)

设置一个可以监控的变量:

setReactive(id = "x_1", value = 10, where = where)

设置一个监控x_1的变量并对其进行反应绑定:

setReactiveid = "x_2", watch = "x_1", where = where)

每当x_1 更改时,x_2 也会相应更改:

where$x_1 
# [1] 10
where$x_2
# [1] 10
where$x_1 <- 100 
where$x_2
# [1] 100

请注意,尝试更改 x_2 将被忽略,因为它只能监视 x_1

where$x_2 <- 1000
where$x_2
# [1] 100

绑定场景2:简单监控(任意函数关系)

setReactiveid = "x_3", watch = "x_1", where = where, binding = function(x) {x * 2})

每当x_1 更改时,x_3 也会相应更改:

where$x_1 
# [1] 100
where$x_2
# [1] 100
where$x_3
# [1] 200
where$x_1 <- 500
where$x_2
# [1] 500
where$x_3
# [1] 1000

绑定场景3:相互绑定(相同值)

设置两个具有相互绑定的变量。 绑定场景 1 的主要区别在于,您可以设置 x_1 x_4 都反映了更改。

为此,还需要重置x_1 的绑定 mutual = TRUE:

setReactive(id = "x_1", watch = "x_4", where = where, mutual = TRUE)
setReactive(id = "x_4", watch = "x_1", where = where, mutual = TRUE)

每当x_1 更改时,x_4 也会相应更改,反之亦然。

请注意,具有相互绑定的变量仅由setThis 初始化,默认值为NULL。您必须实际为任何一个赋值 其中通过&lt;-建立绑定后:

where$x_1
# NULL
where$x_4
# NULL

where$x_1 <- 100
where$x_1
# [1] 100
where$x_4
# [1] 100
where$x_2
# [1] 100
where$x_3
# [1] 200

where$x_4 <- 1000
where$x_4
# [1] 1000
where$x_1
# [1] 1000
where$x_2
# [1] 1000
where$x_3
# [1] 2000

绑定场景4:相互绑定(有效的双向关系)

setReactive(id = "x_5", watch = "x_6", where = where, 
  binding = function(x) {x * 2}, mutual = TRUE)
setReactive(id = "x_6", watch = "x_5", where = where, 
  binding = function(x) {x / 2}, mutual = TRUE)

where$x_5 <- 100
where$x_5
# [1] 100
where$x_6
# [1] 50

where$x_6 <- 500
where$x_6
# [1] 500
where$x_5
# [1] 1000

更多示例

请参阅 ?setReactive?setReactive_bare


分析

我在/inst/prof/prof_1.r 中包含了一个分析脚本。有一个“裸”S3 方法setThis_bare 大约快 10 %。

使用S4方法setValue()

where <- new.env()  

res_1 <- microbenchmark(
  "1" = setReactive(id = "x_1", value = 10, where = where),
  "2" = getReactive(id = "x_1", where = where),
  "3" = setReactive(id = "x_2", where = where, watch = "x_1",
    binding = function(x) {x + 100}),
  "4" = getReactive(id = "x_2", where = where),
  "5" = setReactive(id = "x_1", value = 100, where = where),
  "6" = getReactive(id = "x_2", where = where),
  control = list(order = "inorder")
)

Unit: microseconds
 expr     min       lq   median       uq      max neval
    1 476.387 487.9330 494.7750 545.6640 7759.026   100
    2  25.658  26.9420  27.5835  30.5770   55.166   100
    3 644.875 657.7045 668.1820 743.6595 7343.364   100
    4  34.211  35.4950  36.3495  38.4870   86.384   100
    5 482.802 494.7750 505.4665 543.9535 2665.027   100
    6  51.744  53.0280  54.3100  58.1595   99.640   100

使用S3函数setThis_bare()

where <- new.env()

res_3 <- microbenchmark(
  "1" = setReactive_bare(id = "x_1", value = 10, where = where),
  "2" = getReactive(id = "x_1", where = where),
  "3" = setReactive_bare(id = "x_2", where = where, watch = "x_1",
    binding = function(x) {x + 100}),
  "4" = getReactive(id = "x_2", where = where),
  "5" = setReactive_bare(id = "x_1", value = 100, where = where),
  "6" = getReactive(id = "x_2", where = where),
  control = list(order = "inorder")
)

Unit: microseconds
 expr     min       lq  median       uq      max neval
    1 428.492 441.9625 453.936 567.4735 6013.844   100
    2  25.659  26.9420  27.797  33.9980   84.672   100
    3 599.546 613.0165 622.852 703.0340 2369.103   100
    4  34.211  35.9220  36.777  45.5445   71.844   100
    5 436.189 448.1630 457.571 518.5095 2309.662   100
    6  51.745  53.4550  54.952  60.5115 1131.952   100

对于那些对细节感兴趣的人

这就是样板代码在setThis() 内部被馈送到makeActiveBinding() 的样子(省略了message() 的内容;参见/R/getBoilerplateCode.r)。

可监控的变量:

out <- substitute(
  local({
    VALUE <- NULL
    function(v) {
      if (!missing(v)) {
        VALUE <<- v
        ## Ensure hash value //
        assign(id, digest::digest(VALUE), where[[HASH]][[id]])
      }
      VALUE
    }
  }),
  list(
    VALUE = as.name("value"),
    HASH = as.name(".hash_id")
  )
)

准备评估:

getBoilerplateCode(
  ns = classr::createInstance(cl = "Reactr.BindingContractMonitored.S3")
)

监控变量:

out <- substitute(
  local({
    if (  exists(watch, envir = where_watch, inherits = FALSE) &&
          !is.null(get(watch, envir = where_watch, inherits = FALSE))
    ) {
      VALUE <- BINDING_CONTRACT
    } else {
      VALUE <- NULL
    }
    function(v) { 
      if (exists(watch, envir = where_watch, inherits = FALSE)) {  
        if (missing(v)) {
          hash_0 <- where_watch[[HASH]][[watch]][[watch]]
          hash_1 <- where_watch[[HASH]][[watch]][[id]]
          if (hash_0 != hash_1) {
            VALUE <<- BINDING_CONTRACT
            where_watch[[HASH]][[watch]][[id]] <- hash_0
            where[[HASH]][[id]][[id]] <- hash_0
            where[[HASH]][[id]][[watch]] <- hash_0
          } 
        }
      }
      VALUE
    }
  }),
  list(
    VALUE = as.name("value"), 
    BINDING_CONTRACT = substitute(.binding(x = where_watch[[watch]])),
    HASH = as.name(".hash_id")
  )
)    

准备评估:

getBoilerplateCode(
  ns = classr::createInstance(cl = "Reactr.BindingContractMonitoring.S3")
)

相互绑定的变量:

out <- substitute(
  local({
    if (  exists(watch, envir = where, inherits = FALSE) &&
          !is.null(get(watch, envir = where, inherits = FALSE))
    ) {
      VALUE <- BINDING_CONTRACT
    } else {
      VALUE <- NULL
    }
    function(v) {
      if (!missing(v)) {
        VALUE <<- v
        ## Update hash value //
        assign(id, digest::digest(VALUE), where[[HASH]][[id]])
      }
      if (exists(watch, envir = where, inherits = FALSE)) {
        if (missing(v)) {
          hash_0 <- where[[HASH]][[watch]][[watch]]
          hash_1 <- where[[HASH]][[watch]][[id]]
          if (hash_0 != hash_1) {
            VALUE <<- BINDING_CONTRACT
            where[[HASH]][[watch]][[id]] <- hash_0
            where[[HASH]][[id]][[id]] <- hash_0
            where[[HASH]][[id]][[watch]] <- hash_0
          }
        }
      }
      VALUE
    }
  }),
  list(
    VALUE = as.name("value"), 
    BINDING_CONTRACT = substitute(.binding(x = where[[watch]])),
    HASH = as.name(".hash_id")
  )
)    

准备评估:

getBoilerplateCode(
  ns = classr::createInstance(cl = "Reactr.BindingContractMutual.S3")
)

【讨论】:

    【解决方案2】:

    (试图将其作为评论留下,但 S.O. 说它太长了。)

    感谢您更仔细地研究反应性。您可能会发现这两个链接很有帮助:

    所以实际上 Shiny 的反应性可以在 Shiny 应用程序之外使用——有两个技巧。

    1. 如果您尝试从控制台读取反应式表达式或反应式值,则会收到错误消息。我故意这样做是因为在像 Shiny 这样的基本反应式系统中,从非反应式上下文中读取反应式值或表达式几乎总是一个错误(如果您阅读了上面的两个链接,希望这句话是有意义的)。但是,当您在控制台上驾驶时,想要规避此检查是非常合理的。所以你可以设置options(shiny.suppressMissingContextError=TRUE) 让它消失。
    2. 当你做一些触发反应的事情时,观察者在你调用shiny:::flushReact()之前不会真正执行。这样您就可以执行多次更新,然后让所有响应式代码响应一次,而不是每次更新都重新计算。对于控制台使用,您可以使用 shiny:::setAutoflush(TRUE) 让 Shiny 在每个控制台提示符下自动调用 flushReact。同样,这只需要观察者工作。

    今天有效的示例(在控制台中逐行执行此代码):

    library(shiny)
    options(shiny.suppressMissingContextError=TRUE)
    
    makeReactiveBinding("x_1")
    x_1 <- Sys.time()
    x_2 <- reactive(x_1 + 60*60*24)
    x_1
    x_2()
    x_1 <- Sys.time()
    x_1
    x_2()
    
    # Now let's try an observer
    shiny:::setAutoflush(TRUE)
    observe(print(paste("The time changed:", x_1)))
    x_1 <- Sys.time()
    

    我建议再看看更直接地利用 Shiny 的反应式抽象。我认为您可以使用 makeActiveBinding 非常简单地实现这样的语法(假设您认为这比 Shiny 今天给您的更好):

    where <- new.reactr()
    where$x_1 <- Sys.time()
    where$x_2 <- reactive(x_1 + 60*60*24)
    where$x_1  # Read x_1
    where$x_2  # Read x_2
    

    使用reactive() 而不是setThis 声明反应式表达式的一个关键优势是前者可以轻松自然地对同时依赖于多个反应式值/表达式的表达式进行建模。请注意,反应式表达式既是缓存的又是惰性的:如果您修改x_1,它实际上不会重新计算x_2,直到您尝试读取x_2,如果您再次读取x_2而没有更改x_1,那么它'将只返回之前的值,无需重新计算。

    有关 Shiny 反应性的更多功能变化,请参阅 Hadley Wickham 的新软件包 https://github.com/hadley/shinySignals,其灵感来自 Elm

    希望对您有所帮助。

    【讨论】:

    • 太棒了!非常感谢,我一定会看看的!
    • 试图结合这两种方法:-)
    【解决方案3】:

    感谢 Joe 的指点,我能够显着简化设计。我真的希望 不需要 需要担心某个变量是否是反应变量(前者意味着您必须通过 () 执行底层反应绑定函数,如 @987654322 @在上面乔的回答中)。所以这就是我尝试将 Joe 的代码与 makeActiveBinding() 结合起来的原因。

    优点

    • 不再需要哈希环境where$._HASH,实际的反应细节留给shiny - 这太棒了,因为如果有人知道如何掌握在 R 中完成的反应,那可能是 RStudio 的人 ;-) 还有,这样整个事情甚至可能与shiny 应用程序兼容 - 好吧,至少在理论上 ;-)
    • 正如 Joe 所指出的,reactive() 并不关心您提供给它的观察变量数量 - 只要它们处于相同的环境中(arg env in reactive(),arg where 在我的代码中)。

    缺点

    • 我认为你失去了以这种方式定义“相互依赖”的能力——至少到目前为止是 AFAICT。现在角色已经很清楚了:有一个变量可以被过度服务并且可能被显式设置,而另一个真正只是观察。
    • reactive() 的返回值非常棘手,因为它暗示了一个比实际返回简单得多的对象(它是一个引用类)。这使得很难与substitute()“原样”结合,因为这会导致某种静态绑定(适用于第一个周期,但随后它是静态的)。

      我需要使用很好的旧解决方法,将整个内容转换为 character 字符串:

      reactive_expr <- gsub(") $", ", env = where)", capture.output(reactive(x_1 + 60*60*24))
      

      可能有点危险或不可靠,但似乎capture.output(reactive()) 的末尾总是有尾随空格,这对我们很有好处,因为它让我们识别最后一个)

      此外,它还带有一种 Pro:由于在 内部 setReactive 添加了where,因此用户无需指定where两次 - 否则需要:

      where <- new.env()
      setReactive("x_1", reactive(x_2 + 60*60*24, env = where), where = where)
      

    所以,这是草稿

    require("shiny")
    
    setReactive <- function(
      id = id,
      value = NULL,
      where = .GlobalEnv,
      .tracelevel = 0,
      ...
    ) {
      ## Ensure shiny let's me do this //
      shiny_opt <- getOption("shiny.suppressMissingContextError")
      if (is.null(shiny_opt) || !shiny_opt) {
        options(shiny.suppressMissingContextError = TRUE)  
      }
    
      ## Check if regular value assignment or reactive function //
      if (!inherits(value, "reactive")) {
        is_reactive <- FALSE
        shiny::makeReactiveBinding(symbol = id, env = where)
        value_expr <- substitute(VALUE, list(VALUE = value))
      } else {
        is_reactive <- TRUE
        ## Put together the "line of lines" //
        value_expr <- substitute(value <<- VALUE(), list(VALUE = value))
        ## --> works initially but seems to be static
        ## --> seems like the call to 'local()' needs to contain the *actual*
        ## "literate" version of 'reactive(...)'. Evaluationg it  
        ## results in the reactive object "behind" 'reactive(()' to be assigned
        ## and that seems to make it static.
    
        ## Workaround based character strings and re-parsing //
        reactive_expr <- gsub(") $", ", env = where)", capture.output(value))
        value_expr <- substitute(value <<- eval(VALUE)(), 
                                 list(VALUE = parse(text = reactive_expr)))
      }
    
      ## Call to 'makeActiveBinding' //
      expr <- substitute(
        makeActiveBinding(
          id,
          local({
            value <- VALUE
            function(v) {
              if (!missing(v)) {
                  value <<- v
              } else {
                  VALUE_EXPR
              }
              value
            }
          }),
          env = where
        ),
        list(
          VALUE = value,
          VALUE_EXPR = value_expr
         )
      )
      if (.tracelevel == 1) {
        print(expr)
      }
      eval(expr)
    
      ## Return value //
      if (is_reactive) {
        out <- get(id, envir = where, inherits = FALSE)
      } else {
        out <- value
      }
      return(out)
    }
    

    在 .GlobalEnv 中测试

    ## In .GlobalEnv //
    ## Make sure 'x_1' and 'x_2' are removed:
    suppressWarnings(rm(x_1))
    suppressWarnings(rm(x_2))
    setReactive("x_1", value = Sys.time())
    x_1
    # [1] "2014-09-24 18:35:49 CEST"
    x_1 <- Sys.time()
    x_1
    # [1] "2014-09-24 18:35:51 CEST"
    
    setReactive("x_2", value = reactive(x_1 + 60*60*24))
    x_2
    # [1] "2014-09-25 18:35:51 CEST"
    x_1 <- Sys.time()
    x_1
    # [1] "2014-09-24 18:36:47 CEST"
    x_2
    # [1] "2014-09-25 18:36:47 CEST"
    
    setReactive("x_3", value = reactive({
      message(x_1)
      message(x_2)
      out <- x_2 + 60*60*24
      message(paste0("Difference: ", out - x_1))
      out
    }))
    x_3
    # 2014-09-24 18:36:47
    # 2014-09-25 18:36:47
    # Difference: 2
    # [1] "2014-09-26 18:36:47 CEST"
    x_1 <- Sys.time()
    x_1
    # [1] "2014-09-24 18:38:50 CEST"
    x_2
    # [1] "2014-09-25 18:38:50 CEST"
    x_3
    # 2014-09-24 18:38:50
    # 2014-09-25 18:38:50
    # Difference: 2
    # [1] "2014-09-26 18:38:50 CEST"
    
    ## Setting an observer has no effect
    x_2 <- 100
    x_2
    # [1] "2014-09-25 18:38:50 CEST"
    

    在自定义环境中测试

    与使用.GlobalEnv 类似,只是您需要声明/使用where

    where <- new.env()
    suppressWarnings(rm(x_1, envir = where))
    suppressWarnings(rm(x_2, envir = where))
    
    setReactive("x_1", value = Sys.time(), where = where)
    where$x_1
    # [1] "2014-09-24 18:43:18 CEST"
    
    setReactive("x_2", value = reactive(x_1 + 60*60*24, env = where), where = where)
    where$x_2
    # [1] "2014-09-25 18:43:18 CEST"
    where$x_1 <- Sys.time()
    where$x_1
    # [1] "2014-09-25 18:43:52 CEST"
    where$x_2 
    # [1] "2014-09-25 18:43:52 CEST"
    

    几个后续问题(如果您仍在“倾听”,主要针对 Joe)

    1. 如果我不通过字符串操作来处理env,我将如何访问/更改定义反应性的实际函数/闭包的环境(以防止需要声明环境两次)?

      func <- attributes(reactive(x_1 + 60*60*24))$observable$.func
      func
      # function () 
      # x_1 + 60 * 60 * 24
      # attr(,"_rs_shinyDebugPtr")
      # <pointer: 0x0000000008930380>
      # attr(,"_rs_shinyDebugId")
      # [1] 858
      # attr(,"_rs_shinyDebugLabel")
      # [1] "Reactive"  
      

      编辑: 想通了:environment(func)

    2. 有没有什么方法可以实现“相互依赖”,就像我上面的代码通过现有闪亮功能实现的那样?

    3. 只是一个“遥远”的想法,背后没有特定的用例:是否有可能让观察到的变量也存在于不同的环境中并且仍然有reactive()正确识别它们?

    再次感谢乔!

    【讨论】:

      【解决方案4】:

      /usr/local/lib/R/site-library/shiny/tests/ 位置有一组test_that 单元测试。它们让您很好地了解函数/包装器的方式:

      • reactiveValues
      • reactive
      • observe
      • isolate

      可以在shinyServer 调用之外使用。

      关键是使用flushReact 来关闭反应性。例如,这里是文件test-reactivity.r 中的一项测试,我认为它已经让您很好地了解了您需要做什么:

      test_that("overreactivity2", {
        # ----------------------------------------------
        # Test 1
        # B depends on A, and observer depends on A and B. The observer uses A and
        # B, in that order.
      
        # This is to store the value from observe()
        observed_value1 <- NA
        observed_value2 <- NA
      
        values <- reactiveValues(A=1)
        funcB  <- reactive({
          values$A + 5 
        })  
        obsC <- observe({
          observed_value1 <<-  funcB() * values$A
        })  
        obsD <- observe({
          observed_value2 <<-  funcB() * values$A
        })  
      
        flushReact()
        expect_equal(observed_value1, 6)   # Should be 1 * (1 + 5) = 6
        expect_equal(observed_value2, 6)   # Should be 1 * (1 + 5) = 6
        expect_equal(execCount(funcB), 1)
        expect_equal(execCount(obsC), 1)
        expect_equal(execCount(obsD), 1)
      
        values$A <- 2
        flushReact()
        expect_equal(observed_value1, 14)  # Should be 2 * (2 + 5) = 14
        expect_equal(observed_value2, 14)  # Should be 2 * (2 + 5) = 14
        expect_equal(execCount(funcB), 2)
        expect_equal(execCount(obsC), 2)
        expect_equal(execCount(obsD), 2)
      })
      

      【讨论】:

        【解决方案5】:

        感谢 Rappster、Joe 和 Robert,你们的谈话让我受益匪浅。

        我刚刚写了一个小工具来构建一个可缓存的函数,使用以下思想:

        library(shiny)
        gen.f <- function () {
            reactv <- reactiveValues()
        
            a <- reactive({ print('getting a()'); reactv$x + 1 })
            b <- reactive({ print('getting b()'); reactv$y + 1 })
            c <- reactive({ print('getting c()'); a() + b() })
        
            function (x.value, y.value) {
                reactv$x <<- x.value
                reactv$y <<- y.value
                isolate(c())
            }
        }
        f <- gen.f()
        

        在上面的例子中,返回函数的父环境 用于存储反应值和反应表达式。

        通过这样做,返回的函数将能够缓存它的 中间结果,如果函数不需要重新计算它们 用相同的参数进一步调用。底层的反应式表达式被包裹在里面,函数可以是 用作普通的 R 函数。

        > f(6,9)
        [1] "getting c()"
        [1] "getting a()"
        [1] "getting b()"
        [1] 17
        > f(6,9)
        [1] 17
        > f(6,7)
        [1] "getting c()"
        [1] "getting b()"
        [1] 15
        

        基于这个想法,我写了一个工具来帮助生成这种可缓存的 具有以下语法的函数。你可以在https://github.com/marlin-na/reactFunc看到我的回购

        myfunc <- reactFunc(
            # ARGV is the formal arguments of the returned function
            ARGV = alist(x = , y = ),
        
            # These are reactive expressions in the function argument form
            a = { print('getting a()'); x + 1 },
            b = { print('getting b()'); y + 1 },
            ans = { print('getting ans()'); a() + b() }
        )
        > myfunc(6, 9)
        [1] "getting ans()"
        [1] "getting a()"
        [1] "getting b()"
        [1] 17
        > myfunc(6, 9)
        [1] 17
        > myfunc(6, 7)
        [1] "getting ans()"
        [1] "getting b()"
        [1] 15
        

        问候,

        M;

        【讨论】:

        • 很高兴听到这个消息!这是社交编码,继续破解:-)
        猜你喜欢
        • 2021-02-09
        • 2017-06-28
        • 2017-07-15
        • 1970-01-01
        • 2015-06-23
        • 1970-01-01
        • 2017-09-19
        • 2021-01-12
        • 2020-11-20
        相关资源
        最近更新 更多