Giới thiệu

R là một ngôn ngữ lập trình đặc thù và đối với nhiều người đó là 1 ngôn ngữ lập dị. Dù vậy R lại rất linh hoạt để mở rộng xét về mặt cú pháp và bạn có thể dễ dàng định nghĩa 1 ngôn ngữ chuyên biệt (DSL - domain specific language) ngay bên trong R và bản thân ngôn ngữ lập trình vẫn đảm bảo được tính tương thích với các phiên bản trước đó một cách đáng ngạc nhiên (Câu chuyện không hề dễ dàng như vậy khi chúng ta nhìn vào Python 2 và Python 3). Việc này có thể gây ra phiền toái vì đôi khi để sử dụng 1 package mới bạn sẽ phải làm quen với tập cú pháp của 1 DSL không hề dễ chịu (có ai dùng data.table ở đây không?). Tuy nhiên, bằng việc giới thiệu thêm 1 toán tử mới (mà bản chất là 1 hàm - function) lại là 1 cuộc cách mạng với R và thậm chí thay đổi cách chúng ta tư duy giải quyết vấn đề. Đó là trường hợp của pipe trong R hay quen thuộc hơn là %>%.

Vài năm trở lại đây, cùng với sự phủ sóng của package dplyr, %>% đã quen thuộc với người sử dụng R. Tuy nhiên %>% không chỉ gói gọn trong dplyr hay Hadleyverse nói chung mà đã lan truyền triết lý đó sang rất nhiều package R khác, đặc biệt là các package liên quan đến data visualization (1 phần là do bản chất của data visualization). Thiết nghĩ cũng đã đến lúc dành 1 khoảng lặng để nhìn tìm hiểu khía cạnh lịch sử của pipe - %>% với R.

Trong lập trình, pipe là 1 khái niệm thường được dùng để mô tả việc sử dụng đầu ra của 1 hàm xử lý này để sử dụng như đầu vào của 1 hàm xử lý khác. Khi khối xử lý kéo dài thành 1 chuỗi liên tiếp nhau thì còn được gọi là chainning và có lẽ việc gắn kết liên tục thành luồng đó gợi đến hình tượng của ống nước nên người ta gọi hành vi nối đó là pipe chăng?

pipe có nguồn gốc từ lập trình bash Linux/Unix (nghĩa là từ rất lâu). Người quen thuộc với bash sẽ không cảm thấy mới nếu được giới thiệu với piping. Bài viết hướng về lịch sử của pipe trong R nên không đi sâu vào chủ đề pipe với Linux/Unix. Nếu quan tâm, bạn có thể tìm hiểu thêm ở đây.

Từ F#stackoverflow

Mọi chuyện khởi phát từ stackoverflow vào ngày 17/1/2012 khi user có tên là user4 hỏi về hiện thực 1 toán tử tương tự toán tử foward của FSharp (1 ngôn ngữ lập trình hàm hay được dùng trong tính toán tài chính định lượng) để có thể sử dụng dữ liệu cho cho 1 chuỗi các hàm như

data |> foo |> bar

với ý nghĩa dữ liệu data được thực thi hàm foo sau đó lấy kết quả đó thực thi hàm bar.

Ngay trong ngày, Ben Bolker nhanh chóng có câu trả lời cho vấn đề này dù bản thân ông không nghĩ rằng đó là khởi đầu cho 1 concept lớn hơn.

I don’t know how well it would hold up to any real use, but this seems (?) to do what you want, at least for single-argument functions

Và đây là hiện thực hóa đầu tiên bằng R của pipe:

"%>%" <- function(x,f) do.call(f,list(x))
pi %>% sin %>% cos
## [1] 1

Bạn có thể theo dõi luồng trao đổi đó ở đây

Bẵng đi 1 thời gian, cũng trên stackoverflow, 1 user khác cũng đề cập lại về việc hiện thực toán tử |> của F# trên R:

In R - Instead of:

plot(exp(cumsum(returns)))

I would love to do

returns |> cumsum |> exp |> plot

Câu hỏi này được admin của stackoverflow đánh giá là trùng về mặt ý tưởng với câu hỏi được đề cập ngay bên trên. Cách hiện thực toán tử lần này nhìn có vẻ dễ chịu hơn so với cách trên

`%|>%` = function(x, y) y(x)
1:10 %|>% cumsum %|>% plot

Với những chứng cứ có được trên Internet, ta có thể tạm kết luận rằng ý tưởng về pipe được nhen nhóm từ stackoverflow và lấy cảm hứng từ toán tử foward |> của F#.

Sự xuất hiện của dplyr

