Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Teal refactor #66

Merged
merged 31 commits into from
Nov 4, 2022
Merged

Teal refactor #66

merged 31 commits into from
Nov 4, 2022

Conversation

pawelru
Copy link
Contributor

@pawelru pawelru commented Sep 15, 2022

gogonzo and others added 2 commits September 2, 2022 11:07
* constructor, eval_code, lockEnvironment  - need validation

* prototype

* constructor for the list of data (possibly reactive)

* progress with join quosure

* - added more constructors 
- test constructors
- test join

* update WIP

* fix constructor from list

* Update Quosure.R

* add id to the code, resolve join conflicts, more test

* implement id field in Quosure to distinguish detect code entries
R/quosure-utils.R Outdated Show resolved Hide resolved
R/quosure-utils.R Outdated Show resolved Hide resolved
R/quosure-constructor.R Outdated Show resolved Hide resolved
R/quosure-constructor.R Outdated Show resolved Hide resolved
R/quosure-constructor.R Outdated Show resolved Hide resolved
R/quosure-eval_code.R Outdated Show resolved Hide resolved
R/quosure-join.R Outdated Show resolved Hide resolved
R/quosure-join.R Outdated Show resolved Hide resolved
R/quosure-show.R Outdated Show resolved Hide resolved
R/quosure-join.R Outdated Show resolved Hide resolved
R/quosure-join.R Outdated
Comment on lines 78 to 80
is_overwritten <- vapply(common_names, function(el) {
!identical(get(el, x@env), get(el, y@env))
}, logical(1))
Copy link
Contributor Author

@pawelru pawelru Sep 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little bit concerned here about get() operation as it could evaluate lazy bindings or promises therefore this functionality could be expensive and most importantly - it could have some side effects. I neither have a counter-example nor a potential solution. Just note to myself...

R/quosure-join.R Outdated Show resolved Hide resolved
R/quosure-join.R Outdated
#' - indices of the shared code are not consecutive or don't start from 1
#' @param x (`Quosure`)
#' @param y (`Quosure`)
#' @return `TRUE` if able to join or `character` used to print error message.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's always good to assure that function always returns the same data type. This would prevent you doing expect_true(foo(...)) and expect_match(foo(...), "...") at the same time.

This is not the first time where we are facing the problem of returning a character message if something else TRUE. I suggest to always return boolean with additional attribute. Alternatively you might want to use checkmate assertion collections.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. As a user it can be annoying when a function returns 2 types of objects. ('user' includes developer w/in package)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is internal, user shouldn't use this function at all. .check_joinable is used only in the join in following way:

  join_validation <- .check_joinable(x, y)

  # join expressions
  if (!isTRUE(join_validation)) {
    stop(join_validation)
  }

Alternatively, we can move stop to the .check_joinable and forget the whole thing

