この記事の内容は誤っているかもしれません。
そもそも、Rでforを使うのはダメ、というのは嘘。多分ベクトル化に関して誤解があります。
applyはただのラッパで、実際はforをつかうよ、の理由を書こうと思ったんだけど、@a_bickyさんがapplyの解説記事を書いてくれたので、そっちを見て下さい。とにかく、Rでもforは使うんです。
Rでは多重ループがよく出てくる。
多次元配列とか使うと出てきます。当たり前です。まあなんでもいいんですが、多重ループ途中で条件によって全ループ脱出したい、という場合があるかもしれません。
例えばC系だと、gotoを使うか、フラグを使うかします。以下の例は、trinoの走り書きっていうブログからお借りしました。ありがとう。
for( int z = 0; z < iSizeZ; ++z ){
for( int y = 0; y < iSizeY; ++y ){
for( int x = 0; x < iSizeX; ++x ){
if( ... ){
goto LABEL;
}
}
}
}
LABEL:
とか
bool bEnd = false;
for( int z = 0; z < iSizeZ; ++z ){
for( int y = 0; y < iSizeY; ++y ){
for( int x = 0; x < iSizeX; ++x ){
if( ... ){
bEnd = true;
}
if( bEnd ){ break; }
}
if( bEnd ){ break; }
}
if( bEnd ){ break; }
}
とか。あと、関数を使うのもあり。
void Loop()
{
for( int z = 0; z < iSizeZ; ++z ){
for( int y = 0; y < iSizeY; ++y ){
for( int x = 0; x < iSizeX; ++x ){
if( ... ){
return;
}
}
}
}
}
Rではフラグを使うか、関数を作るか、tryでエラー起こすか、というのが手ですが、
- フラグ面倒。バグ入りがち。
- 関数はあり。Rはどこでも関数が定義できるので、なおアリ。でもスコープの問題があって、関数の中の副作用(代入とかね)はデフォで関数内しか効かない。
- エラーtryは、予定外のエラーも拾ってパスしちゃうから、なし。一応
stop(e)
で例外投げて、分岐すればOKかもだけど、多重ループ脱出のために条件分岐してエラー投げて、受け取った側でエラーの中身見て条件分岐、とか、ないわ。
Rによる多重ループの脱出
前回の記事で書いたとおり、シグナリング機構を使うと多重ループが実装できる。
やってることは以下の通り、
- 関数(
._.()
)に多重ループ式を渡す。 - シグナル埋め込む(
.Internal(.addRestart(…))
)。 - 式を評価する関数を評価する。
- 多重ループ式中では、ある条件でシグナルを生成する(
.V.
)。 - 式を評価する関数の評価が停止して、呼び出し環境に戻る。
- 関数を抜ける。
という感じです。プロミスとして渡された式を評価してるので、式の環境は呼び出し元の環境になっています。つまりグローバルから呼び出したら、グローバルの変数の書き換えも可能ということ。要するに、スコープ気にしないで普通の式みたいに使える、ということ。
ラベル付きループ
Java(など)にはラベル付きループというものがあって、多重ループの中のどのループまで抜けるか指定できる。これは便利。こういうのね。
で、これをRで(あとmatlabでも)使えるといいなあ、と思っていたんだけど、多重ループ脱出のことを考えたら出来るような気がして、書いてみたらできた。CRANから使えます。
これフル脱出。
> library(labeledLoop)
> A %._.% for (i in 1:3) {
+ B %._.% for (j in 4:6) {
+ C %._.% for (k in 7:9) {
+ if (i == 2 && j == 5 && k == 8) {
+ print(c(i, j, k))
+ ._.(A)
+ }
+ }
+ }
+ }
[1] 2 5 8
真ん中を抜ける。
> A %._.% for (i in 1:3) {
+ B %._.% for (j in 4:6) {
+ C %._.% for (k in 7:9) {
+ if (k == 8) {
+ print(c(i, j, k))
+ ._.(B)
+ }
+ }
+ }
+ }
[1] 1 4 8
[1] 2 4 8
[1] 3 4 8
という感じです。
実装についてのメモ
ソースコードはgithubにおいてあります。
パッケージとは言っても、中身は%._.%
という演算子と._.
という関数があるだけ。あわせて10行くらいです。
`%._.%` <- function(label, statement) {
label <- substitute(label)
if (!is.character(label)) label <- deparse(substitute(label))
arg <- list(as.name("statement"), label)
names(arg) <- c("expr", label)
do.call("withRestarts", arg)
invisible()
}
`._.` <- function(label = ".") {
label <- substitute(label)
if (!is.character(label)) label <- deparse(substitute(label))
invokeRestart(label)
}
%._.%
は左辺にラベル、右辺に式をとる演算子です。ラベルを受けフラグとしてリスタートを仕込んでます(withRestarts
)。それだけ。
._.
はラベルを引数にする関数。リスタートを発行します。それだけ。
withRestarts
は任意の仮引数の「名前」で、invokeRestart
のフラグを判定するんだけど、一番面倒なのが、動的に渡されるラベルを使って引数を作るところ。まあeval(parse(text)))
しちゃえばいいんですが・・・。
まとめ
というわけで、@a_bickyさんのいうように、Rではfor使ったほうがいい場面が結構あります。そんな時に使えるライブラリかもしれません。