Rで関数型プログラミング

背景

1年前の自分が、こんなデータを作っていた。

  • データ全体は、recordのリスト
  • recordは、そのrecordが作成された時刻とdataを納めたリスト
  • dataは、entryのリスト
  • entryは、TYPE_A, TYPE_B, TYPE_Cの3つの数値ベクトルを納めたリスト。但し、長さ0かもしれない。また、それぞれの長さは異なる。

1 recordをコンソールに出力すると、こんな感じ。

[[2]]
[[2]]$time
[1] 0.3170583

[[2]]$data
[[2]]$data$ENTRYNAME_LSFU
[[2]]$data$ENTRYNAME_LSFU$TYPE_A
[1] -0.02828374 -0.65252939

[[2]]$data$ENTRYNAME_LSFU$TYPE_B
[1]  1.4714831 -0.4096999 -0.2346018  0.4678917 -0.3272068

[[2]]$data$ENTRYNAME_LSFU$TYPE_C
numeric(0)


[[2]]$data$ENTRYNAME_DZQY
[[2]]$data$ENTRYNAME_DZQY$TYPE_A
numeric(0)

[[2]]$data$ENTRYNAME_DZQY$TYPE_B
[1] 1.877881

[[2]]$data$ENTRYNAME_DZQY$TYPE_C
numeric(0)


[[2]]$data$ENTRYNAME_GZDP
[[2]]$data$ENTRYNAME_GZDP$TYPE_A
[1] -0.5423344  0.8562093  1.1156591

[[2]]$data$ENTRYNAME_GZDP$TYPE_B
[1] -1.54553885  0.04345684 -0.65033450 -0.19447399

[[2]]$data$ENTRYNAME_GZDP$TYPE_C
[1] 0.7071812

で、このデータを、(時間、データタイプ、値)を持つデータフレームに変換する必要に迫られた。

解法

素直に考えたらfor文回すか、となるんだけど、Map, Reduceを使う練習をしてみたら、とてもシンプルに書けたので、メモしておく。初めて、関数型プログラミングって便利だ、と思った。

make.for.each.type <- function(entry){
  function(type){
    v <- entry[[type]]
    data.frame(type=type,
               value=if(length(v)) v else NA)
  }
}

for.each.entry <- function(entry){
  types <- c("TYPE_A", "TYPE_B", "TYPE_C")
  for.each.type <- make.for.each.type(entry)
  Reduce(rbind, Map(for.each.type, types))
}

for.each.record <- function(record){
  cbind(t=record$time, Reduce(rbind, Map(for.each.entry, record$data)))
}

make.data.frame <- function(record.list){
  Reduce(rbind, Map(for.each.record, record.list))
}
考察と結論

for.each.type 関数はentryへの参照を保持したクロージャとして作成しなければならなくなっている。もし、entryの構造が、

  • entryは、複数のentry elementから成る。
  • entry elementはtype と value を納めたリスト。

という風だったら、素直に書けたはず。さらに、今回は必要なかったけど、結果のデータフレームにエントリー名も必要ならば、entryの構造を、

  • entryは、entry nameと複数のentry elementから成る。

とした方が便利だっただろう。
listを木構造を表すように使う場合には、要素名は、xmlのタグのように扱った方が便利みたいだ。但し、これだと、特定の名前を持つentryへのアクセスに 演算子が使えんかったりするので、要所要所で使い分けか。