R/quosure-join.R Outdated Show resolved Hide resolved
R/quosure-class.R Outdated Show resolved Hide resolved
R/quosure-class.R Outdated Show resolved Hide resolved
R/quosure-constructor.R Outdated Show resolved Hide resolved
R/quosure-constructor.R Outdated Show resolved Hide resolved
@gogonzo gogonzo self-assigned this Oct 28, 2022
vignettes/qenv.Rmd Outdated Show resolved Hide resolved
Signed-off-by: Nikolas Burkoff <nikolas.burkoff@tessella.com>
Signed-off-by: Nikolas Burkoff <nikolas.burkoff@tessella.com>
#' q1 <- new_qenv(code = "print('a')", env = new.env())
#' q1
#' @export
setMethod("show", "qenv", function(object) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #88 (for later)

vignettes/qenv.Rmd Outdated Show resolved Hide resolved
Nikolas Burkoff added 2 commits November 3, 2022 10:10
Signed-off-by: Nikolas Burkoff <nikolas.burkoff@tessella.com>
Signed-off-by: Nikolas Burkoff <nikolas.burkoff@tessella.com>
Comment on lines +28 to +35
q1 <- eval_code(new_qenv(), quote(library(checkmate)))
# library call adds package env in the search path just over .GlobalEnv
# it means that q3@env before the call was a parent of .GlobalEnv but not after the call

q2 <- eval_code(q1, quote(assert_number(1)))
testthat::expect_identical(parent.env(q2@env), parent.env(.GlobalEnv))

detach("package:checkmate", unload = TRUE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming this test leaves the status of the checkmate package unchanged (we don't need a on.exit(detach("package:checkmate")) on line 29 to make sure that it is detached if something goes wrong in lines 32 and 33? @gogonzo

Copy link
Contributor

@nikolas-burkoff nikolas-burkoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets do this 🦾

DESCRIPTION Outdated
@@ -19,14 +19,17 @@ Depends:
R (>= 4.0)
Imports:
checkmate,
cli,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we remove cli dependency? It's a big package

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we do then we don't fix this:
#86 - unless you want to fix it a different way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's my question - can we do (name that thing) differently not involving cli package?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this is what we call:

> cli::ansi_strip
function (string, sgr = TRUE, csi = TRUE, link = TRUE) 
{
    if (!is.character(string)) 
        string <- as.character(string)
    string <- enc2utf8(string)
    stopifnot(is_flag(sgr), is_flag(csi), is_flag(link))
    clean <- .Call(clic_ansi_strip, string, sgr, csi, link)
    class(clean) <- setdiff(class(clean), c("cli_ansi_string", 
        "ansi_string"))
    clean
}
<bytecode: 0x0000018dd338c3e0>
<environment: namespace:cli>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and dplyr imports cli so in practice ppl using this package will have cli installed but if there's an easy way round this then happy to change (or put issue in bnacklog to change)

Copy link
Contributor

@nikolas-burkoff nikolas-burkoff Nov 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could add it into suggests like rlang does and only call it if installed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I am more concerned about the package size and the installation process. Up to you how would you like to process it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could add it into suggests like rlang and only call it if installed?

This only makes sense if a particular piece of code is unlikely to be called assuming standard workflow. E.g. a wide tree-based workflow / case when where there are many many possibilities. Here, if I understand everything correctly it's a part of standard logic so it's very likely that it will be executed.

Copy link
Contributor

@nikolas-burkoff nikolas-burkoff Nov 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I assume that these funny terminal printing characters added by rlang are only added if cli is installed (cli is a suggests in rlang only) - therefore I think it makes sense for us to use suggests as well - as striping these terminal printing characters out only makes sense if cli (via rlang) added them in the first place - commit coming soon

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so rlang is only in the suggests but needs to be imported - whoops

@pawelru
Copy link
Contributor Author

pawelru commented Nov 3, 2022

And we are one step away from test coverage perfection :). In the covr report there is only one line non-covered in tests:

Filename                 Stmts    Miss  Cover    Missing
---------------------  -------  ------  -------  ---------------------------------------------------------------------------------------------------------------------------------------------
R/qenv-eval_code.R          45       1  97.78%   89

@gogonzo gogonzo mentioned this pull request Nov 3, 2022
@gogonzo
Copy link
Contributor

gogonzo commented Nov 3, 2022

For anyone concerned about deep coping the objects. There are no deep copies in eval_code and join, we only create a new environment each time, set parent to .globalEnv and we create a new reference to the same pointer in the new env.

library(teal.code)
library(lobstr)
x <- data.frame(a = runif(10))

q1 <- new_qenv(env = list2env(list(x = x)), code = "x <- data.frame(a = 1)")

identical(lobstr::ref(q1[["x"]]), lobstr::ref(x))
# TRUE

q2 <- eval_code(q1, quote(x$b <- runif(10)))

!identical(lobstr::ref(q2[["x"]]), lobstr::ref(q1[["x"]]))
# TRUE
identical(lobstr::ref(q2[["x"]]$a), lobstr::ref(q1[["x"]]$a))
# TRUE

q3 <- eval_code(q2, quote(y <- x))
identical(q3[["y"]], q2[["x"]])
# TRUE

q4 <- join(q2, q3)
identical(lobstr::ref(q4[["y"]]), lobstr::ref(q2[["x"]]))
# TRUE

@donyunardi
Copy link
Contributor

qenv vignette was clear and easy to follow. Great job!

@gogonzo gogonzo merged commit 2fc8018 into main Nov 4, 2022
@gogonzo gogonzo deleted the teal_refactor@main branch November 4, 2022 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants