Hatena::ブログ(Diary)

やねうらお−ノーゲーム・ノーライフ このページをアンテナに追加 RSSフィード

GT-Rの買取ならここですわ。どこよりも高く買取ってもらえるはず。お勧め!GT-R 買取
電王戦出場記念! 書籍化されたで! 監修したで!(`ω´) 絶版なってしもた Kindle版で復活!! 記事書いたで!
解析魔法少女美咲ちゃん マジカル・オープン!

YaneuLabs / やねうら王公式 / やねうらおにメール / twitter / プロフィール

 | 

2009-12-02 美しいループ記法求む!

[] 美しいループ記法求む!  美しいループ記法求む!を含むブックマーク  美しいループ記法求む!のブックマークコメント


いま私は速度的に特化したプログラムを書かなくてはいけない。1%でも速いほうが望ましい。必然的に言語C/C++となる。しかし、いまさらC/C++なんか使いたくない。もう少し洗練された記法が使える言語が好ましい。


なければ作ればいいやと思い、丸一日かけてC++のparserをC#で書いた。

C++言語の字句解析と構文解析は出来るようになった。(C++ templateは除く。)


あとは、いかようにも文法を拡張できる。この言語を仮にいまYaneCと名付ける。YaneCはコンパイルするとC++コードを生成する。


それでいま悩んでいるのがループ記法だ。C#のようなforeachが欲しい。最初、次のように書けるようにした。

foreach var x in vect
	sum += x;

ふむ、C#っぽくていい感じだ。C/C++のdefineマクロでforeachみたいなのを作ると次のようなものになるが、この末尾の");"を私は生理的に受け付けない。

foreach(x,vect,{
	sum += x;
});

次に範囲型という型を作って".."で書けるようにした。

foreach i in 0..9
	sum += i;

まあ悪くはない。N回まわるならどう書くか?

foreach i in 1..N
	sum += i;

ループzero originではないのは嫌だ。0からだろう。

foreach i in 0..N-1
	sum += i;

なんだこの"-1"というダサさ。これだと普通のforのほうがよっぽどマシだ。そもそも範囲型でExpression(式)をとれるようにしてしまうと、次のように書いたとき、"1"のあとの"*"を掛け算だと認識して次の行もExpressionとして捉えてしまうんだよ。

foreach i in 0..N-1
	*sum += i;

Cのポインタ記法が憎い。YaneCではC/C++ポインタ記法を禁止してもいいが、過去ソースとの互換がとれないのも嫌だ。

これを回避するためには、foreachの行末の改行をセパレータとして認識するようにするか、次ように"{"〜"}"でくくる記法にするしかない。行末の改行をセパレータと認識するようにすると、フリーフォーマットではなくなってしまう。それは少し嫌だ。だとすると後者か?

foreach i in 0..N-1
{
	*sum += i;
}

一気にダサくなった。げんなりである。すでに普通のforが恋しい。普通のforなら(C#なら)code snippetが使える。苦労してC++ parserまで書いて、状況は悪化している。やるせない。N回まわるのは、Rubyっぽく次のように書けばどうか。

N.times 
	printf( "こんにちは" );

こう書くには、integer型に対するstatic methodを定義できれば良い。こんなことは1時間もあれば楽勝だ。さっそく私はinteger型のstatic methodを書けるように拡張した。でもよく考えたら、ループ変数が欲しいこともある。Rubyに倣って次のように書けるようにするか?

N.times 
{ |i|
	sum += i;
}

なんだこれ。一気にダサくなった。おいおい勘弁してくれ。私は'{'とか'}'が大嫌いなんだよ。ループ内の処理を1行書きたいだけなのに、'{'で'}'囲って行数がだらだらと伸びるとソースが見にくくてしょうがない。あと、N+M回まわりたいときはどう書くんだ?

(N+M).times
{ |i|
	sum += i;
}

ダサすぎる。普通のforのほうがマシである。ループ構造なのだから、先頭に「これはループですよ」とわかる識別子が無いと可読性が悪いだろ。

foreach i in 10
	sum += i;

まだこちらのほうがマシだ。しかし「in 10」って英語的にどうなのか。これ絶対おかしいだろ。あと、foreachの"each"とタイプするのも面倒だ。forだけにさせてくれ。

for i 10 times
	sum += i;

言わんとすることはわかるが、どう見ても片言英語だ。しかもこの文法構造だと次のように書いたとき、Xが型名なのか変数名なのかを確定させるのにtimesのところまで読みにいかないと確定しない。

for X N times
	sum += i;

違う。こんなことがしたいんじゃない。ちなみに、VC++ソース入力することが前提なら、次のようにforの直後に"("を持ってきて、普通のfor文に見せかけてインテリセンスを騙すテクニックが考えられる。

	for(i in e)
		sum += i;

この場合、"sum += i"の部分を入力しているときに正しくインテリセンスが機能する。ここを"foreach"にしてしまうとインテリセンスは騙せない。いや、ちょっと待てよ。ダミーのforeachをdefineで定義しておけばインテリセンスを騙せるなぁ。

// ダミーのforeach
#define foreach(X)

どうせインテリセンスを騙すつもりならYaneCの文法をC#と文法をコンパチにしてしまう方法も考えられる。そうすればReSharperのような強力な開発支援ツールが使える。


しかし、C#だとtypedefやCのdefineのようなマクロが使えなくて、私の開発しているプログラムではC#の文法に準拠させるにはかなり追加コストがかかるのだ。それはとりあえずは避けたい。


やはりVC++インテリセンスを騙せる範囲で拡張記法を考えるのがよろしい。PASCAL風に':'を導入してはどうか。

	for(i:10 times)
		sum += i;

これは読みやすいだろうか?またこの場合、多重ループは次のようになる。

	for(i:10times,j:5times,k:3..5)
		sum += i*j*k;

あとはC#のようにユーザー定義型に対してもfor(foreach)を定義できるようにしたい。しかしこのとき":"だとPASCALの型宣言のようでforeachっぽくない。

for(sq:uservar)
	target&=mask[sq];

やはり、次のように書かれているほうが私としては読みやすい。

for(sq in uservar)
	target&=mask[sq];

ここまで書いてきてから言うのも何だが、これなら、Cで普通にforeach_XXXをdefineして、次のように書けばいいんじゃないかと言う話になる。読みやすさで言えばさほど変わらない。末尾の"});"が気になるだけの話だ。

foreach_XXX(sq,uservar,{
	target&=mask[sq];
});

スタート地点に戻ってしまった。何をやっているのかさっぱりわからない。


そんなわけで、美しい回数ループ記法を求む!


・回数ループが美しく書ける

・回数ループループ変数がわかりやすく表記できる

・読みやすいforeach構文

・多重foreachが簡潔に書ける

インテリセンスを騙せる


以上の条件をある程度満たすものが望まれる。コメント欄でお願いします(´ω`)人

nn 2009/12/02 06:58 D言語と同じで良いような
foreach(x; vect) sum+=x;
foreach(i; 0..10) sum+=i;
foreach(sq; uservar) target&=mask[sq];
インテリセンス的な問題があるかも?

hogehoge 2009/12/02 07:03 Ruby っぽくいくなら 0...N で N を含まない範囲 [0,N) を作ればええんでないの

yaneuraoyaneurao 2009/12/02 07:07 > インテリセンス的な問題があるかも?

foreachのあと"("があり、")"まではインテリセンスは読み飛ばしますので、")"以降は正しくインテリセンスが機能します。

私は、N回まわるときに、0..N-1 の "-1"がすごく嫌なんです。

> Ruby っぽくいくなら 0...N で N を含まない範囲 [0,N) を作ればええんでないの

はい。[0,N) をうまく書く表記があれば良いのですが、"("と対応していない ")" がインテリセンスを阻害するので、これはまずいのです。

全角文字を投入して、"【0,N》"とかする方法も無くはないのですが、タイプしにくさから少し敬遠しています。

satoru_hsatoru_h 2009/12/02 07:27 N回まわるときは0..Nですよね?>D言語。

yaneuraoyaneurao 2009/12/02 07:44 ↑あっ、そうですね。D言語では、A..B だとB-A回ループを回るんですね。なんか紛らわしいですなぁ・・(´ω`)

でも慣れれば0..NでN回まわるのは結構いいような気はします。私はPASCALの部分範囲型とかすごく好きなので。

matarillomatarillo 2009/12/02 09:01 「rubyっぽくいくなら」と上の人が書いてるのは、rubyだとピリオド2つで閉区間、ピリオド3つで左閉右開区間だからです。つまり「ピリオド3つで、0...Nと書けるようにすればええんでないの」という意味でしょう。

yaneuraoyaneurao 2009/12/02 09:06 > rubyだとピリオド2つで閉区間、ピリオド3つで左閉右開区間だからです

うわ!知らなかった。そうなんですか。それはとても面白い記法ですね。目の悪い私は「..」の数を見誤りそうなので採用はしませんけど..。

kanokekanoke 2009/12/02 09:27 for(0<=i<N)
とか駄目ですかね?

yaneuraoyaneurao 2009/12/02 09:39 ↑ "i<N"の部分とかがbool型に見えてしまい、X<Nなら3回、さもなくば5回まわるループを書こうと思ったときに、for(0<=i<X<N?3:5) のようになって、これきちんとparseできるようにするのはちょっと大変でございます(´ω`)

Spring 88Spring 88 2009/12/02 09:44 >> いまさらC/C++なんか使いたくない。
おじさんほんき?
いまさらC/C++(の記法)なんか使いたくない。
の意味ですよね?

わたし、foreach なんかいらない。C/C++ のfor() で十分!
めんどうくさくfor()を書いているようでも、その間、気分が休まるので。

yaneuraoyaneurao 2009/12/02 09:56 > いまさらC/C++(の記法)なんか使いたくない。

いや、できればC/C++ともおさらばしたいんですけど、おさらば出来ないなら出来ないで、見えないところにC/C++が居てくれればそれでいいんですけど。

> その間、気分が休まるので。

私は開発は撮りためてあったテレビドラマを見ながらやってます。気分的にはずっと休まってます。

さすがにプログラムを30年以上やっていれば手を動かしているだけでぼけーっとしててもプログラムは完成します。どうせ手を動かしているだけでいいなら、動かす手の量は極力少ないほうが腱鞘炎にならなくていいよね、という。

ufcppufcpp 2009/12/02 11:35 集合論的にみると 10 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } なので、in 10 って表記は別におかしくないと思います。

yaneuraoyaneurao 2009/12/02 11:46 ↑あっ、そうですか..。それならfor(i in 10)でよければ表記的にはすっきりしますね。10回ループならfor(10)とか。間違えてwhile(10)と入力して永久ループになったりして(´ω`)

valpvalp 2009/12/02 11:54 VC++限定で良ければ、本文中のfor(sq in uservar)に近い構文、for each (type t in v)のようなfor each構文が使えます(vはvector<int>などbegin/endメンバ関数を持つオブジェクト)。
http://msdn.microsoft.com/en-us/library/ms177203.aspx
それはさておき、本文中の案では、ループ変数と回数の指定をすべて括弧の中に書くか、括弧が登場しないかのどちらかだけのようですが、for i in (0, N)またはfor 0, N (int i)のようにどちらかだけ括弧の中に入れるというのはどうでしょう?

yaneuraoyaneurao 2009/12/02 12:04 > 、for i in (0, N)またはfor 0, N (int i)のようにどちらかだけ括弧の中に入れるというのはどうでしょう?

それは、見た目、なかなか美しいですね。
左閉区間右開区間は意外とよいような気がしてきました。

p95095yyp95095yy 2009/12/02 12:09 トランスレーターは、コンパイルエラーが出たときに大変ではないですか?
あまりないかもしれませんが。

yaneuraoyaneurao 2009/12/02 12:29 単純なトランスレーターではなく意味論的なチェックも行なうので、まあたいていは変換時にエラーが出ます。

やむなく変換後にエラーが出た場合は、ソースファイル名とソース行を変換後の各行に埋め込んでおいて、Visual Studioのマクロか何かでそこから変換元ファイルの該当行にタグジャンプできるようにしようと思ってます。(まだそこまで出来てませんが)

p95095yyp95095yy 2009/12/02 13:14 トランスレーターがコンパイルエラー相当(トランスレートエラー?)をサポートするわけですね。YaneCはより賢いトランスレータというわけですね。

ss 2009/12/02 16:50 あまり知られていませんが、VisualStudio2005以降なら(マネージドでない普通の)C++でも普通にfor each使えますよ。MSが用意してるのじゃだめですか?

例:
vector<int> v;
v.push_back(1);
v.push_back(2);

for each(int item in v){
item++;
}

yaneuraoyaneurao 2009/12/03 06:37 ↑*1は、↑*6で既出でございます。

それはそうと、foreachしたいのはclass / structとは限らず、普通にtypedefした型や、enumなんかに対してもforeachが使いたいというのと、定数回のループをもっと簡潔に書けないかなというのがあるので"for each"では少し物足りないのです。

hogehoge 2009/12/03 19:15 色々心配してる人はまあ #line とか知らないんでしょうな。確かにマイナーだけども。

aoisomeaoisome 2009/12/03 19:17 C++のparserを一日で書いてしまうなんて凄すぎます!
手書きのparserなんですか?アルゴリズムは何を使ってるんですか?
何かのライブラリやツールを使っているのでしょうか?
どうしたらそんなに速く組めるのか教えてください。

yaneuraoyaneurao 2009/12/03 21:38 > 手書きのparserなんですか?アルゴリズムは何を使ってるんですか?

完全に手書きの普通の再帰下降型parserでございます。C#で書くと開発効率が良くて、まあ、tokenを切り出す部分と、構文解析する部分ぐらいまでは1日あれば十分書けます。そこまででソース行数は1500行程度です。

慣れていれば、4時間ぐらいで書けるのでしょうけど、私はそこまで慣れてないです(´ω`)

aoisomeaoisome 2009/12/03 23:05 それぐらいコーディングが速いから「無ければ作ればいいや」っていう発想になるんでしょうねー
私の速度ではそんな発想にならないです。

MMXMMX 2009/12/05 11:30 Google の開発中の言語 Go では slice や range のほうに 振り分けて for 構文は Cのまま、言語の設計はバランスらしい。
Matz日記
http://www.rubyist.net/~matz/20091113.html

variantvariant 2009/12/10 20:49 parser無駄になるけど、
BOOST_FOREACHマクロ使って、回数ループは"Range"のコンセプトを満たして、1〜Nを返すような関数オブジェクトを使うとかはダメ?

yaneuraoyaneurao 2009/12/10 21:34 ↑まあ、普通(?)、boost使いならそうしますよね。

私、boostのRangeの細かいところまで見ていないのですが、Rangeはbeginとかendとか定義できないといけないような?

例えば、いま私がやりたいのは、uint型の整数のbitが1のところだけforeachで回したいのですが、これを事前にend()を求めるためには、1であるbitを数える(ex.Popucount)必要があって、これを事前にやりたくないのです。

1であるbitは、下位からなら、x &= x -1 みたいな形で自然な形で除去できるのでループ終了判定はなりゆきまかせにしたいのです。

↑boostのRangeの実装よく見てないので見当違いならすみません。

ただまあ、こういうところに頭を悩ませるぐらいなら、最初から単にdefineでforeach_XXXを定義してしまって、それを使うだとか、自分で簡単なコンパイラを書いたほうがまだすっきりするなぁというのが私の考えです。

名無し名無し 2009/12/23 06:27 http://image.blog.livedoor.jp/zonkichi180/imgs/9/a/9a56706c.jpg
これ思い出した

yaneuraoyaneurao 2009/12/23 06:41 ↑あ、まさにそんな感じだ!!

通りすがり通りすがり 2010/02/05 06:35 foreach_XXX(sq,uservar){ target&=mask[sq]; }
なら define できそうだけど、どうかな。

#define foreach(T, i, d) for(T i=(d).begin();i!=(d).end();++i)
みたいな。多重もできそう。

takano32takano32 2010/03/07 08:45 0..^n

トラックバック - http://d.hatena.ne.jp/yaneurao/20091202
 | 

1900 | 01 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 06 | 08 | 10 | 11 | 12 |
2015 | 01 | 02 |


Microsoft MVP
Microsoft MVP Visual C# 2006.07-2011.06