-
Notifications
You must be signed in to change notification settings - Fork 292
Expand file tree
/
Copy pathpluck.R
More file actions
195 lines (188 loc) · 6.14 KB
/
pluck.R
File metadata and controls
195 lines (188 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#' Safely get or set an element deep within a nested data structure
#'
#' @description
#' `pluck()` implements a generalised form of `[[` that allow you to index
#' deeply and flexibly into data structures. (If you're looking for an
#' equivalent of `[`, see [keep_at()].) `pluck()` always succeeds, returning
#' `.default` if the index you are trying to access does not exist or is `NULL`.
#' (If you're looking for a variant that errors, try [chuck()].)
#'
#' `pluck<-()` is the assignment equivalent, allowing you to modify an object
#' deep within a nested data structure.
#'
#' `pluck_exists()` tells you whether or not an object exists using the
#' same rules as pluck (i.e. a `NULL` element is equivalent to an absent
#' element).
#'
#' @param .x,x A vector or environment
#' @param ... A list of accessors for indexing into the object. Can be
#' an positive integer, a negative integer (to index from the right),
#' a string (to index into names), or an accessor function
#' (except for the assignment variants which only support names and
#' positions). If the object being indexed is an S4 object,
#' accessing it by name will return the corresponding slot.
#'
#' [Dynamic dots][rlang::dyn-dots] are supported. In particular, if
#' your accessors are stored in a list, you can splice that in with
#' `!!!`.
#' @param .default Value to use if target is `NULL` or absent.
#'
#' @details
#' * You can pluck or chuck with standard accessors like integer
#' positions and string names, and also accepts arbitrary accessor
#' functions, i.e. functions that take an object and return some
#' internal piece.
#'
#' This is often more readable than a mix of operators and accessors
#' because it reads linearly and is free of syntactic
#' cruft. Compare: \code{accessor(x[[1]])$foo} to `pluck(x, 1,
#' accessor, "foo")`.
#'
#' * These accessors never partial-match. This is unlike `$` which
#' will select the `disp` object if you write `mtcars$di`.
#'
#' @seealso
#' * [attr_getter()] for creating attribute getters suitable for use
#' with `pluck()` and `chuck()`.
#' * [modify_in()] for applying a function to a plucked location.
#' * [keep_at()] is similar to `pluck()`, but retain the structure
#' of the list instead of converting it into a vector.
#' @export
#' @examples
#' # Let's create a list of data structures:
#' obj1 <- list("a", list(1, elt = "foo"))
#' obj2 <- list("b", list(2, elt = "bar"))
#' x <- list(obj1, obj2)
#'
#' # pluck() provides a way of retrieving objects from such data
#' # structures using a combination of numeric positions, vector or
#' # list names, and accessor functions.
#'
#' # Numeric positions index into the list by position, just like `[[`:
#' pluck(x, 1)
#' # same as x[[1]]
#'
#' # Index from the back
#' pluck(x, -1)
#' # same as x[[2]]
#'
#' pluck(x, 1, 2)
#' # same as x[[1]][[2]]
#'
#' # Supply names to index into named vectors:
#' pluck(x, 1, 2, "elt")
#' # same as x[[1]][[2]][["elt"]]
#'
#' # By default, pluck() consistently returns `NULL` when an element
#' # does not exist:
#' pluck(x, 10)
#' try(x[[10]])
#'
#' # You can also supply a default value for non-existing elements:
#' pluck(x, 10, .default = NA)
#'
#' # The map() functions use pluck() by default to retrieve multiple
#' # values from a list:
#' map_chr(x, 1)
#' map_int(x, c(2, 1))
#'
#' # pluck() also supports accessor functions:
#' my_element <- function(x) x[[2]]$elt
#' pluck(x, 1, my_element)
#' pluck(x, 2, my_element)
#'
#' # Even for this simple data structure, this is more readable than
#' # the alternative form because it requires you to read both from
#' # right-to-left and from left-to-right in different parts of the
#' # expression:
#' my_element(x[[1]])
#'
#' # If you have a list of accessors, you can splice those in with `!!!`:
#' idx <- list(1, my_element)
#' pluck(x, !!!idx)
pluck <- function(.x, ..., .default = NULL) {
check_dots_unnamed()
pluck_raw(.x, list2(...), .default = .default)
}
#' @rdname pluck
#' @inheritParams modify_in
#' @export
`pluck<-` <- function(.x, ..., value) {
assign_in(.x, list2(...), value)
}
#' @rdname pluck
#' @export
pluck_exists <- function(.x, ...) {
check_dots_unnamed()
!is_zap(pluck_raw(.x, list2(...), .default = zap()))
}
pluck_raw <- function(.x, index, .default = NULL) {
.Call(
pluck_impl,
x = .x,
index = index,
missing = .default,
strict = FALSE
)
}
#' Get an element deep within a nested data structure, failing if it doesn't
#' exist
#'
#' `chuck()` implements a generalised form of `[[` that allow you to index
#' deeply and flexibly into data structures. If the index you are trying to
#' access does not exist (or is `NULL`), it will throw (i.e. chuck) an error.
#'
#' @seealso [pluck()] for a quiet equivalent.
#' @inheritParams pluck
#' @export
#' @examples
#' x <- list(a = 1, b = 2)
#'
#' # When indexing an element that doesn't exist `[[` sometimes returns NULL:
#' x[["y"]]
#' # and sometimes errors:
#' try(x[[3]])
#'
#' # chuck() consistently errors:
#' try(chuck(x, "y"))
#' try(chuck(x, 3))
chuck <- function(.x, ...) {
check_dots_unnamed()
.Call(
pluck_impl,
x = .x,
index = list2(...),
missing = NULL,
strict = TRUE
)
}
#' Create an attribute getter function
#'
#' `attr_getter()` generates an attribute accessor function; i.e., it
#' generates a function for extracting an attribute with a given
#' name. Unlike the base R `attr()` function with default options, it
#' doesn't use partial matching.
#'
#' @param attr An attribute name as string.
#'
#' @seealso [pluck()]
#' @examples
#' # attr_getter() takes an attribute name and returns a function to
#' # access the attribute:
#' get_rownames <- attr_getter("row.names")
#' get_rownames(mtcars)
#'
#' # These getter functions are handy in conjunction with pluck() for
#' # extracting deeply into a data structure. Here we'll first
#' # extract by position, then by attribute:
#' obj1 <- structure("obj", obj_attr = "foo")
#' obj2 <- structure("obj", obj_attr = "bar")
#' x <- list(obj1, obj2)
#'
#' pluck(x, 1, attr_getter("obj_attr")) # From first object
#' pluck(x, 2, attr_getter("obj_attr")) # From second object
#' @export
attr_getter <- function(attr) {
force(attr)
function(x) attr(x, attr, exact = TRUE)
}