Связывание активных объектов в не блестящем контексте
Актуальный вопрос
Как вы могли бы либо приблизиться к реактивной среде/поведению, установленному shiny или, возможно, даже использовать эти самые функции в контексте не блестящего, чтобы создать "реактивные" переменные?
Фон
Я абсолютно очарован блестящей инфраструктурой и ее лежащими в основе парадигмами. В частности, в отношении установленной общей реактивной среды. Просто для этого было интересно, можно ли перенести эту реактивную парадигму программирования в не блестящий контекст, т.е. Обычное R-приложение/проект/пакет или, тем не менее, вы хотите его назвать.
Возможно, подумайте о вариантах: вы можете хотеть option_2
зависеть от значения option_1
для обеспечения
согласованные состояния данных. Если option_1
изменяется, option_2
также должен измениться.
Я думаю, что я идеально ищу что-то максимально эффективное, т.е. option_2
должен обновляться только при необходимости, т.е. когда option_1
фактически изменяется (в отличие от вычисления текущего состояния option_2
каждый раз запрашиваю опцию).
Due dilligence
Я немного поиграл со следующими функциями:
-
shiny::reactiveValues
-
shiny::reactive
-
shiny::observe
-
shiny::isolate
Но AFAIU, они, конечно, тесно связаны с блестящим контекстом.
Собственный прототип
Это очень простое решение, основанное на environment
s. Он работает, но
- Меня бы интересовали разные/лучшие подходы и
- Я подумал, что, возможно, можно каким-то образом использовать блестящий код.
Определение функции 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
Ответы
Ответ 1
В локации /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)
})
Ответ 2
Для тех, кого это интересует: это продолжало прослушивать меня в выходные, поэтому я собрал небольшой пакет под названием reactr, который на основе способа привязки можно определить с помощью makeActiveBinding
. Вы можете найти основную идею здесь.
Основные функции
- Поддерживаемые сценарии мониторинга: пакет позволяет определять простые сценарии мониторинга, а также более сложные, такие как произвольные функциональные отношения, взаимные привязки и различные среды для "исходных" и "целевых" переменных (см. аргументы
where
и where_watch
).
- Кэширование: этот способ создания привязок использует кешированные значения, где это возможно, по соображениям эффективности (если контролируемая переменная не изменилась, все равно использовать кешированное значение, а не повторять функцию привязки каждый раз).
- В качестве ссылки я все еще оставил решение на основе концепции в моем вопросе выше. Он доступен через
binding_type = 2
. Тем не менее, он не поддерживает использование синтаксических сахаров для assign()
и get()
(<-
и <obj-name>
или $<obj-name>
) для сохранения значений хэша в синхронизации - поэтому я бы не использовал его я думаю.
Минус
Что мне не очень нравится в этом, так это то, что мне нужна вспомогательная среда для хранения хеш-значений, которые сравниваются, чтобы принять решение "обновить кеш или вернуть кеш". Он по умолчанию плавает в where
, в настоящее время в where$._HASH
(см. ensureHashRegistryState()
, но по крайней мере вы можете изменить имя /ID к тому, который вам больше нравится или нужен (см. Аргумент .hash_id
).
Если кто-то знает, как избавиться от этого, было бы очень благодарно!: -)
Пример
См. README.md
Load:
require("devtools")
devtools::install_github("Rappster/classr")
devtools::install_github("Rappster/reactr")
require("reactr")
Используйте примерную среду, чтобы мы не испортили наш .GlobalEnv
:
where <- new.env()
Сценарий привязки 1: простой мониторинг (идентичные значения)
Задайте переменную, которая может контролироваться:
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
, и отражены изменения.
Чтобы сделать это, необходимо reset привязку для 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
. Вы должны фактически присвоить значение одному
из них через <-
после установление привязки:
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
.
Профилирование
Я включил профилирование script в /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
Для тех, кто интересуется подробными подробностями
Вот как выглядит шаблон шаблона, который подается на makeActiveBinding()
внутри setThis()
(исключая материал 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")
)
Ответ 3
(Пытался оставить это в качестве комментария, но С.О. сказал, что он слишком длинный.)
Престижность для более пристального изучения реактивности. Вы можете найти эти две ссылки полезными:
Таким образом, на самом деле Блестящая реактивность может использоваться вне приложений Shiny - с двумя трюками.
- Если вы попытаетесь прочитать реактивное выражение или реактивное значение с консоли, вы получите сообщение об ошибке. Я намеренно сделал это, потому что в принципиально реактивной системе, такой как Shiny, почти всегда есть ошибка, чтобы прочитать реактивную ценность или выражение из нереактивного контекста (надеюсь, это предложение имеет смысл, если вы прочитали две ссылки выше). Однако, когда вы едете на консоли, довольно разумно хотеть обойти эту проверку. Поэтому вы можете установить
options(shiny.suppressMissingContextError=TRUE)
, чтобы он исчез.
- Когда вы делаете что-то, что вызывает реактивность, наблюдатели фактически не выполняются до тех пор, пока вы не назовете
shiny:::flushReact()
. Это значит, что вы можете выполнить несколько обновлений, а затем дать ответному реактивному коду один раз, вместо пересчета с каждым обновлением. Для использования консоли вы можете попросить Shiny автоматически вызвать flushReact
в каждом приглашении консоли, используя shiny:::setAutoflush(TRUE)
. Опять же, это необходимо только для работы наблюдателей.
Пример, который работает сегодня (выполните эту строку за строкой на консоли):
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 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
просто верните предыдущее значение без пересчета.
Для более функционального твиста на блестящей реактивности см. новый пакет Hadley Wickham https://github.com/hadley/shinySignals, который вдохновлен Elm.
Надеюсь, что это поможет.
Ответ 4
Благодаря Rappster, Джо и Роберту, ваши разговоры действительно очень помогли мне.
Я только что написал небольшой инструмент для создания кешируемой функции, используя следующую идею:
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
Привет,
М;
Ответ 5
Благодаря указателям Joe я смог значительно упростить дизайн. Мне бы очень хотелось, чтобы не нуждался в том, чтобы беспокоиться о том, является ли какая-либо переменная реактивной переменной или нет (первая подразумевает, что вам нужно будет выполнить базовую функцию реактивного привязки через ()
, как в x_2()
в ответе Джо выше). Поэтому я попытался объединить код Джо с makeActiveBinding()
.
Pros
- больше нет необходимости в хэш-среде
where$._HASH
, а фактическая информация о реактивности остается до shiny
- это потрясающе, потому что, если кто-то знает, как справиться с реактивностью, сделанной в R, это, вероятно, RStudio.;-) Также, таким образом все это может быть даже совместимо с приложениями shiny
- ну, по крайней мере, теоретически; -)
- как указал Джо,
reactive()
не заботится о том, сколько наблюдаемых переменных вы ему кормите, если они находятся в одной среде (arg env
в reactive()
, arg where
в моем коде).
Против
- Я думаю, что вы теряете способность определять "взаимную зависимость" таким образом - по крайней мере, AFAICT. Теперь роли довольно ясны: есть переменная, которая может быть зависеть и может быть явно задана, а другая - действительно просто.
-
Возвращаемое значение reactive()
довольно сложно, поскольку оно предлагает гораздо более простой объект, чем фактически возвращается (что является ссылочным классом). Это затрудняет объединение с substitute()
"как есть", поскольку это приведет к несколько статической привязке (работает для самого первого цикла, но затем статично).
Мне нужно было использовать добрый старый обходной путь, чтобы полностью преобразовать все это в строку character
:
reactive_expr <- gsub(") $", ", env = where)", capture.output(reactive(x_1 + 60*60*24))
Вероятно, это немного опасно или ненадежно, но кажется, что в конце capture.output(reactive())
всегда есть это конечное пустое пространство, которое для нас является пустым, поскольку оно позволяет идентифицировать последний )
.
Кроме того, это также относится к виду Pro: поскольку where
добавляется внутри setReactive
, пользователю не нужно указывать 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 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"
Несколько последующих вопросов (в основном направленных Джо, если вы все еще "слушаете" )
-
Если вы не заботитесь об обрезке 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"
EDIT:
Выяснилось, что: environment(func)
-
Есть ли способ реализовать "взаимные зависимости", как реализованный с моим кодом выше с существующими блестящими функциональными возможностями?
-
Просто "далекая" мысль без конкретного варианта использования: можно ли иметь наблюдаемые переменные в разных средах, а также reactive()
распознать их соответствующим образом?
Еще раз спасибо, Джо!