-
Notifications
You must be signed in to change notification settings - Fork 8
/
pipe.Rmd
225 lines (181 loc) · 7.1 KB
/
pipe.Rmd
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# Pipe {#pipe}
Dificilmente alguém que programa R passa muito tempo sem ouvir falar do operador
pipe (`%>%`). Saber o que ele faz e significa, no entanto, é algo mais complexo.
Apesar de não passar de uma função como outra qualquer, os efeitos que ele pode
ter no visual e na compreensibilidade de um código são imensos.
Entender o pipe em profundidade pode levar muito tempo, mas o básico já é
suficiente para a maioria das pessoas. É importante ter pelo menos uma ideia do
que ele faz caso você acabe se deparando com um código que o utiliza e, quem
sabe um dia, usá-lo nos seus próprios programas.
No fundo, o conceito de pipe existe pelo menos desde os anos 1970. De acordo com
seu criador, Douglas McIlroy, o operador foi concebido em "uma noite febril" e
tinha o objetivo de simplificar comandos cujos resultados deveriam ser passados
para outros comandos.
```sh
ls | cat
# Desktop
# Documents
# Downloads
# Music
# Pictures
# Public
# Templates
# Videos
```
Com esse exemplo já é possível ter uma ideia de onde vem o seu nome: _pipe_ em
inglês significa "cano", referindo-se ao transporte das saídas dos comandos. Em
português o termo é traduzido preferencialmente como "encadeamento", mas no
dia-a-dia é mais comum usar o termo em inglês.
A partir daí o pipe tem aparecido nas mais diversas aplicações, desde HTML até o
próprio R. Ele pode aparecer em diferentes formas, mas o seu objetivo é sempre o
mesmo: canalizar resultados de um comando para o outro.
## Como funciona
Em R, o pipe tem uma aparência bastante particular (`%>%`), mas no fundo ele não
passa de uma função infixa, ou seja, uma função que aparece entre os seus
argumentos (como `a + b` ou `a %in% b`). Na verdade é por isso mesmo que ele tem
porcentagens antes e depois: no R uma função infixa só pode ser declarada assim
(vide o próprio `%in%()`).
Se você estiver no RStudio, como sugerido anteriormente, para usar o pipe basta
carregar a biblioteca `magrittr` e utilizar o atalho **Ctrl + Shift + M**; essas
inocentes teclas irão fazê-lo aparecer magicamente diante dos seus olhos. Abaixo
é possível notar como o pipe não passa de uma função como outra qualquer (ignore
os acentos graves):
```r
library(magrittr)
`%>%`("oi", print)
#> [1] "oi"
```
Perceba que, no código acima, o primeiro argumento do pipe (`"oi"`) virou a
entrada do seu segundo argumento (a função `print()`). Abaixo o pipe está na sua
forma mais tradicional entre seus dois argumentos:
```r
"oi" %>% print()
#> [1] "oi"
```
Observe agora o comando abaixo. Queremos primeiro somar 3 a uma sequência de
números e depois dividí-los por 2:
```r
mais_tres <- function(x) { x + 3 }
sobre_dois <- function(x) { x / 2 }
x <- 1:3
sobre_dois(mais_tres(x))
#> [1] 2.0 2.5 3.0
```
Perceba como fica difícil de entender o que acontece primeiro. A linha relevante
começa com a divisão por 2, depois vem a soma com 3 e, ao fim, os valores de
entrada. É exatamente o oposto da nossa ordem de leitura da esquerda para a
direita.
Nesse tipo de situação é mais legível usar a notação de [composição de
funções](https://pt.wikipedia.org/wiki/Composi%C3%A7%C3%A3o_de_fun%C3%A7%C3%B5es),
com as funções sendo exibidas na ordem em que serão aplicadas: $f \circ g$. Não
é necessário conhecer essa notação, basta imaginar quão mais legível ficaria
aquele comando se houvesse algum recurso que passasse o que o resultado do que
está à sua esquerda para a função que está à sua direita. Esse é o pipe.
```r
x %>% mais_tres() %>% sobre_dois()
#> [1] 2.0 2.5 3.0
```
No comando acima fica evidente que pegamos o objeto `x`, somamos 3 e dividimos
por 2.
Perceba que a entrada de um pipe (esquerda) sempre é passada como o _primeiro_
argumento da função à sua direita. Isso não impede que as funções utilizadas em
uma sequência de pipes tenham outros argumentos.
```r
mais_n <- function(x, n) { x + n }
x %>% mais_n(4) %>% sobre_dois()
#> [1] 2.5 3.0 3.5
```
## Vantagens
A grande vantagem do pipe não é só enxergar quais funções são aplicadas
primeiro, mas sim nos ajudar a programar pipelines ("encanamento" em inglês) de
tratamentos de dados.
```r
library(dplyr)
starwars %>%
mutate(bmi = mass/((height/100)^2)) %>%
select(name, bmi, species) %>%
group_by(species) %>%
summarise(bmi = mean(bmi))
#> # A tibble: 38 x 2
#> species bmi
#> <chr> <dbl>
#> 1 Aleena 24.0
#> 2 Besalisk 26.0
#> 3 Cerean 20.9
#> 4 Chagrian NA
#> 5 Clawdite 19.5
#> 6 Droid NA
#> 7 Dug 31.9
#> 8 Ewok 25.8
#> 9 Geonosian 23.9
#> 10 Gungan NA
#> # ... with 28 more rows
```
Acima fica extremamente claro o que está acontecendo em cada passo da pipeline.
Partindo da base `starwars`, primeiro transformamos, depois selecionamos,
agrupamos e resumimos: em cada linha temos uma operação e elas são executadas
em sequência.
Isso não melhora só a legibilidade do código, mas também a sua
_debugabilidade_^[Ainda não encontrei um termo melhor em português para esse
conceito]. Se tivermos encontrado um bug na pipeline, basta executar o
encadeamento linha a linha até que encontremos o comando problemático. Com o
pipe é possível programar de forma mais compacta, legível e correta.
Todos os exemplos acima envolvem passar a entrada do pipe como o primeiro
argumento da função à direita, mas esta não é uma obrigatoriedade. Com o
operador _placeholder_ `.` podemos indicar exatamente onde deve ser colocado o
valor que chega no pipe:
```r
y_menos_x <- function(x, y) { y - x }
x %>%
mais_tres() %>%
purrr::map2(4:6, ., y_menos_x)
# [[1]]
# [1] 0
#
# [[2]]
# [1] 0
#
# [[3]]
# [1] 0
```
Outra funcionalidade interessante e pouco conhecida do pipe são as funções
unárias. Se você estiver familiarizado com o [pacote
`purrr`](https://lente.dev/posts/magica-purrr/), esse é um jeito bastante
simples de criar funções descartáveis.
```r
m3_s2 <- . %>%
mais_tres() %>%
sobre_dois()
m3_s2(x)
#> [1] 2.0 2.5 3.0
```
Usando novamente o `.` definimos uma função que recebe apenas um argumento com
uma sequência de aplicações de outras funções. Mas não se preocupe se as funções
unárias tiverem parecido algo de outro mundo porque é realmente muito raro
encontrá-las nos código alheios.
Por fim, caso você queira utilizar o pipe dentro do seu próprio pacote, basta
executar uma simples função do `usethis`: `use_pipe()`. Ela adiciona o
`magrittr` como uma dependência do seu pacote e faz com que o pipe seja
acessível dentro do mesmo, o que realmente facilita o trabalho de quem não quer entender o significado do código a seguir:
```r
#' Pipe operator
#'
#' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details.
#'
#' @name %>%
#' @rdname pipe
#' @keywords internal
#' @export
#' @importFrom magrittr %>%
#' @usage lhs \%>\% rhs
NULL
```
Se você não estiver trabalhando em um pacote, mas também não quer carregar o
pacote com `library()`, há duas formas de trazer o pipe para os seus scripts:
puxando ele como uma função ou usando o argumento `include.only` da `library()`.
```r
# Puxando como função
`%>%` <- magrittr::`%>%`
# Usando include.only
library(magrittr, include.only = "%>%")
```