LoginSignup
4

More than 5 years have passed since last update.

R のラムダ式でプレースホルダを使えるようにする #rstatsj

Last updated at Posted at 2014-11-22

※この記事をもとにパッケージを作成しました
R にラムダ式を導入するパッケージ lambdaR を作った

前回、R にラムダ式を導入するという話をしました。

これにより、R でも次のようにラムダ式を使った書き方ができるようになりました。

R
library(dplyr)
1:10 %>% Filter_(x: x %% 2 == 0) %>% Map_(x: x**2) %>% Reduce_(x,y: x+y)

ところで、Scala のラムダ式ではプレースホルダというものが使えます。
例えば、次のようなコードがあったとします。

Scala
(1 to 10).filter(x => x % 2 == 0)
結果
Vector(2, 4, 6, 8, 10)

実際に Scala でプログラミングする際には、このような場合にラムダ式をこういう書き方はしません。
なぜなら、プレースホルダを使って次のように簡潔に書けるからです。

Scala
(1 to 10).filter(_ % 2 == 0)

この _ をプレースホルダと言って、受け取った変数がここに代入される感じで使えるという非常に便利な機能です。

これを R でも使えるようにできないでしょうか?
というわけで、前回の lambda() 関数を次のように書き換えてみました。

R
lambda <- function(..., envir = parent.frame()) {
  if(!require(stringr)) stop("Please install.packages('stringr')")
  if(!require(lazyeval)) stop("Please install.packages('lazyeval')")
  args <- lazyeval::lazy_dots(...)
  args_len <- length(args)
  args <- Map(function(x) x$expr, args)
  vars <- unlist(Map(function(x) deparse(x), args[-length(args)]))
  expr <- as.character(args[length(args)])
  if(args_len == 0) {
    function() {}
  } else if(args_len == 1 && !stringr::str_detect(expr, ":")) {
    if(stringr::str_detect(expr, "\\._")) {
      var_count <- stringr::str_count(expr, "\\._")
      vars <- paste0("x", seq_len(var_count))
      expr <- Reduce(function(expr,var) stringr::str_replace(expr, "\\._", var), vars, expr)
      vars_str = paste0(vars, collapse=",")
      func_str <- sprintf("function(%s) %s", vars_str, expr)
      eval(parse(text=func_str), envir = envir)
    } else {
      func <- eval(args[[1]], envir = envir)
      if(is.function(func)) {
        func
      } else {
        func_str <- sprintf("function() %s", expr)
        eval(parse(text=func_str), envir = envir)
      }
    }
  } else {
    expr <- stringr::str_split(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)
  }
}

これを使えば、ラムダ式でプレースホルダとして ._ が使えるようになります。

R
lambda(._ + 1)
lambda(._ + ._)
結果
function(x1) x1 + 1
function(x1,x2) x1 + x2

これで、冒頭に紹介したコードは次のように書けるようになりました。

R
1:10 %>% Filter_(._ %% 2 == 0) %>% Map_(._ ** 2) %>% Reduce_(._ + ._)

気持ちいいー!

3つの書き方を比較してみましょう。

R
library(dplyr)
# 無名関数
1:10 %>% Filter(function(x) x %% 2 == 0, .) %>% Map(function(x) x ** 2, .) %>% Reduce(function(x,y) x + y, .)
# ラムダ式
1:10 %>% Filter_(x: x %% 2 == 0) %>% Map_(x: x ** 2) %>% Reduce_(x,y: x + y)
# プレースホルダ
1:10 %>% Filter_(._ %% 2 == 0) %>% Map_(._ ** 2) %>% Reduce_(._ + ._)

やはりプレースホルダがあると便利ですね!

追記

こういう書き方もできます。

R
is_even <- lambda(._ %% 2 == 0)
square <- lambda(._ ** 2)
add <- lambda(._ + ._)
1:10 %>% Filter_(is_even) %>% Map_(square) %>% Reduce_(add)
結果
 220

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4