Игра со средами R

У меня странная динамика среды/области действия, которую я пытаюсь выяснить и ищу правильный или рекомендуемый метод для достижения этой цели.

Я сделал игрушечный пример моей проблемы ниже исключительно для иллюстрации. (Я знаю, что эту конкретную проблему можно решить гораздо проще, но она иллюстрирует динамику, которую я пытаюсь реализовать).

Текущий рабочий код:

master_function <- 
  function(x, iter = 100){
    x_p1 <- function(){ x <<- x + 1 }
    x_m1 <- function(){ x <<- x - 1 }

    path <- numeric(iter)
    for(i in 1:iter){
      next_step <- sample(c('p', 'm'), 1)
      if(next_step == 'p'){
        x_p1()
      } else { 
        x_m1()
      }
      path[i] <- x
    }
    path
  }

Проблема с этим кодом (особенно для действительно сложной проблемы) заключается в том, что он делает невозможной отладку содержимого функций x_p1, x_m1 с помощью утилиты отладки RStudio.

Надеясь реструктурировать код, чтобы он выглядел примерно так:

master_function <- 
  function(x, iter = 100){
    master_env <- environment()
    path <- numeric(iter)
    for(i in 1:iter){
      next_step <- sample(c('p', 'm'), 1)
      if(next_step == 'p'){
        x_p1(master_env)
      } else { 
        x_m1(master_env)
      }
      path[i] <- x
    }
    path
  }

x_p1 <- function(env){ assign('x', get('x', envir = env) + 1, envir = env) }
x_m1 <- function(env){ assign('x', get('x', envir = env) - 1, envir = env) }

Но это тоже очень некрасиво. Есть ли способ увеличить путь поиска, например, чтобы доступ к master_env был чище?

Редактировать: дополнительная информация по запросу @MrFlick По сути, у меня есть симуляция с большим количеством движущихся частей. По мере его развития запускаются различные события (на которые ссылаются подфункции), изменяющие состояние симуляции. Эти функции в настоящее время изменяют множество различных объектов состояния для каждого вызова функции. Поскольку функции создаются в вызове основной функции, я могу воспользоваться преимуществами лексической области видимости и оператора <<-, но теряю возможность отладки внутри этих функций.

Попытка выяснить, как создать эти функции вне основной симуляции. Если я правильно понимаю, если я сделаю функции такими, что они потребляют состояние симуляции и возвращают модифицированную версию, это приводит к большим затратам памяти.


person jameselmore    schedule 20.03.2019    source источник
comment
Я не могу понять, каковы требования из этого примера. Выглядит не очень показательно. Может быть, сказать словами, чего именно вы надеетесь достичь? кажется, вы хотите оптимизировать для отладки, но я не совсем уверен, что это значит в данном случае.   -  person MrFlick    schedule 20.03.2019
comment
@MrFlick отредактировал вопрос более подробно   -  person jameselmore    schedule 20.03.2019
comment
Я не понимаю, почему вы просто не отслеживаете всю информацию о своем состоянии в списке, а затем передаете текущее состояние своим функциям, и они могут при необходимости выводить обновленный список состояний. Это было бы гораздо больше похоже на R. Только изменяющиеся значения будут обновлены в памяти. У вас есть пример, который показывает большую стоимость памяти?   -  person MrFlick    schedule 20.03.2019


Ответы (2)


1) трассировка Используйте trace, чтобы вставить операторы debug после определений x_p1 и x_m1, а затем можно пройти через них при запуске master_function.

trace(master_function, at = 4, quote({debug(x_p1); debug(x_m1) }))

untrace(master_function) отключает это. Используйте body(master_function)[4], чтобы увидеть, какая строка соответствует 4. См. ?trace для получения дополнительной информации.

2) инструмент. Другой вариант — настроить вашу функцию таким образом, а затем вызвать ее с помощью master(function(x, DEBUG = TRUE), чтобы включить отладку.

master_function <- 
  function(x, iter = 100, DEBUG = FALSE){
    x_p1 <- function(){ x <<- x + 1 }
    x_m1 <- function(){ x <<- x - 1 }
    if (DEBUG) {
      debug(x_p1)
      debug(x_m1)
    }

    path <- numeric(iter)
    for(i in 1:iter){
      next_step <- sample(c('p', 'm'), 1)
      if(next_step == 'p'){
        x_p1()
      } else { 
        x_m1()
      }
      path[i] <- x
    }
    path
  }
person G. Grothendieck    schedule 20.03.2019

Почему x вообще должен находиться в альтернативной среде? Следующее усваивает и полностью избегает множественных сред.

x_p1 <- function(z){ z + 1 }
x_m1 <- function(z){ z - 1 }
master_function <- 
  function(x, iter = 100){
    new_x <- x


    path <- numeric(iter)
    for(i in 1:iter){
      next_step <- sample(c('p', 'm'), 1)
      if(next_step == 'p'){
        new_x <- x_p1(new_x)
      } else { 
        new_x <- x_m1(new_x)
      }
      path[i] <- new_x
    }
    path
  }
person Soren    schedule 20.03.2019
comment
Это чрезвычайно упрощенная версия того, чего я на самом деле пытаюсь достичь. Внутри master_function есть несколько десятков объектов и 5-6 функций, предназначенных для условного изменения некоторых или всех этих объектов в среде master_function. Если я придерживаюсь подхода, подобного описанному выше, мне будет сложно отлаживать какие-либо ошибки, поскольку я не могу размещать точки останова в этих функциях. - person jameselmore; 20.03.2019
comment
Я обновил предложение разместить x_p1 и x_m1 полностью за пределами master_function, что, возможно, еще больше подталкивает исходное предложение к изоляции каждой функции в автономной области среды. Отказ от глобальных или перекрестных переменных среды одновременно упрощает отладку. Учитывая простоту примера, его трудно оценить, но в большинстве случаев функции могут работать, используя только входные переменные; и даже может работать с несколькими переменными и возвращать их, возвращая список - person Soren; 20.03.2019
comment
@jameselmore: иметь разные места в вашем коде, изменяющие переменные в удаленных средах, не рекомендуется, поскольку это приводит к запутанной логике кода. Поместите все свои переменные в список, затем передайте весь список своим 5-6 функциям для изменения определенных элементов. Каждая функция должна возвращать измененный список. Это известно как семантика копирования при изменении, которая естественно подходит для функциональных языков, таких как R. - person Artem Sokolov; 20.03.2019
comment
@Soren: из-за того, что R передается по значению, new_x <- x в ответе не требуется. Любые изменения в x внутри master_function() не видны вне функции. - person Artem Sokolov; 20.03.2019
comment
@ArtemSokolov согласился с тем, что new_x не нужен, но ввел его, поскольку OP, возможно, требовалось, чтобы «x» и внешняя глобальная переменная «x» содержали отдельные значения. - person Soren; 20.03.2019
comment
@ArtemSokolov - имеет смысл, но некоторые из моих объектов очень большие. Я полагаю, что этот подход сопряжен со значительными затратами на производительность, не так ли? - person jameselmore; 20.03.2019
comment
@jameselmore, если вас так беспокоит память, напишите свой код на C ++ и используйте Rcpp для взаимодействия с ним. Подсчет байтов не использует сильные стороны R. - person Hong Ooi; 20.03.2019
comment
@HongOoi, конечно, но это займет много времени. У меня есть кое-что, что работает сейчас и хорошо служит своей цели в R, поскольку он очень гибкий. Вопрос в том, есть ли в R способ слегка структурировать мой существующий код, чтобы с ним было немного легче работать. - person jameselmore; 20.03.2019
comment
data.table использует семантику модификации по ссылке, что позволяет избежать наличия копий data.frame или подобных объектов в памяти. cran.r-project.org/ веб/пакеты/data.table/виньетки/ - person Soren; 20.03.2019
comment
В общем, R и большинство популярных пакетов представляют семантику копирования при изменении, но на самом деле копируют только тогда, когда это необходимо за кулисами. Как всегда, лучший способ измерить влияние изменений кода на память — это профилирование< /а>. - person Artem Sokolov; 20.03.2019
comment
@jameselmore перепишите свой код и беспокойтесь о других вещах, если это действительно станет проблемой - person Hong Ooi; 20.03.2019