※この記事をもとにパッケージを作成しました
R にラムダ式を導入するパッケージ lambdaR を作った
(※恥ずかしながら、無名関数とラムダ式の違いがよく分かっていないのですが、この記事では、ラムダ式は無名関数の生成を簡単にする方法ということにして話を進めます)
いくつかのプログラミング言語には、無名関数を生成する方法として、ラムダ式が用意されています。
例えば:
increment = lambda x: x + 1
def increment = (x:Int) => x + 1
ところが、R には、ラムダ式が用意されておらず、次のようにして無名関数を生成します。
increment <- function(x) x + 1
今回、R にラムダ式を導入するための関数 lambda()
を作成してみました。
次のコードを実行してみて下さい。
install.packages("lazyeval") # インストールしていない人のみ
library(lazyeval)
lambda <- function(..., envir = parent.frame()) {
if(!require(lazyeval)) stop("Please install.packages('lazyeval')")
args <- lazyeval::lazy_dots(...)
if(length(args) == 0) return(function() {})
args <- Map(function(x) x$expr, args)
vars <- unlist(Map(function(x) deparse(x), args[-length(args)]))
expr <- as.character(args[length(args)])
expr <- strsplit(expr, ":")[[1]]
var <- expr[1]
expr <- paste0(expr[-1], collapse=":")
vars <- ifelse(is.null(vars),
var,
paste(paste(vars, collapse=","), var, sep=","))
fun_str <- sprintf("function(%s) %s", vars, expr)
eval(parse(text=fun_str), envir = envir)
}
これで関数 lambda()
により、下記のように無名関数を生成できるようになりました。
increment <- lambda(x: x+1)
print(increment)
increment(1:5)
function(x) x + 1
2 3 4 5 6
add <- lambda(x,y: x+y)
print(add)
add(1:5, 6:10)
function(x,y) x + y
7 9 11 13 15
従来の書き方とラムダ式を使った書き方を比較してみましょう。
add <- function(x,y) x + y #従来の書き方
add <- lambda(x,y: x + y) #ラムダ式を使用
多少はコードが短くなったようですが、ラムダ式のありがたみが分かりますか?
実は、ラムダ式は、関数を引数にとる関数(高階関数)と組み合わせることで実力を発揮します。
実例として、R のための関数型プログラミング用の関数を lambda()
関数を使って改良してみましょう。
例として Filter()
, Map()
, Reduce()
を改良します。
これらの関数については下記を参照してください。
まずは Filter()
です。
Filter_ <- function(data, ...) {
FUN <- lambda(..., envir = parent.frame())
Filter(FUN, data)
}
このようにラムダ式を使えるような新たな関数を用意することで、次のような書き方が可能になります。
data = 1:10
Filter(function(x) x %% 2 == 0, data) #従来の書き方
Filter_(data, x: x %% 2 == 0) #ラムダ式を使用
2 4 6 8 10
続いて Map()
も同じように変更します。
Map_ <- function(data, ...) {
FUN <- lambda(..., envir = parent.frame())
Map(FUN, data)
}
library(dplyr)
data = 1:10
Map(function(x) x ** 2, data) %>% unlist #従来の書き方
Map_(data, x: x ** 2) %>% unlist #ラムダ式を使用
1 4 9 16 25 36 49 64 81 100
さらに Reduce()
に対してもラムダ式版を作ります。
Reduce_ <- function(data, ..., init, right = FALSE, accumulate = FALSE) {
FUN <- lambda(..., envir = parent.frame())
Reduce(f=FUN, x=data, init=init, right=right, accumulate=accumulate)
}
data = 1:10
Reduce(function(x,y) x + y, data) #従来の書き方
Reduce_(data, x,y: x + y) #ラムダ式を使用
55
どうでしょう?
ラムダ式のありがたみはまだ分かりませんか?
それではこれらを組み合わせたコードを書いてみましょう。
#従来の書き方
data <- 1:10
fil <- Filter(function(x) x %% 2 == 0, data)
map <- Map(function(x) x**2, fil)
res <- Reduce(function(x,y) x+y, map)
print(res)
#ラムダ式を使用
library(dplyr)
1:10 %>% Filter_(x: x %% 2 == 0) %>% Map_(x: x**2) %>% Reduce_(x,y: x+y)
220
従来の書き方ではごちゃごちゃしていたコードが、めちゃめちゃすっきりしました!
ラムダ式と dplyr
のパイプ %>%
は非常に親和性が高く、相乗効果によりこれだけすっきりしたコードが書けるようになったのです。
どうでしょうか?
慣れないうちは、このコードは読みにくいかもしれません。
しかし、関数型プログラミングに慣れている人にとっては、R でもこのような書き方ができるということに、大きなメリットを感じるのではないでしょうか。
以上です。
追記
Map_()
関数が1変数にしか対応していないので2変数以上用の関数を作成してみました。
Map2_ <- function(data, ...) {
FUN <- lambda(...)
args <- c(f=FUN, data)
do.call(Map, args)
}
list(1:10, 2:11) %>% Map2_(x,y: x+y) %>% unlist
list(1:10, 2:11, 3:12) %>% Map2_(x,y,z: x+y+z) %>% unlist
3 5 7 9 11 13 15 17 19 21
6 9 12 15 18 21 24 27 30 33
どうにかして共通化したいですけど、無理ですかねー。
関連記事
参考
みんなで作ろう俺々ラムダ式 #rstatsj https://t.co/19En8dsTj1
— むっくり活動開始 (@TobakuCptlsm) 2014, 11月 20