Linqでコマンドラインオプション解析

最近、Linqでの副作用について考えこむことが多くなりました。きっかけはSelectメソッド内で、外部のListに対してAddしたあげくreturn null -> ToArrayとかいうForEach代わりに使うかのような超勘違いしたコードを見せられたことなのですが(自信満々にどうだ!って感じで出されたのでモニョるしかなかったという苦い記憶ががが) と、そんな私の愚痴はどうでもよくて、副作用。基本的には邪悪ですよね。個人的に嫌なのは、せっかくスコープが狭く、ラムダ式だけを見つめれば良い状態になっているのに、副作用が入ると広い範囲を意識しなければならないこと。この変数名はどこからきたの? インスタンスの状態はどうなるの? 考えごとが増えるのは嫌なものです。ミスも増えるでしょう。エラーの温床となってしまいます。

とはいえ、使いどころによっては強力な効果を発揮するのも事実。例えば、以前書いたIEnumerableの文字列連結なんて、カウント用変数を一つ用意するだけで、Aggregateでサクッと書けてしまいます。というわけで、無駄な多用は厳禁だけど、使いどころをちゃんとおさえて書きましょう、というイイコな結論を出しつつ本題というか例題。

コマンドラインオプション解析。シーケンスを前方から解析して、次のキーが現れるまでは以前のキーで分類する。という分かるような分からないようなお話です。コマンドラインオプションだけでなく、たとえば決まった形式のテキストファイルを解析するとかでよくありそうなパターンだと思います。これがXMLなら簡単に解析出来るのに、クッ…… みたいな。

さて、グループ分けとなると、じゃあLinqで出来るよね? GroupByかなんかを使えばいいっしょー。と思い浮かぶわけなのですが、素の状態だと上手くいきません。GroupByを使うためのキーを列挙内部だけで保持することは出来ないからです。じゃあどうするか、というと、そうそう、副作用です副作用。はいはいクロージャクロージャ。というわけで列挙中にサクサクッとキーを書き換えてしまいましょう。

// こんな風に来るコマンドラインオプションを解析しよう
var args = new[] { "-i", "input.txt", "-hoge", "-huga", "-o", "output.txt" };
// グループ分けといったらLinqだよね?
// ディクショナリに分解したい、dict["-i"]で"input.txt"が取れる、というように

// コマンドラインオプションをHashSetに格納する
var options = new HashSet<string> { "-i", "-hoge", "-huga", "-o" };

string key = null;
var result = args
    .GroupBy(s => options.Contains(s) ? key = s : key) // 副作用!
    .ToDictionary(g => g.Key, g => g.Skip(1).FirstOrDefault()); // 1番目はキーなのでSkip

とまあ、こうなります。副作用便利!

そういえばコマンドラインオプションの解析は.NET Framework標準では用意されていないのですよねえ。外部ライブラリのものは、当然なのですがあらゆるものに対処するため、どれもこれもヘヴィーすぎです。別にそんな複雑なのいらないよー、-oを解析出来ればそれだけでいいんだよー、的な小さいシチュエーションなら、この程度でも問題ない、はずです、きっと。

例としてコマンドラインオプションの解析を出したのは、id:coma2nさんのNDesk.Options(Mono) - コマンドラインパーサー - Programmable Lifeという記事を見てのことです。このNDesk.Optionsは凄いですね! まだ触ってないので実際の使いかっては分かりませんが、ラムダ式の使い方に驚きました。非常に上手いやり方だと思います。シンプルで。明快で。覚えやすく書きやすく。私もこういう発想が出来るようになりたいなあ。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive