vctrs icon indicating copy to clipboard operation
vctrs copied to clipboard

Draft `list_flatten()`

Open lionel- opened this issue 3 years ago • 4 comments

lionel- avatar Aug 10 '20 09:08 lionel-

Added a ptype argument that can be an atomic vector. In that case, the list is first flattened and then assembled into a vector of the requested type.

x <- list(1, list(2:3))

list_flatten(x, ptype = integer())
#> [1] 1 2 3

These are the same semantics as in rlang:

rlang::flatten_int(x)
#> [1] 1 2 3

Conceptually, rlang atomic flattens wrap list_flatten(), whereas purrr ones wrap list_unchop().

lionel- avatar Aug 10 '20 10:08 lionel-

I completely forgot that purrr converts atomic vectors to lists before flattening:

purrr::flatten(list(1, 2:3))
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] 3

This is not how list_flatten() currently works. Instead of chopping atomic vectors, it wraps them:

list_flatten(list(1, 2:3))
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2 3

Interestingly, switching to these semantics makes the list and non-list paths opposite of each other. For list flattening, all non-list elements are chopped. For atomic flattening, all list elements are unchopped:

  if (vec_is_list(ptype)) {
    # Chop all non-list elements so all the elements to concatenate are lists
    out <- map_if(x, negate(vec_is_list), vec_chop)
  } else {
    # Unchop all list elements so all the elements to concatenate are of type `ptype`
    out <- map_if(x, vec_is_list, vec_unchop, ptype = ptype, name_spec = name_spec)
  }

One disadvantage of these semantics is that it is an error to flatten lists that contain scalar elements:

# CRAN
flatten(list(function() NULL))
#> Error: Element 1 of `.x` must be a vector, not a function

I'm not sure what to think. On the one hand it seems to make sense that you could iteratively flatten a list until it no longer has list elements (is completely flat). In that case, non-list elements should be preserved as is. On the other hand, flatten() should normally be called after a splitting operation, and the purrr semantics makes it possible to deal with different kinds of splitting operations: ones that return atomic vectors, and ones that return lists.

I may be coming around to the current behaviour in purrr.

lionel- avatar Aug 10 '20 12:08 lionel-

This feels a little unexpected to me, I think I like how list_flatten() currently works. Not erroring on scalar elements seems useful to me.

purrr::flatten(list(1, 2:3))
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] 3

This feels sort of like a list_chop()

DavisVaughan avatar Aug 10 '20 14:08 DavisVaughan

I keep being surprised about how hard flatten() is 😅

lionel- avatar Aug 10 '20 14:08 lionel-

Closing in favour of https://github.com/tidyverse/purrr/pull/912

lionel- avatar Sep 08 '22 14:09 lionel-