Ngày 28/10/2012, Hadley Wickham bắt đầu 1 dự án mã nguồn mở trên github mang tên dplyr. Ban đầu package có tên là plyr2 như là 1 phiên bản mở rộng tiếp theo của package plyr trước đó. dplyr được thiết kế để xử lý các tác vụ xử lý với data frame. Ban đầu dplyr gồm những hàm như arrange, mutate, summarise, và subset. Rất nhanh chóng sau đó, dplyr được xây dựng chung quanh 5 động từ như chúng ta biết ngày nay:

  • select
  • filter
  • mutate
  • summarise
  • arrange

Gần 1 năm sau đó, dplyr trình làng toán tử pipe đầu tiên là %.% thay thế cho hàm chain (trong package ptools của Peter Meilstrup). Những đoạn mã nguồn dưới đây cho chúng ta thấy quá trình tiến hóa của pipe trong dplyr:

Cách viết truyền thống

Bạn sẽ cần lồng các hàm xử lý với nhau và để hiểu thì đọc ngược từ bên trong:

library(hflights)
library(dplyr)
filter(
   summarise(
     select(
       group_by(hflights, Year, Month, DayofMonth), 
       Year:DayofMonth, ArrDelay, DepDelay
     ), 
     arr = mean(ArrDelay, na.rm = TRUE),  
     dep = mean(DepDelay, na.rm = TRUE)
   ), 
   arr > 30 | dep > 30
 )

Sử dụng hàm chain

Sử dụng chain giúp trình bày dễ nhìn hơn

chain(
   hflights,
   group_by(Year, Month, DayofMonth),
   select(Year:DayofMonth, ArrDelay, DepDelay),
   summarise(
     arr = mean(ArrDelay, na.rm = TRUE), 
     dep = mean(DepDelay, na.rm = TRUE)
   ),
   filter(arr > 30 | dep > 30)
 )

Dùng toán tử %.%

hflights %.%
   group_by(Year, Month, DayofMonth) %.%
   select(Year:DayofMonth, ArrDelay, DepDelay) %.%
   summarise(
     arr = mean(ArrDelay, na.rm = TRUE), 
     dep = mean(DepDelay, na.rm = TRUE)
   ) %.%
   filter(arr > 30 | dep > 30)

Với %.%, việc liên kết 1 loạt các thao tác xử lý dữ liệu phức tạp trở nên tự nhiên và gần với tinh thần của *nix vốn đã quen thuộc với rất nhiều lập trình viên. Mã nguồn và ý tưởng xử lý trở nên gần gũi với nhau và do đó tốc độ tư duy sẽ nhanh hơn khi ít phải đi qua quá trình thông dịch

magittr

Ngày 29/12/2013, Stefan Bache đặt lại vấn đề về pipe và đưa ra 1 hiện thực hóa khác với câu trả lời đã được chấp nhận trước đó:

How about:

`%>%` <- function(e1, e2) {
  cl <- match.call()
  e  <- do.call(substitute, list(cl[[3]], list(. = cl[[2]])))
  eval(e)
}

which allows a chain like:

iris %>% 
  subset(., Species == "setosa", select = -Species) %>% 
  colMeans(.)

Stefan Bache tiếp tục phát triển ý tưởng này thành package plumbr vào ngày hôm sau. Sau đó plumbr được đổi tên thành magittr. magittr là tên được đặt được lấy cảm hứng từ bức tranh nổi tiếng The Treachery of Images của họa sĩ người Bỉ René Magritte.

dplyrmagittr gặp nhau

17/1/2014, Hadley Wickham công bố chính thức về dplyr. Ở thời điểm đó, %.% và cả chain đang được sử dụng làm toán tử pipe. Ngay trong comment đầu tiên của thread đó, Stefan BacheHadley Wickham đã trao đổi với nhau về toán tử pipe.

Rồi thì những gì xảy ra sau đó đã trở thành lịch sử khi ngày 14/4/2014,%>% được sử dụng làm toán tử pipe trong dplyr. %.% vẫn còn được sử dụng song song 1 thời gian nữa cho đến ngày 1/8/2014 thì %.% được xem là depricated, %>% là toán tử pipe chính và chain thì chính thức không còn hỗ trợ nữa.

Kết

dplyr nói riêng và hệ sinh thái tidyverse (người hơi hoài cổ chút thì gọi là Hadleyverse) nói chung ngày nay rất phổ biến. Do đó nhiều người gần như đánh đồng piping operator/%>% với dplyr. Tuy nhiên pipe là 1 triết lý sâu xa có thể thay đổi (người viết nhìn như 1 cuộc cách mạng) trong phương thức suy nghĩ và lập trình với R. Đây cũng là 1 minh chứng tiêu biểu cho sự phát triển 1 ngôn ngữ lập trình mã nguồn mở ngày nay:

  • Khởi thủy từ 1 triết lý sâu xa
  • Học hỏi từ những cái hay đã có
  • Nhiều người xây dựng các giải pháp độc lập giải quyết những bài toán tương tự.
  • Thống nhất 1 giải pháp có thể dùng chung cho hầu hết mọi người.