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

Unexpected behavior of lmap_if when .f alters structure of .x and a function is supplied to .else #847

Closed
TimTeaFan opened this issue Dec 12, 2021 · 6 comments · Fixed by #895
Labels
bug an unexpected problem or unintended behavior map 🗺️

Comments

@TimTeaFan
Copy link

TimTeaFan commented Dec 12, 2021

I was playing around with the lmap_if function and encountered an unexpected behaviour when a function is provided to .else while the function in .f is altering the structure of the object that lmap_if is applied to. This behavior might be intended, in this case please close this issue.

# setup
library(purrr)

Here is a simple function, similar to the one in the documentation. This function just duplicates
a list element.

list_rep <- function(x) {
  out <- rep_len(x, 2)
  names(out) <- paste0(names(x), 1:2)
  out
}

This is the example list from the documentation:

x <- list(a = 1:4, b = letters[5:7], c = 8:9, d = letters[10])

Lets apply lmap_if and say we want to duplicate each numeric list element and we want to
replace all other elements with list(z = 1).

lmap_if(x, 
        .p = is.numeric,
        .f = list_rep,
        .else = ~ list(z = 1))
#> $a1
#> [1] 1 2 3 4
#> 
#> $z
#> [1] 1
#> 
#> $b
#> [1] "e" "f" "g"
#> 
#> $z
#> [1] 1
#> 
#> $c2
#> [1] 8 9
#> 
#> $d
#> [1] "j"

The output is somewhat unexpected. Rewritting lmap_if as a loop - at least the way I thought it would work - would yield the following result.

out <- vector("list", length = length(x))

for (i in seq_along(x)) {
  out[[i]] <- if (is.numeric(x[[i]])) {
    list_rep(x[i]) 
  } else {
    list(z = 1)
  }
}
out <- flatten(out)
out
#> $a1
#> [1] 1 2 3 4
#> 
#> $a2
#> [1] 1 2 3 4
#> 
#> $z
#> [1] 1
#> 
#> $c1
#> [1] 8 9
#> 
#> $c2
#> [1] 8 9
#> 
#> $z
#> [1] 1

The reason for this unexpected behavior is that lmap_if creates an selector based on the function in .p and then applies lmap_at first on all selected elements (which might alter the structure of .x) and then applies lmap_at again at the result of the first operation using all elements which were not selected. However, since the selector was created before the first call to lmap_at it doesn’t necessarily match the structure of .x anymore. Is this behavior intended and was just my mental model of what lmap_if is doing wrong?

Created on 2021-12-12 by the reprex package (v0.3.0)

@TimTeaFan

This comment was marked as outdated.

@hadley

This comment was marked as outdated.

@hadley hadley added the reprex needs a minimal reproducible example label Aug 23, 2022
@TimTeaFan

This comment was marked as outdated.

@hadley

This comment was marked as outdated.

@TimTeaFan

This comment was marked as outdated.

@hadley
Copy link
Member

hadley commented Aug 27, 2022

Somewhat more minimal reprex:

library(purrr)

x <- list("a", 99)
str(lmap_if(x, is.character, ~ list(1, 2), .else = ~ list(3, 4)))
#> List of 4
#>  $ : num 1
#>  $ : num 3
#>  $ : num 4
#>  $ : num 99

Created on 2022-08-27 by the reprex package (v2.0.1)

@hadley hadley added bug an unexpected problem or unintended behavior map 🗺️ and removed reprex needs a minimal reproducible example labels Aug 27, 2022
@hadley hadley mentioned this issue Aug 28, 2022
hadley added a commit that referenced this issue Aug 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug an unexpected problem or unintended behavior map 🗺️
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants