Tutorial de dots
Chamdos de “três pontinhos”, “reticências”, “dots” ou “ellipsis”, os
...
são uma das funcionalidades mais comuns do R, mas ao mesmo tempo
uma das menos conhecidas. Explicá-los em linguagem técnica é muito
simples: eles são os argumentos
variádicos do R! O
difÃcil é entender de verdade o que eles são e como usá-los. Vamos
abandonar o jargão e sigamos em frente, agora em bom português…
Obs.: O nome correto no R para os ...
é dots, então vou usar esse
termo a partir de agora. A prova disso é que, para consultar a sua
documentação, executamos ?dots
.
Onde estão
Como eu disse anteriormente, eles são bastante comuns, mas quão comuns
exatamente? Talvez mais do que você imagine. Veja abaixo os
protótipos de
algumas poucas funções que talvez você conheça (ignore o NULL
, ele é
parte da saÃda da função args()
):
args(sum)
#> function (..., na.rm = FALSE)
#> NULL
args(c)
#> function (...)
#> NULL
args(dplyr::mutate)
#> function (.data, ...)
#> NULL
Te convenci? Entender os dots é, portanto, uma excelente arma no arsenal do programador de R, tanto que eles são usados pelas funções mais importantes da linguagem toda.
O que são
De forma bem geral, os dots são um argumento que, quando colocado na sua
função, pode ser substituÃdo por qualquer coisa pelo usuário. Na função
sum()
, por exemplo, os dots podem virar uma série de números (quantos
o usuário quiser).
sum(1, 2, 3, 4, 5)
#> [1] 15
Quando falamos de argumentos normais, não precisamos declarar seus argumentos caso estejamos utilizando-os na ordem correta. Os dots, entretanto, podem ser substituÃdos por qualquer número de objetos, então eles quebram essa regra; qualquer argumento que vier depois dos dots precisa ser nomeado.
# Não funciona do jeito esperado (TRUE mais um elemento dos dots)
sum(1, 2, NA, 4, 5, TRUE)
#> [1] NA
# Agora sim
sum(1, 2, NA, 4, 5, na.rm = TRUE)
#> [1] 12
Sem os dots a função sum()
estaria limitada a receber um vetor de
números, mas com essa ferramenta ela passa a poder receber números
separados como se fossem cada um um argumento. A função c()
,
entretanto, não poderia ser implementada sem os dots.
O seu poder completo, porém, fica mais claro na função
dplyr::select()
. Aqui vemos que podemos até dar nomes arbitrários para
os “argumentos” de dentro dos dots e a função pode usá-los sem o menor
problema:
mtcars |>
dplyr::select(mpg, cil = cyl, marcha = gear) |>
head()
#> mpg cil marcha
#> Mazda RX4 21.0 6 4
#> Mazda RX4 Wag 21.0 6 4
#> Datsun 710 22.8 4 4
#> Hornet 4 Drive 21.4 6 3
#> Hornet Sportabout 18.7 8 3
#> Valiant 18.1 6 3
Perceba que a função dplyr::select()
não tem como saber quantas
colunas nós vamos selecionar e quais nomes eu vou dar para cada uma,
tornando impossÃvel o uso de argumentos convencionais. O uso do dots é
inevitável nesses casos.
Como usá-los
Agora que já vimos universalidade e importância dos dots, chegou a hora de entender como eles funcionam e como usá-los. Vamos começar com um exemplo simples: criar uma função que captura quaisquer argumentos que o usuário resolver passar e imprime seus valores.
# Captura dots e os imprime como uma lista
captura <- function(...) {
list(...)
}
captura(arg1 = 1, arg2 = "b", arg3 = FALSE)
#> $arg1
#> [1] 1
#>
#> $arg2
#> [1] "b"
#>
#> $arg3
#> [1] FALSE
Simples, né? 90% das vezes podemos simplesmente transformar os dots em
uma lista comum com list(...)
e utilizá-la normalmente. Em breve
ficará mais claro por que isso funciona.
Se quisermos capturar argumentos especÃficos dentro dos dots, aà podemos
usar uma função especial chamada ...elt()
(sim, as reticências fazem
parte de seu nome):
# Captura dots e os imprime como uma lista
captura_segundo <- function(...) {
...elt(2)
}
captura_segundo(arg1 = 1, arg2 = "b", arg3 = FALSE)
#> [1] "b"
A terceira forma de usar os dots é os transportando para uma função que recebe dots. Como já deve ter ficado evidente, os dots podem ser substituÃdos por qualquer número de argumentos por parte do usuário, mas eles também podem ser passados como argumento no lugar dos dots de outra função!
filtra_seleciona <- function(marchas, ...) {
mtcars |>
dplyr::filter(gear == marchas) |>
dplyr::select(...) |>
head()
}
filtra_seleciona(4, mpg, cil = cyl)
#> mpg cil
#> Mazda RX4 21.0 6
#> Mazda RX4 Wag 21.0 6
#> Datsun 710 22.8 4
#> Merc 240D 24.4 4
#> Merc 230 22.8 4
#> Merc 280 19.2 6
No caso acima, os dots eram mpg, cil = cyl
e isso foi transportado
perfeitamente para dentro de dplyr::select()
.
Para fechar este tutorial com chave de ouro, vamos criar uma função
arbitrária que precisa de um argumento depois dos dots: nossa função
deve receber qualquer quantidade de valores numéricos, ignorar o
primeiro e somar o resto com n
.
ignora_um_soma_n <- function(..., n = 0) {
valores <- list(...)
valores <- valores[-1]
unlist(valores) + n
}
ignora_um_soma_n(1, 2, 3, 4, 5, n = 10)
#> [1] 12 13 14 15
Espero que agora esteja pelo menos um pouco mais claro o funcionamento dos dots! Se não, pode entrar em contato comigo via Twitter ou postar uma dúvida no nosso fórum.