日常雑話
- 2010-01-24
扁桃炎で水曜日にダウンしてから(扁桃炎って40度も熱出るんだねー、インフルエンザかと思っちゃったよ)本当にダウンしっぱなしで、ここ数日は寝て薬飲んで寝るだけの最低最悪でした。今も全然治ってないので最悪です。そんな時でも人は働かなきゃ行けないんですね、社畜! NEETになりたいよう。さて、そんなわけなのですが、そろそろやることがいっぱいつまってきてしまっているので整理します。だらだら生きてるとだらだらネット見てTwitterやって過ごすだけになってしまうのです。それは私です。
- XboxInfoTwitでJewel Questをプレイ中の人がいると「未知のエラー」で死ぬ件を修正する
これは原因まで掴めているし、そもそもこの件については、わざわざ教えてもらったというのに半月スルーしていたという最低な有様なのでとっとと直しなさい、という話なので直します。体調が悪い時にやるとポカミスやりそうなので、体調治ったら真っ先にアップデートかけますので。
- OS再インストールする
物凄くグチャグチャなので、ずーっと再インストールしようと思っていたのですが延ばし延ばしにしすぎました。どうでもいい話なのですけど、とっととする、とここに書けばいい加減にやる気を出す気がしてきました。気のせいです。
- Rxの記事書く
ネタは結構いっぱいあるのですが、Forumが活況すぎてそっち追うのにいっぱいいっぱい。あとRx自体が実に難しくて、私の能力的に結構いっぱいいっぱいです。あと英語のせいで脳みそが知恵熱でオーバーヒートしてます。私のTOEICのスコア舐めるなよ(受けた事ないので知らないけど、多分想像を絶するほど低い)。扁桃炎になったのもそれのせい(違)。とりあえず、近いうちにIObservableの連鎖についてきっちり書きたいと思ってます。
- Ajax MinifierにGUIつけたの作る
Ajax Minifierとかみんな憶えてないでしょ。便利なMS製のJavaScript圧縮/解析ソフトです。CUIのみなのでGUIを提供したいと思ったのです。半分作ってあって、一応動くのはあります。ただ全然GUIになってません。Ajax Minifierが告知されてすぐに作り出して、そしてすぐに放置したというダメ人間。完成させたいです。GoogleがClosure Toolsとか出しちゃったしもういっかー、とか思ってません。Microsoft大好きっ子なのでAjax Minifier使うぜ。ていうかネーミングが悪いよね。Ajaxあんま関係ないし。
- linq.js WSH拡張作る
作る。作る。本気本気。もう1月も終わりそうなのに何も手をつけてないけど本気。
- プログラミングHaskell読む
F#の本がamazonから届いてしまう前に読む(笑) ちなみに積み本はいっぱいあって、どうしたものかなー、って感じですねえ。レガシーコード改善ガイドとリファクタリングとEffective C#と初めてのRubyと、まあ、そんなのを積んでます。そんな量でもないか。まあとにかく、積み本は良くないのでちゃんと読めって話ですな。
- シュタインズゲートをプレイする
- アサシンクリード2クリアする
シュタゲ未プレイなのが許されるのは(ry それにしても最近積みゲー多すぎですね。ゲームへの欲望ってのは本当に確実になくなってますねえ。そんなにC#楽しいか!って話なのですが。C#楽しいよLinq可愛いよハァハァ。それがいいのか悪いのかはなんとも言えないですけど、ただでさえ視野狭窄な私なので、ゲームからインスピレーションを受けないと感性が完全に死ぬので切らしたくはないですねぇ。今後はバイオショック2とガクブル島(OBLIVION)とスプセルコンヴィクションかな、手を出すのは。XBLAのDarwinia+は出たら買うと思う。まさかNAIJはないでしょう……。オフィシャルで動画見れます→Darwinia+ Promotional Trailer Streaming。良い感じ。
と
いうわけでどうでもいいTODO?の列挙でした。もう少し先の方を見ると、一応コレ作りたいとかアレ作りたいとかいう計画もあるんですが、少しも手をつけてないうちは妄想でしかないので、現実的な目の前の課題をちゃんと片付けてから夢見なさい、ですね。薬飲んでも続く微熱と下痢が治ってくれさえすれば――。TODOを消化出来る、なんてことはないんですが、ここ数日の人生死んだ感がヤヴァかったので、ちょっとは心入れ替えてやるんじゃないかと思われます。
人生死んだ感が発生すると、何か美味しいものが食べたくなって、食べログで近場の店を漁るなどしてしまうのだけど、一人じゃ入れないな、しょぼーん。となるなど。そんな時は「孤独のグルメ」を胸に……。いや、ランチと夜は違うじゃないですか。考えてみると孤独のグルメはお昼が多かったような。とりあえず今は美味しいビーフシチューが食べたい気分なので夜に一人で言っても平気な感じにコースじゃなくてビーフシチューだけ食べて帰れるような店(そんな都合のいい店などない)を探すという無駄に時間費やしてます。寝ろ。
デスクトップシアター for Windows 7杯
- 2010-01-15
Windows 7杯 自作PCの祭典 2009応募の記事です。
応募対象は「勝手にオレが1番! 部門」で、コンセプトは「デスクトップシアター」。机周りと一台のPCに全ての機能を集約させることで、椅子から一歩も動くことなくゲーム、映画、インターネット、プログラミングなどあらゆることを快適に行うという引きこもり推奨システムです。実際、私は寝るとき以外は常に椅子の上にいます。
個室やワンルームの狭い空間を有効活用する、という点でもPCに全てを集約させるというのは合理的判断だと思います。その狭い部屋にテレビを入れる必要はあるのか?ディスプレイで全部まかなえばいいじゃない、ノートPCなんて捨てろ! 自作PCを組め!
最近では1920x1080のマルチメディア用途を狙った(ただのコスト削減流用という話でもある)16:9 HD液晶も数多いので、別に珍しいスタイルではないのですが、デスクトップシアターはただたんに表示させるだけ、ただたんに集約させただけではありません。スペースがないから妥協して集約させたのではなく、集約させたが故のメリットをハードウェア・ソフトウェア両面から徹底的に追求しました。
なお、画像はクリックすると原寸写真に遷移します。
構成パーツ
CPU : Intel Core i7 920
マザーボード : Asus P6T
メモリ : Corsair TR3X6G1600C8 2GB x 6
ビデオカード(メイン) : ELSA GLADIAC GTX 260 896MB(NVIDIA GeForce GTX 260)
ビデオカード(サブ) : LEADTEC WinFast PX8400 GS TDH Silent(NVIDIA GeForce 8400 GS)
SSD(メイン) : Intel SSDSA2MH08 X25-M 80GB x2 (RAID 0)
HDD(サブ) : SEAGATE ST31500341AS (1.5TB SATA300 7200) x2 (RAID 0)
光学ドライブ : Pioneer BD-ROM BDC-202
CPUクーラー : サイズ MUGEN∞2 無限2 SCMG-2100
ケース : CoolerMaster Sileo 500
電源 : CoolerMaster Silent Pro M600
センターモニタ : NEC LCD3090WQXi(BK)
サイドモニタ : SAMSUNG SyncMaster 2343BW x2
トップモニタ : DELL 2405FPW
HDMIキャプチャボード : Blackmagic Design: Intensity
AVアンプ : ONKYO TX-NA1007(B)
スピーカー : ECLIPSE TD307II x9
サブウーファー : ECLIPSE 316SW
Game Console:Xbox360 Elite
使用OS : Windows 7 Ultimate(64bit版)
PCケース内部
モノがある関係上、こんな角度からしか撮れなくてすみません。パーツよくわかりませんよね、これじゃあ。PC本体はあまり特筆すべき組み方でもなく、いたって普通です。メインドライブのSSDx2のRAID 0は体感ですら明らかに高速で非常に快適。プチフリ?そんなのありませんよ。みんなSSDにすればいいのさ。容量不足(160GB)は基本的にはそこまで深刻でもないのですが、HD動画キャプチャを行うので(主にXbox360の、1時間で数百GB行く)、大容量かつ高速なドライブが必要(未圧縮でキャプチャするため速度がないとコマ落ちします)。そのため、サブとして1.5TBのHDDをx2 RAID 0で用意。静音PCを狙ってHDDはスマートドライブに格納、また密閉度の高いケースを利用しました。CPU, Caseファンも平常時は低速回転させています。
かなり窒息度が高く、熱源も少なくないため温度モニタリングの状態は常に怪しげで不安度高し(平常時CPU温度55度前後)。そして絶望的な配線センス。次にPCを作るときは配線を魅せるようなのが作りたいかも。
PCケース外観
見た目はヘンテツもないわけで、実際のところ部屋の光の当たらない隅に置いてあるので、黒ければそれで良かったりします。なので、外観デザインは目立たないこと、ただそれだけを求めました。ついでに天板のサイズがぴったりだったので、ヘッドフォンアンプのSRM-600limitedを載せています。更にその上にオーディオインターフェイスとしてRME Fireface UCを設置。これは、明らかにオーバースペックでした。色々と反省。買う前は使いこなす構想があったのですが、今じゃあSTAXとAVアンプに音を送るだけの「聴き専」野郎ですよ! 許すまじ。時間に余裕ができたら、追々弄っていきたいです。
解説文
正面画像は一番上で使ったので、周囲を写して。4画面で9.1chサラウンドでXbox360しながらも同時に攻略サイトを見ながらTwitterに投稿しつつ動画キャプチャのモニタリングもしながらついでにUStreamで動画配信(以前にUstreamでFlash Media Encoderを使って高画質配信するためのまとめという記事を書いています)も出きれぅ(動画配信の状況は冒頭写真がそれです)。
やりすぎ。しかし圧倒的に便利。これこそ、まさに集約させたが故のメリットです。どれだけ過剰なやりすぎ要求にも答えてくれる、それが自作PC。
マルチメディア再生も当然、全てPCで行います。PCで動画を見ると解像度の差がTVで見るよりも、モロに出ますからね、持っててよかったBlu-rayドライブ。Xbox360好きーな私ですが、別にBlu-rayは否定しませんよ?(別にPS3も否定しませんが……買わないというだけで)。
DVDはTotal Media TheatreのSlimHDで再生しています。NVIDIA CUDAを利用した超解像による拡大処理で、Blu-rayに迫……りは全然しないのですが、まあ、何もしないスケーリング処理よりかは遥かにマシです。
音声はBD, DVD共に、PCではデコード処理は行わず生データのまま光出力でAVアンプに回してのサラウンド再生。ちなみに普通の2ch音楽の再生も、AVアンプでサラウンドエフェクトかけて聴いてたりします。音場がグッと広がって良いものです。普段はサラウンドで、しっかり聴きたいときはヘッドフォンで。そういう切り分けをすると、音楽がより楽しめます。
ベンチマーク
以上のようにコンテストの趣旨を履き違えたかのごとく、自作PC本体よりも周辺環境の拡充に力を注いでいるため、ベンチマーク結果はどうでもいいと思ってたりします。ただの飾りです。偉い人にはそれがわからんのです。
Windows 7を使って良かったと感じた点
いやー、いいOSですよ。画面綺麗だしメモリ大量に積めるし。メモリは7ではなく64bit OSの利点なわけですが。Win7は64bitのみの提供でも良かったんじゃないかしらん。ショートカットキーの充実(Winキー+矢印によるウィンドウサイズ変更とか)は最高に便利ですね!と、褒めたいのですが、この辺はAutoHotKeyによる自作スクリプトで解決していて使っていなかったり……。まあ、わざわざAutoHotKeyを導入しなくても使えるというのは良いことです。Windows標準搭載により、こういうことが出来ることの便利さが周知されるというのも素敵。この、偏狭のブログで幾ら布教させようとしても閑古鳥が泣くだけで虚しさがつどりますが、Microsoftが布教してくれたなら、それで満足です。さすがMicrosoft、(以下略)にしびれるあこがれるぅ。
あ、モニタの多さによるウィンドウ移動の大変さはAutoHotKeyの自作スクリプトで解決させました。 -> AutoHotKeyによるマウスカスタマイズとマルチディスプレイのためのスクリプト
デスクトップシアターはWindows OSとハードウェア(自作PC)とソフトウェアが全部噛みあってこそ成り立つもの。私はOSにもハードウェアにも貢献出来ませんが、一個人として、ソフトウェア側からの拡充に努めたいと思っています。AutoHotKeyスクリプトもそうだし、Xbox360の隣で常にPCが起動しているならPC側で、ネット経由でXbox360の状況をモニタリングすればいいぢゃない -> XboxInfoTwit とかもそう。
何にせよ、アホみたいに大量にソフトを起動してもメモリ余裕、OS大安定、動作軽快なわけでして、Windows 7の凄さ、良さというのを実感するところです。そういえば、Windows7といったらマルチタッチ対応も挙げられます。デスクトップシアターに有効活用できないかなあ、どうやったら上手く組み込めるかなあ、というのを考えています。まだまだ拡張の構想はあるので、来年は更にパワーアップしたもので応募してみたいですね!
更新履歴
2010/01/22 CPU-Zによるベンチマークの画像を張り忘れていました(画像自体のアップロードは行っていたのですがHTMLに張り忘れていた)。申し訳ありませんでした。
C#(.NET Framework)の文字列連結について
- 2010-01-07
一般に文字列を+=で連結するのは遅いと言われています。事実そのとおりで、多量の連結を+=で行うと死ぬほど時間がかかります。例えば、以下のようなコードで示されることが多いでしょう。
// ベンチマーク用関数(10万回実行)
Func<Action, TimeSpan> bench = action =>
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
{
action();
}
return sw.Elapsed;
};
// StringBuilderの計測(最後のToStringを入れてませんが、あまり変わらないのでスルー)
var sb = new StringBuilder();
var sbTime = bench(() => sb.Append("hoge"));
// stringの+=での計測
var s = "";
var stringTime = bench(() => s += "hoge");
Console.WriteLine(sbTime); // 0.004sec
Console.WriteLine(stringTime); // 14.97sec
0.004secと15secでは話になりません(正確には、StringBuilderでは最後に文字列に変換するToStringを入れるべきですが、それでも1secは超えなくて差は歴然なので省略します)。ならば、文字列を連結する場合は、どのような時でもパフォーマンスのためにStringBuilderを使うべきでしょうか? 答えは違います。
// ILではひとつにまとまる
// IL_0001: ldstr "abcde"
var s = "a" + "b" + "c" + "d" + "e";
定数の連結はコンパイル時にひとまとめにされるので、StringBuilderを使うのは愚かな選択となります。この辺はILDASMで見ればわかるし、Reflectorでもひとまとめになって展開されているのが確認できます。では定数ではなく動的に値を返すものは?
static string Get()
{
return DateTime.Now.ToString();
}
static void Main(string[] args)
{
// IL_0031: call string [mscorlib]System.String::Concat(string[])
var s = Get() + Get() + Get() + Get() + Get();
}
ILを見ると、s += Get(); s += Get(); みたいな展開のされかたにはならず、String.Concat(string[])が呼ばれることになります。よって、速度を心配してStringBuilderを使う必要は全くありません。測定してみましょう。(ちなみにs+=Get()だとString.Concat(string,string)が大量に呼ばれることになるのが遅い理由)
// benchとGetは上で使ったのと同じものを流用
var sbTime = bench(() =>
{
var sb = new StringBuilder();
sb.Append(Get())
.Append(Get())
.Append(Get())
.Append(Get())
.Append(Get());
var s = sb.ToString();
});
var stringTime = bench(() =>
{
var s = Get() + Get() + Get() + Get() + Get();
});
Console.WriteLine(sbTime); // 0.65sec
Console.WriteLine(stringTime); // 0.64sec
速度はほとんど変わりません。StringBuilderとString.Concatでは処理の中身は結構違いますが、速度変わらないのならどっちでもいいよね。なら、記述しやすいほうを選ぶのが良いでしょう。妄信的にパフォーマンスのためにStringBuilder!とか思っている人は、少し考え直してみてください。そんなの当たり前だろ常識的に考えて、と思っていた時期が私にもありました……。世の中は存外StringBuilder神話に溢れているかもですよ? いやほんと。あとC#なら逐語的リテラル文字列もお忘れなく。
Haskell用IDE 「Leksah」の紹介と導入方法
- 2010-01-04
本格的にプログラミングを学び始めたのがC# with Visual Studioな私としては、充実した、とまではいかなくてもそれなりに動くIDEがないとシンドい。新しい言語を学ぶときは、まずIDE探しから始めるのだよ、はっはっは。と、全く自慢にならないゆとりっぷりを晒してしまうわけですが、事実辛いものは辛い。そしてHaskell。日本語による書籍も4冊出ていて、学習しやすくなったものの実行環境導入の敷居の高さは変わらず。GHCi(インタプリタ)でコマンド打ち込みながらやれって? いやいや、ムリムリ。
初心者にこそ強力なIDEが必要なのだよー、入力補完や背後でのコンパイルによるエラー報告、色分けにオートインデント、デバッガ。これらが素早いトライアンドエラーを可能にし、学習速度を高める。まずはメモ帳で十分、なんていうのは誤り。学習するなら最初からIDE。ということはneue cc - 最もタメになる「初心者用言語」はVisualStudio(言語?)が、それをHaskellにも持ってこようとしています。大体がしてEmacsってIDEっしょ、もはや。さて、しかしWindowsでEmacsってちょっと……。
Leksah
そこで、Leksahの登場です。HaskellによるHaskellのための開発環境。Leksah(逆から読むと……)はHaskell自身で書かれたHaskell用IDEで、WindowsでもMac OSでも動作します。バージョンは0.6と、まだまだ不安定気味なところも見え隠れしますが(不意に落ちても泣かない)十二分に使えます。インストール・設定も簡単なので、非常にお薦め。Windows用のIDEだと、他にEclipseプラグインのEclipseFPやVisual Studio 2005拡張のVisual Haskellがありますが、試したところどちらもイマイチでした。今のところLeksahしか選択択はないように思います。
Haskell Platform
IDEを入れる前にコンパイラを入れましょう。ということはGHCですね?と思ってしまいますがちょっと違います。Haskell Platformからセットアッププログラムをダウンロードしましょう。オールインワンで全てやってくれます。インストールが終了したら、Leksahのインストールと実行。初回実行時には何やらディレクトリ位置を指定してください的なダイアログが立ち上がりますが、それは無視しても構わなかった、はず、です。
Hello, Worldまで
IDEのお約束として、最初の設定は少し面倒くさいです。が、それさえ乗り越えれば簡単生活が待っているので、ちゃちゃっと設定を済ませましょう。まずメニューからPackage->New Packageを選択して新しいPackageの作成。これはVisualStudioで言うところのソリューションですかね。するとPackageのコンフィグ画面が立ち上がっているので、まずはPackage IdentifierのNameとVersionを適当に記載します。
次にDependenciesを選択して、Selectからbaseを選び、Addボタンを押す。これはVisualStudioで言うところの参照設定です。System.dllを読み込むように、baseを読み込むよう指定したわけです。
次にExecutablesを選択して、Executable NameとFile with main functionにMain.hsと記述してAddボタン。これはVisualStudioで言うところのスタートアッププロジェクトですね。今はまだMain.hsはないので、後で作ります。
最後に1 Buildでファイルを置く予定のディレクトリを指定したら設定は完了。Saveボタンを押してからClose。
次に右ウィンドウModulesタブを開き、ラジオボタンLocalを選択して右クリックからAdd Module。入力欄にMainと入力すればMain.hsが作成されます。コメントが色々書かれたものが読み込まれているので、とりあえず全部削除。一行目にmodule Main where。あとは好きなように書いて、最後にmain = do以下に実行文を書けば出来上がり。
module Main where -- 名前空間みたいなもの
double x = x * 2 -- とりあえず関数など作ってみる
-- mainは必須。ようはstatic void Mainですな
main = do
print "Hello World"
print $ double 100 -- $でカッコを省く print(double 100)と同じ
Ctrl+Bでビルド。Ctrl+Alt+Rで実行結果が見れます。 あとは、好きなように書き換えて実行、実行、実行。インタプリタで頑張るよりも学習効率良いですよ、きっと。なお、デバッグはデバッグのアイコンを押してデバッグモードに入って、Show Debuggerでデバッガウィンドウを出して、あとは適当に弄る(よくわかってない)。
Leksahの特徴
インパクトがあるのが、エディタ上で文字が記号に置換されること。上の画像は一切手を加えていないエディタのスクリーンショットなのですよ。非常に異国情緒に溢れていて、いいですね。λがλですよ。ホットコーナーの舞台裏でのプログラミングHaskellのレビューでも記号について触れられていますが、本の通りの綺麗な記号でディスプレイに表示し、編集出来ます。
ただし、使用するフォントが制限されます。プログラミング用フォントでは、私はConsolasがお気に入りなのですが、Consolasでは一部の記号が化けてしまうため、今はDejaVu Sans Monoを使っています。ただ、これだと今度は日本語が化けてしまったり。
変換される記号はインストールディレクトリの\data\Default.candyで確認出来ます。スペースも補完入力されるのが気にくわないぜ、と思ったら書き換えてやりましょう。また、Default以外にも、.candyファイルを作成してエディタのコンフィグで読み込むcandyファイルを指定すれば、好きな文字を好きなルールで変換可能です。ショートカットキーも同様にHoge.keymapを作成してエディタで直に編集して、コンフィグで指定します。ちなみに私はRunがCtrl+Alt+Rなのは指が厳しいので、とりあえずF5にしておきました。
なお、もし通常表記したい場合は、Config->To Candyを選択することで簡単にオンオフできます。
何か書くと常にバックグラウンドでコンパイラが動いて、エラーを表示してくれます。静的言語の強み!勿論、ダブルクリックで該当行にジャンプできます。
入力補完も効きます。また、ModulesのPackageを見れば、標準ライブラリ一覧と、その型を見ることが出来ます。素早く関数を知る・試すことができるのは学習速度に影響しますからね。非常に便利。これでDescriptionも表示されれば完璧なのですが、今は型のみ。
本
という感じに、プログラミングHaskellをぽてぽてと読んでいます。Haskell自体が刺激的な言語ということもあって、非常に楽しい。良い本です。章末問題の解答は公式サイトでpdfが配られています。そして、最近ではReactive Extensionsですっかりお馴染みに動画を見ることが多いErik Meijerによる各章解説動画もありますね。おお、なんという致せり尽くせり。素晴らしい。
AssemblyInfoの取得
- 2009-12-31
public sealed class AssemblyInfo
{
public string FileName { get; private set; }
public string Version { get; private set; }
public string FileVersion { get; private set; }
public string Title { get; private set; }
public string Description { get; private set; }
public string Configuration { get; private set; }
public string Company { get; private set; }
public string Product { get; private set; }
public string Copyright { get; private set; }
public string Trademark { get; private set; }
public string Culture { get; private set; }
public AssemblyInfo(Assembly assembly)
{
FileName = assembly.GetName().Name;
Version = assembly.GetName().Version.ToString();
FileVersion = GetAttributeName<AssemblyFileVersionAttribute>(assembly, a => a.Version);
Title = GetAttributeName<AssemblyTitleAttribute>(assembly, a => a.Title);
Description = GetAttributeName<AssemblyDescriptionAttribute>(assembly, a => a.Description);
Configuration = GetAttributeName<AssemblyConfigurationAttribute>(assembly, a => a.Configuration);
Company = GetAttributeName<AssemblyCompanyAttribute>(assembly, a => a.Company);
Product = GetAttributeName<AssemblyProductAttribute>(assembly, a => a.Product);
Copyright = GetAttributeName<AssemblyCopyrightAttribute>(assembly, a => a.Copyright);
Trademark = GetAttributeName<AssemblyTrademarkAttribute>(assembly, a => a.Trademark);
Culture = GetAttributeName<AssemblyCultureAttribute>(assembly, a => a.Culture);
}
private string GetAttributeName<T>(Assembly assembly, Func<T, string> selector) where T : Attribute
{
var attr = assembly.GetCustomAttributes(typeof(T), true).Cast<T>().FirstOrDefault();
return (attr == null) ? "" : selector(attr);
}
}
// 利用例
class Program
{
static void Main(string[] args)
{
var assembly = Assembly.GetEntryAssembly();
var info = new AssemblyInfo(assembly); // こんな感じに。
Console.WriteLine(info.Title);
}
}
通常AssemblyInfo.csに記載する、タイトルとか説明とかバージョンの取得って、用いたいシーンも少なくないわりに存外面倒くさい。そんなわけで補助クラスを作ってみました。コンストラクタにAssemblyを投げ込むと、文字列にして返してくれます。
これと同じことをやるコードを一年ぐらい前に書いたのですが、今見たらどうしょうもなく酷かった……。(あまりにも酷いので見せられません!)。なので、今基準で書き直してみました。いかに型を書かないで済ませるか、いかに行数を少なく見た目をすっきりさせられるか。Func<T, string>という発想が一年前は出来なかったんだなあ。CastやFirstOrDefaultも知らなかったやも。
そんなわけで今年もありがとうございました。無事、閉鎖せずに一年を乗り越えられました。ただ、ある意味閉鎖してますけれどね、ゲサイト的な意味では。今年を振り返るとプログラミング、プログラミング、プログラミングでした。一年前とは見比べるまでもなく成長出来たと思います。ブログにコードを晒すこと、小さくてもいいのでソフトウェアを作って公開すること、というのが確実に貢献してくれました。よく言われる、コード晒せば他の人の添削が期待できるよ!ってのはそこまで期待できないと思うのですが(勿論、ありがたい指摘も幾つかありました、感謝です)、それよりも他の人が見る、という意識をもってコードに取り組むのが効いた気がします。カッコつけて書こうと、何度も練り直すのが結果的には良かったかな、と。
もう少し振り返れば、なんといってもC#、というかLinq。Linqの魅力に取りつかれて、そのままフルスロットルで加速した一年でした。C#や.NET Frameworkに詳しいか? と言われるとまだまだモニョるのですが、Linq to Objectsなら詳しい、と言えるだけの自信はつきました。これは、linq.jsとしてJavaScriptに移植したのが大きいです。動作が完全に一致するよう何度もチェックしたり、Monoのソース読んだり、リフレクタでSystem.Core.dll読んだりしたので、内部をきちんとイメージ出来るようになったので。
来年は、んー、とりあえずlinq.jsのWSH拡張の早期リリースを目指したいです。linq.jsは、個人的には非常に便利だと思っているのですが、ウェブ用のライブラリとしてアピールするのはどう考えても「無理」。パフォーマンス無視で、リスト処理がこんなに簡単に書けるんです、どうでしょう?ってんじゃあ請求力もないって話です。そもそもウェブ用JavaScriptで多用するDOM操作関連は未実装部分(linq.js Xml Extensions)多いし。とはいえ、せっかく作ったわけなので、真面目に布教させたいと思っています。今のところJavaScriptライブラリの隙間、WindowsScriptHost用やテキストエディタのマクロ用としての応用例を探っている、というか実装中。特に、WSH用に使うと物凄く便利なことが分かったので、とっとと実装を終えて、使ってみて欲しいところです。
実装中というか、途中で放置しちゃってるのがアレですが。ちょろっと記事書いたのが8月。今まで、それから全く進んでおりません。XboxInfoTwitのリリースに追われたり、Rxで遊んだりで放置ルートに入ってしまったのですねえ。やる気はめっちゃあるので、来年はまず一番に、linq.jsのWSH拡張のリリースを目指します。本気で本当に。
プログラミング以外だと、今年で一番影響が大きかったのがTwitterかなあ→neuecc on Twitter。今までチャットやネトゲなど、コミュニケーション系のウェブサービスを全く受けつけなかったコミュ不全の私が、唯一利用出来たサービスだという。一人で書き飛ばしていればよくて、無理に繋がらなくていいのが非常に楽。……。まあ、私はもう少し@飛ばしてもいいと思います。むしろ飛ばせ。これも来年の目標、ですかね。
C#のWebRequestとWebClientでCookie認証をする方法(と、mixiボイスへの投稿)
- 2009-12-17
WebからHTMLをダウンロードするにはWebClientが便利です。が、そのまんまだとCookie認証で躓きます。せっかく便利にダウンロード出来るのに、認証を超えられないんじゃ意味が無いよ!というわけかで幾つかのやり方を紹介したいと思います。海外だと沢山情報が出回っているのですが、日本だとWebClientはクッキーがとれないが検索上位に出てくるので、WebClientの利用を諦めて面倒くさいWebRequestを使う羽目になっている人が多いんじゃないかしらん。WebRequestなら@ITの記事、@IT:.NET TIPS クッキーを使ってWebページを取得するには?が引っかかりますからね。
とりあえず、@ITのmixiへの認証を例題に、まずはWebRequestでのやり方を見てみます。
// WebRequestによるCookie認証
// POSTしてCookieContainerに書き込む
var data = Encoding.ASCII.GetBytes(string.Join("&",
new[] { "next_url=/home.pl", "email=めるあど", "password=ぱすわど" }));
var cookieContainer = new CookieContainer();
var req = (HttpWebRequest)WebRequest.Create("https://mixi.jp/login.pl");
req.CookieContainer = cookieContainer;
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = data.Length;
using (var stream = req.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var res = req.GetResponse(); // ここでCookieContainerに書き込まれる
// 以下、そのCookieを使えばアクセスし放題
var reqLog = (HttpWebRequest)WebRequest.Create("https://mixi.jp/show_log.pl");
reqLog.CookieContainer = cookieContainer; // CookieContainerセット
var resLog = reqLog.GetResponse();
using (var stream = resLog.GetResponseStream())
using (var sr = new StreamReader(stream, Encoding.GetEncoding("euc-jp")))
{
Console.WriteLine(sr.ReadToEnd()); // アクセスできてるのを確認
}
CookieContainerを設定すれば、Cookieのサーバーからの取得も送信も全部自動でやってくれる、というのがポイント。そこは楽です。楽なのですが、WebRequest自体が使いづらい。何をやるにも、いちいちStreamがどうだのこうだのなんてウンザリです。ていうか何だこのvarの多さ、変数乱れ打ち! そうなるとついつい、よーしパパ、ラッパー作っちゃうぞー、とか言ってしまいますが、もう見てらんない。.NET FrameworkにはWebClientというMS謹製のラッパーがあるわけなので、それを使いましょう。認証?Cookie?自前で取ればいいんですよ、ヘッダーから。
// WebClientならポストは超簡単!
var wc = new WebClient { Encoding = Encoding.GetEncoding("euc-jp") };
wc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection
{
{"next_url", "/home.pl"},
{"email", "めるあど"},
{"password", "ぱすわど"}
});
// じゃあCookieはどうするの?というと、ResponseHeaderから自前で抽出します
var setCookie = wc.ResponseHeaders[HttpResponseHeader.SetCookie];
var cookies = Regex.Split(setCookie, "(?<!expires=.{3}),")
.Select(s => s.Split(';').First().Split('='))
.Select(xs => new { Name = xs.First(), Value = string.Join("=", xs.Skip(1).ToArray()) })
.Select(a => a.Name + "=" + a.Value)
.ToArray();
var cookie = string.Join(";", cookies);
// 以降は取得したCookieをHeaderに設定しておけばOk
wc.Headers[HttpRequestHeader.Cookie] = cookie;
var result = wc.DownloadString("https://mixi.jp/show_log.pl");
Console.WriteLine(result); // アクセスできてるのを確認
そう、WebClientでも、ResponseHeaderからSetCookieは取れるのです。なので、ここからCookieにバラしてやれば、あとはHeaderに設定するだけなので簡単です。一見WebRequest並に行数がかかっているのですが、大変なのはCookie分解部分だけです。分解がちょっと面倒なのは否めませんが……。基本的にカンマ区切りとなっていますが、有効期限の設定されているものが含まれていると「expires=Fri, 16-Dec-2011」のようにカンマが入ってしまい、単純なSplit(',')では失敗します。なので正規表現の否定戻り読みでexpires=***,の場合は除外しています。あとは、バラしてクッツケテ、を繰り返して生成。そういえばSelect三連打ですが、これはもちろん複数行にすることでSelect一つで済ますこともできます。でも、そこはそれぞれ役割を切って3つに分けるのが、私の美意識、でしょうか。効率を考えれば匿名型なんて作らない方がいいぐらいなのですけどね、効率じゃない良さってのがあるんです。Linqには。
やり方はまだあります。WebClientは本当にただのWebRequestのラッパーで、中では普通にWebRequestを呼んで処理しています。よって、継承してoverrideしてGetWebRequestの辺りを書き換えて、CookieContainerを使うようにすれば非常に簡単です。
class CustomWebClient:WebClient
{
private CookieContainer cookieContainer = new CookieContainer();
// WebClientはWebRequestのラッパーにすぎないので、
// GetWebRequestのところの動作をちょっと横取りして書き換える
protected override WebRequest GetWebRequest(Uri address)
{
var request = base.GetWebRequest(address);
if (request is HttpWebRequest)
{
(request as HttpWebRequest).CookieContainer = cookieContainer;
}
return request;
}
}
// WebClientを継承してちょっと書き換えてやれば一番簡単
var cwc = new CustomWebClient { Encoding = Encoding.GetEncoding("euc-jp") };
cwc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection
{
{"next_url", "/home.pl"},
{"email", "める"},
{"password", "ぱす"}
});
var result = cwc.DownloadString("https://mixi.jp/show_log.pl");
Console.WriteLine(result); // アクセスできてるのを確認
私的にはこれがお薦め。どうせWebRequestはそのまんまじゃ使い辛いので、多かれ少なかれラッパー作るでしょう。出来の悪いラッパーを作る/使うぐらいなら、WebClientの気の利かない部分だけ書き換えた方が良い。 ちなみにCookieの他にもWebClientの気の利かないところとしては、自動でリダイレクトするところが辛い、場合がある。普段はリダイレクトでいいんですが、リダイレクトされると困るシチュエーションもあります、たまに。そんな問題も、CookieContainerと同じくGetWebRequestの部分で、request.AllowAutoRedirectを設定すれば回避出来ます。
Web上のものをゴニョゴニョ処理するのに「Rubyなどのスクリプト言語の良さが目立つ。」というのは、ライブラリの問題にすぎない、ってことですな。XML処理には今やLinq to XMLがあるし、HTMLの取得にしてもちょっと工夫するだけで回避できるのでC#だから書きにくい、なんてことは無いと思っています。いやまあMechanize便利やん、とかありますがありますが。しかしC#には最終兵器、WebBrowserがあるので何とでもなる。HTML解析ならHtml Agility Packを使えば、物凄く簡単に出来ます。
最後に、Twitterの自分の投稿最新20件をmixiボイスに投げ込む、というコードを例として出してみます。CustomWebClientクラスは上に乗っけた奴を使っています。
static void Main(string[] args)
{
var encoding = Encoding.GetEncoding("euc-jp");
// ログイン
var cwc = new CustomWebClient { Encoding = encoding };
cwc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection
{
{"next_url", "/home.pl"},
{"email", "めーる"},
{"password", "ぱすわど"}
});
// 投稿に必要なpost_keyをhtmlから取り出す
var echo = cwc.DownloadString("https://mixi.jp/recent_echo.pl");
var postKey = Regex.Match(echo, "id=\"post_key\" value=\"(.+?)\"").Groups[1].Value;
// 例なので簡易化するため認証無しのTwitterステータスを取得します
// HttpUtilityの利用にはSystem.Webの参照設定が別途必要
var id = "自分の(じゃなくてもいいけど)TwitterID";
var texts = XDocument.Load("http://twitter.com/statuses/user_timeline/" + id + ".xml")
.Descendants("status")
.Select(x => HttpUtility.HtmlDecode(x.Element("text").Value))
.Reverse();
foreach (var text in texts)
{
// mixiボイスに投稿(UTF-8以外の日本語の投稿はUploadValuesが使えない(泣)
cwc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
cwc.UploadString("http://mixi.jp/add_echo.pl", string.Join("&", new[]
{
"body=" + HttpUtility.UrlEncode(text, encoding),
"post_key=" + postKey,
"redirect=recent_echo"
}));
}
}
差分を記録するようにしたり、@付きを除外したりするようにすれば、そこそこ使えるんじゃないかしらん。利用はご自由にどうぞ。
Linqでコマンドラインオプション解析
- 2009-12-13
最近、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は凄いですね! まだ触ってないので実際の使いかっては分かりませんが、ラムダ式の使い方に驚きました。非常に上手いやり方だと思います。シンプルで。明快で。覚えやすく書きやすく。私もこういう発想が出来るようになりたいなあ。
TwitterTLtoHTML ver.0.1.0.0
- 2009-12-08
ノートPCを買いました。完全デスクトップ至上主義者だったというのに!あれです、あんまり引きこもってばかりいるのもよろしくないので、ノートPCさえあれば勉強会とかも出れる!のかどうかは、そもそもなくても出れるよねえ、あっても出れないよねえ(私の非コミュ脳的に) などと思いつつも、まあそんなこんなで買いました。流行りのCULVノートって奴です。Visual Studio 2008が思っていたよりも遥かに実用的な速度で動いていて、そう、こんなんでいーんだよ、とか思ったりなどした。けれど、VS2010は絶望的に動かなかった。重過ぎる。世の中厳しい。
引きこもり解消目的の他にもう一つ、常時起動の半サーバー用途というのもあります。ストリームAPIを監視したxboxinfotwitusersリストへの追加プログラムを常時デスクトップPCで振り回すのもカッタルイというか消費電力的に無駄なので、低消費電力なノートPCへ退避させよう、と思ったわけです。そもそも他にも、はてなついったー同期ツールだのXboxInfoTwitだの、PC常時起動を前提のアプリを幾つか公開しているので、調度良いということで。
んで、本題。常時起動PCがあるなら、過去ログも常時起動で定期的に取得して、差分をHTMLに残せばいいよね!それをDropboxなんかの共有フォルダに保存するようにすれば、取りこぼしもないし、何処からでもログを参照できるしで最高ぢゃん(そこで本当に自宅鯖にしてネットワークに公開する、というのは手間がかかりすぎるので超却下)。というわけで定期起動実行用のモードを追加しました。前回取得からの差分のみを、yyyy/MM/dd_HHmmssの形式(/はフォルダ)で保存します。今まで通り、過去800件取得モードも残してあります。
定期取得でやりたい場合は、タスクスケジューラに突っ込めばおk。タスクスケジューラは柔軟に設定出来る分、とっつきづらくて面倒くさいんですねえ。でも、例えば「バッテリ電源の時は実行しない」とか素敵オプションが色々用意されているので、使うといいと思います。トリガを大量に設定しておいて、18-24とかの流速の激しい時間帯は更新間隔短め、0-9とか静かな時間帯は更新間隔長め、12時のお昼休憩の前に一度まとめて読みたいので12時ジャストに設定。とか色々と考えられますので適当に気に入る設定を探ると良いんじゃないかと思われます。
あと一応、TinyUrlとかのデコード機能も入れておきました。実装は超手抜きで、Urlを片っぱしから WebRequest.Create(url).GetResponse().ResponseUri.AbsoluteUri; しているだけです。んま、問題ないでしょう、多分。普段Echofonで短縮Urlのまま表示されていただけに、こうして展開された形で見れると、いかに短縮Urlがイライラさせるものなのかよーく分かりますな。投稿時に必須なのはしょうがないのですけど……。
それと、今回からは初回設定は対話式ウィザードで行うようにしてます。あと、パスワードはそのPCでのみ復元出来る、という形で暗号化されます。設定ファイル直書き換えで一番嫌なのは、パスワードを平文で置く、ということなので、それを避けるために、ですね。書き捨ての小さいコンソールアプリなのでいっかー、と最初思ったんですが、やっぱり気になりました。
最後に、バグフィックス。二重でHtmlエンコードしてた部分を直しました。TwitterからのXMLは既にHTMLエンコードされている状態なので、それをそのままXElementに流し込むと二重でエンコードされてしまいます。なので、一旦デコードしています。この辺は結構よくミスしてしまうんですよねー。取得したものがどんな状態なのか、利用するクラスがどういう動作をするのか、ともにちゃんと把握していないとハマリがちです。
ちなみにまるで利用者がいる風な口で紹介していますが、ダウンロード数は超絶少ないので利用者なんていませんよ! 完全に自分用ですな。
2009/12/09 追記
ダウンロード先ファイルが古いバージョンのままでした……。今、直しました。ただでさえゼロに近いダウンロード数だったというのに、こうして使ってくれるかもしれない/コードを見てくれるかもしれない人を失ってしまう……。
書評 : More Effective C#
- 2009-12-06
結論は「Linq to Objectsの本」です。全編に渡って例題がLinqの再実装となっていて驚きました。「作って学ぶLinq」のほうが題として正しいぐらい。冗談じゃなく本当に、7割ぐらいが実質Linq周りです。実質、と言ったのは本書中では特に明言されていないからですが、見ればすぐにこれLinq to Objects……と突っ込みたくなること請け合いの例が沢山収録されています。
以前からLinq to Objectsに絞った解説書が出るべきだ、と思っていました。Linq to Objectsはこれでいて結構深いのです。どうもLinqというとLinq to SQLとか、データベース周りの喧伝の印象が強いようで、Linq to Objectsの実態が正しく伝わっていない気がします。今時リスト処理に高階関数使うなんてどのLightweight Languageでも常識よねー、というお話でもあります。Rubyのメソッドチェイン+ブロックなんて見た目だけで言えばLinqと丸っきり一緒ですし。昨今のモダンな言語の最も優れた部分を、最も優れた形で掲示しているのがLinqです。(優れた形、というのに異論はあると思いますが突っこまんで下さい)
そんなわけで褒め称えたいところだし、内容は結構良いと思っているのですが難点が一つ。対象範囲がC#3.0までのわりに、書き方が微妙に2.0っぽいこと。これはよろしくない。Linqに関しても再実装であることが本書中に明言がなく、書き方が2.0なので、「2.0でLinqをやるには」になっています。別に原理を知れればいいわけで、何も本書中の書き方を真似る必要はないのですが、それだと人に薦めづらいのですね。Linq知らない人に、これ見て学ぶといいよ、と素直に手渡したいのだけど手渡しにくい感が悔しいです。変にC#2.0と3.0を行ったり来たりするようなフワフワした構成じゃなく、Linqであることを明言した上で、その解説に徹してくれればよかったなあ、なんて思うんですね。
本の意義というか効用は、Linqや高階関数を多用してしまっても、この本が免罪符になるというのが一番大きいですね! C#3.0というのはLinqを使いこなし、更にはLinq風に設計構築していくのがEffectiveなのです、と大手を振って言える、かも。でもまあ、実際Linq風に扱うのが基盤になっているのは確かなので、変に凝るよりはLinq to Xxxみたいになっているほうが嬉しいです<ライブラリのような根幹部分での設計
Moreが先に出ていて、無印の発刊はこれから先です。無印がMoreの後に出るのは、本国では無印はC# 4.0対応の第二版が出るのでそれを待つのかなー、と思ったのですがそういうわけでもないようで。というわけで恐らくC#1.0まで対応のものだと思うので残念のような、そうでもないような。私はC#3.0から入ったにわかC#使いなので、1.0の書き方を見れるというのも新鮮で面白いんじゃないかなー、なんて思ってます。
Reactive Extensions for .NET (Rx) メソッド探訪第六回:exception handling
- 2009-11-29
.NET Reactive FrameworkからReactive Extensions for .NET (Rx)に名称が変わったようなので、タイトルも変更。長いね。というわけで久しぶりなのですが、今回はざっとexception handling operators、つまり「Catch, Finally, Retry, OnErrorResumeNext」を見てみることにします。それとRun(ForEachなので説明不要ですが)。Rxって何?という人はHello, Reactive Extensionsをまず参照下さい。
Rxの花形はイベント合流系のメソッドにあると思うので、ひたすら脇役ばかりを紹介してちっとも本流に入ろうとしないのはどうかと思うのですけど、EnumerableExのCatchを見て、あー、こりゃ便利だ、ヤバい、便利だ、用途すぐ浮かんでしまった、というわけでしてCatchを紹介します。まずは、その浮かんだ例であるTwitterのタイムライン取得をどうぞ。例はIEnumerableに対してのものですが、IObservableに対してのものも同じです。
class Twitter
{
public string Text { get; set; }
public DateTime CreatedAt { get; set; }
}
static IEnumerable<Twitter> EnumerateUserTimeline(string userName)
{
// {0}はユーザー名、{1}はページ番号 公開ユーザーのものを取得なら認証不要
var format = "http://twitter.com/statuses/user_timeline/{0}.xml?page={1}";
foreach (var page in Enumerable.Range(1, 1000))
{
var query = XDocument.Load(string.Format(format, userName, page))
.Descendants("status")
.Select(e => new Twitter
{
Text = e.Element("text").Value,
CreatedAt = DateTime.ParseExact(e.Element("created_at").Value,
"ddd MMM dd HH:mm:ss zzzz yyyy",
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal)
});
foreach (var item in query) yield return item;
}
}
static void Main(string[] args)
{
// 2009/11/23から今日までの投稿を古い順に並べるというもの
var test = EnumerateUserTimeline("neuecc")
.TakeWhile(t => t.CreatedAt >= new DateTime(2009, 11, 23))
.OrderBy(t => t.CreatedAt)
.ToArray();
// これで基本的には問題ないわけですが、TwitterにはAPI制限があるので
// ちゃんと全部取得出来るわけではなく、API制限発動 => 死亡になる可能性がある
// 死んでもいいんだけど、せっかく取った死ぬ前のデータはがめておきたいよねえ
// というわけで、そこで出番なのがRxのCatch!
var test2 = EnumerateUserTimeline("neuecc")
.TakeWhile(t => t.CreatedAt >= new DateTime(2009, 11, 23))
.Catch((Exception e) => Enumerable.Empty<Twitter>())
.OrderBy(t => t.CreatedAt)
.ToArray();
// 例外が発生したら握りつぶして、代わりにEnumerable.Emptyを返します
// なので、例外発生前のデータは全て取得出来ています、素晴らしい!
}
といった感じです。つまりCatchは、そのまんまCatchです。Linqで全部書くのも良いんだけど、例外処理が出来なくてなあ、という不満がこれで解消されます。残りのFinally, Retry, OnErrorResumeNextですが、全部Catchの派生みたいなものです。とりあえず簡単な例を。
static IEnumerable<int> Iterate1To5()
{
yield return 1;
yield return 2;
throw new DivideByZeroException(); // 嘘例外でも投げておく
yield return 4;
yield return 5;
}
static void Main(string[] args)
{
// 1,2
Iterate1To5().Catch((Exception e) => Enumerable.Empty<int>()).Run(Console.WriteLine);
// 1,2,100,200
Iterate1To5().Catch((Exception e) => new[] { 100, 200 }).Run(Console.WriteLine);
// 1,2 -> 例外発生(ArgumentNullExceptionはDivideByZeroExceptionじゃないのでCatchしない)
Iterate1To5().Catch((ArgumentNullException e) => new[] { 100, 200 }).Run(Console.WriteLine);
// 1,2,100,200。つまりCatchの簡略版
Iterate1To5().OnErrorResumeNext(new[] { 100, 200 }).Run(Console.WriteLine);
// 1,2,Finally。これでtry-catch-finallyが出来あがる
Iterate1To5()
.Catch((Exception e) => Enumerable.Empty<int>())
.Finally(() => Console.WriteLine("Finally"))
.Run(Console.WriteLine);
// 1,2 -> 1,2 -> 例外発生。例外を検知したら最初から列挙し直しての再試行
// EnumerableExのRetryはバグっぽくてObservableとは違う動きをする
// 明らかにオカシイのでそのうち修正されるでしょう
Iterate1To5().ToObservable().Retry(2).Subscribe(Console.WriteLine);
}
最後に、中身をちゃんと知るには自分で実装するに限る、ということでIEnumerableでの拡張メソッドで再現してみました。Catchは本当に便利なので、わざわざRx使うのも、と思う場合は以下のコードを是非コピペして使ってくださいな。
// ループをぶん回すだけ、というもの(linq.jsではForce()が同様の働き)
public static void Run<TSource>(this IEnumerable<TSource> source)
{
source.Run(_ => { });
}
// ようするにForEach
public static void Run<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
{
foreach (var item in source) action(item);
}
// try-catch句の中でyield returnが使えないので回りっくどいことに
public static IEnumerable<TSource> Catch<TSource, TException>(this IEnumerable<TSource> source,
Func<TException, IEnumerable<TSource>> handler) where TException : Exception
{
using (var enumerator = source.GetEnumerator())
{
while (true)
{
TException exception = null;
var hasNext = false;
try
{
hasNext = enumerator.MoveNext();
}
catch (Exception e)
{
exception = e as TException;
if (exception == null) throw;
}
if (exception != null)
{
foreach (var item in handler(exception)) yield return item;
}
if (hasNext) yield return enumerator.Current;
else yield break;
}
}
}
// Rxにはこういう、handlerがActionのオーバーロードが欲しいです
// わざわざ空のシーケンス投げるのは面倒くさいし、匿名型に対応できないじゃないか!
public static IEnumerable<TSource> Catch<TSource, TException>(this IEnumerable<TSource> source,
Action<TException> handler) where TException : Exception
{
return source.Catch((TException e) => { handler(e); return Enumerable.Empty<TSource>(); });
}
// OnErrorResumeNextはCatchの簡略版みたいなもんですね、別に必要ないような
public static IEnumerable<TSource> OnErrorResumeNext<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> next)
{
return source.Catch((Exception e) => next);
}
// ToList().ForEach()とRun()ではactionの出るタイミングが変わることに注意
public static IEnumerable<TSource> Finally<TSource>(this IEnumerable<TSource> source, Action action)
{
try { foreach (var item in source) yield return item; }
finally { action(); }
}
// 本当は無限でやるべきなんでしょうが、int.MaxValueで。
public static IEnumerable<TSource> Retry<TSource>(this IEnumerable<TSource> source)
{
return source.Retry(int.MaxValue);
}
// EnumerableExのRetryがバグ臭いのでObservable.Retryの挙動を採用しました
public static IEnumerable<TSource> Retry<TSource>(this IEnumerable<TSource> source, int retryCount)
{
var count = 0;
Exception exception = null;
while (count < retryCount)
{
exception = null;
foreach (var item in source.Catch((Exception e) => exception = e))
{
yield return item;
}
if (exception == null) yield break;
count++;
}
throw exception;
}
どれもCatchの派生のようなものです、CatchイイよCatch。これは使いまくりたくなる。それにしてもtry-catchの中でyield returnが使えないのを、はじめて知りました。こんなことやろうとしたことがなかったので。あと、EnumerableEx.Retryはひっじょーにバグ臭いです。ちなみにEnumerableEx.Mergeもバグ臭い。全体的にEnumerableExはバグ臭さ全開です。明らかに(Observableから)適当に移植した感漂ってます。ヤバい。
TwitterのTL過去ログをHTMLにするツール
- 2009-11-27
Twitterの他の人のポストは全部読みたいと思っています。数千もフォローしてるアルファーツイッタッターでは無理でしょうけど、せいぜい百ちょいぐらいなら全然いけるわけです。と、思っていたのですが、たかだか200を超えたところで、ん、無理……?と思える感じになってきてしまいました。ツール的限界で。Webから過去ログを見ようとすると、限界点に到達してしまって未読があるのに過去ログが見れない状態になってしまって。ていうか、そもそもWebでログを見るというのはダルい。まあ、ないですよね。私がTwitterで使っているツールはEchofonで、これは過去ログ見るのに適さないし全然昔の見れないし、というわけでどうしたものかなー、と思っていたんですが、作ればいいわけですよね、過去ログ閲覧専用Twitterクライアント。
と、考えてはみたものの、そもそもわざわざツール作るまでもなく、ログをHTMLで吐けばいいんじゃね?と気付いた。YesYesYes。流し読みなら、むしろへっぽこ専用ツールよりもブラウザのほうが見やすいし。家でガッとHTML取得しといてモバイルに転送して電車でゆったり見る、とか出来るし。というわけで、可能な限り過去ログを掘ってHTMLに吐きだすプログラムを書きました。可能な限り、といってもAPI制限の都合上で最大800件まで、のようです。うーん、これじゃあ半日ぐらい前、程度ですよねえ。18-24時とかだと一瞬で吹っ飛びそうかも。3000件ぐらいまでは欲しいとこなのですが……。なお、API消費はたった4か5なので安心です。一回につき200件取れるので。
デザインはCSSで行えます。例えばimgのwidthとheightを0pxにすればアイコン表示を消せます。これで学校や会社で見る時にアニメアイコンが並んで恥ずかしい思いをしなくて済む! あとまあ、デフォルトのCSSはショボい(私がCSSの知識ないので……。float良く分からん、高さ揃わない、50pxで決め打ち!とか)ので、適当に改良して使ってください。
あと、コード(C# 3.0)も同梱してあるので適当に見て突っ込んでくださいな。HTML組み立て部分はLINQ to XMLです。
var urlPattern = new Regex("(s?https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+)");
var xhtml = new XElement("html",
new XElement("head",
new XElement("link", new XAttribute("rel", "stylesheet"), new XAttribute("href", "style.css"))),
new XElement("body",
new XElement("ul",
EnumerateHomeTimeline(username, password).Select((t, i) =>
new XElement("li", new XAttribute("class", (i % 2 == 0 ? "even" : "odd")),
new XElement("div",new XAttribute("class","name"), t.ScreenName),
new XElement("div",new XAttribute("class","date"),t.CreatedAt.ToString("G")),
new XElement("div",
new XAttribute("class","image"),
new XElement("img",new XAttribute("src", t.ProfileImageUrl))),
new XElement("div",
new XAttribute("class","text"),
urlPattern.Split(t.Text).Select(s=>
{
var href = urlPattern.Match(s);
return (!href.Success)
? (XNode)new XText(s)
: new XElement("a",new XAttribute("href",href.Value),href.Value);
})))))));
えーと…… 汚い、ですね!それでも、このLINQ to XMLの関数型構築がなければどれだけ悲惨なことになっていたか!やはりLINQ to XMLは素晴らしい。さて、しかし困ったのがリンクのaタグ付け。文字列で扱っていれば普通に置換すれば済む話なのですが、XTextにそれを放り込むとタグはエスケープされます。最初驚いたのですが、考えてみると当然ですね、XMLとして不正なものは許されないので。しょうがないのでSplitしてXMLとして組み立ててやりました。
json/xmlを拾ってきてHTMLに整形するだけなのだから、JavaScriptで書いてうぇぶあぷり、的なものにしたほうが利便性とか何とかかんとかが良好なんじゃございませんこと?とか思わなくもなかったのですが、C#、楽なので、ほんと。良い言語なんですって。
ver.2.1.0.0
- 2009-11-27
未知のエラーが発生する原因の一つを解消しました。私の確認出来た範囲では、フレンドの中にNetflixを使っている人がいると100%エラーが発生するようでした。原因はプレイ中状況が<Translated text>で、この<がエスケープされてないせいでタグとして認識してたせいでパースに失敗してるせいでした。適当に検索したところTranslated textはちょっと特殊な状況表示?で他のゲームでも出現するようですね、何だろう、翻訳しようと思ったけどまだ出来てませんって感じでしょうか(笑)
で、えーと、これはXbox.comが悪いですよ、ほんと、Microsoftはもっとしっかりサイト作って欲しいなあ。いつぞやかの実績暴走の件だってそもそも……。と、言ってみたところでユーザーからはプログラムがタコなせいにしか見えないわけですし、Xbox.comはXbox.comで、イレギュラーなアクセスをしてる輩のことなんて別に考える必要はないわけで、やっぱり悪いのはプログラムですね、あはは。
さて、今回は<Translated text>を丸ごと置換するという頭悪すぎな方法での応急処置をしたのですが、今後も平然と<がエスケープ抜きで登場するようなケースは、ありそうですね……。というわけで、何とかすべきところではあるのですが、汎用的な置換表現を作るのはほぼ不可能だし、全てに対応しようにも如何せん何処に出現するかも不定すぎて無理げ。別の問題が出た時にまた考えることにします。
機能追加が一つ。指定文字列が含まれる場合には投稿しない、という機能を足しました。例えば「Xbox 360 ダッシュボード|Halo Waypoint」にすれば、ダッシュボードとHalo Waypoint再生時は投稿しないようになります。なお、大文字と小文字やスペースの有無を完全に区別しますので、利用するときは一度Twitterに投稿されたものをコピペすると良いと思います。なお、ver.1にあった「ダッシュボードは無視」機能に似ていますが、ver.2のものは起動時投稿設定にも適用されるため、100%、ver.1と同じというわけではありません。うーん、ver.1の起動時設定のみ特別扱いってのがどうかなー、と思っていたので今回の仕様に変更されたわけですが、どうなんでしょうねえ。
Hello, Reactive Extensions
- 2009-11-24
Reactive FrameworkがReactive Extensions for .NET (Rx)として、DevLabsで公開されました。紫のうなぎアイコンが可愛い。これは(消滅してしまった)Microsoft Voltaと同様のものなのですが、開発チームが同じだからだそうです。DevLabsには他にAxumやSTM.NETなど、興味深いプロジェクトがいっぱいありますが、日本語による情報がほとんど手に入らないので手を出しづらいところがあります。RxもDevLabsに登場したことでグッと情報が増えましたが、英語ソースによるものばかりなので、私の脳みそ的には相当シンドイことになっています。英語辛いよぅ。
とはいえ、小細工しなくても.NET3.5 SP1上で動かせるのは素敵なので是非試しましょう! Silverlight Toolkitにこっそり収録版からも、かなりパワーアップしています。メソッド大増量、そしてToolkit版でバグい挙動していたのが本当にバグなのか私のやり方が悪いのか悩んでいた部分がサクッと修正されいてホッとしたり。
インストールディレクトリに置いてあるchmのヘルプを見ることも出来るし、IntelliSenseも動きますので、前に比べると触りやすくなりました。ただまあ、例ぐらいは入れてよって感じで、簡素極まりない一行説明から理解するのは、やっぱり難しい。
System.Interactive.dll
Rxには興味ないよ、という人にも大変役立ちなのがこのdll。デフォで参照設定に加えることは確定的に明らか。中のクラスはSystem.Linq.EnumerableExのみで、ようするに拡張メソッド集です。EnumerableExという名の通り、Linq.Enumerableに対する追加版となっています。基本的にはIObservableに用意されていたメソッドをIEnumerable用に持ってきたという感じです。Repeat(value)による無限リピートやReturn(value)による単体シーケンス作成、Generate(所謂Unfold)など、欲しかった生成メソッドが沢山用意されています。
更に当然のようにIEnumerableに対する拡張メソッドもたっぷり。Zip(.NET 4に搭載)やMemoize、それにDo(副作用専用のActionメソッド)、Run(ようはForEachです、そう、ForEachですよ!)が非常にうれしい。他、挙げればキリがない用途不明の拡張メソッドがテンコ盛りなので要研究ですね。
例えばフィボナッチ数列。
EnumerableEx.Generate(
new { v1 = 0, v2 = 1 }, // initialState
_ => true, // condition
a => a.v1, // resultSelector
a => new { v1 = a.v2, v2 = a.v1 + a.v2 } // iterate
)
.Take(30)
.Run(i => Console.WriteLine(i));
// conditionが無限ならこうも書ける(第二引数の戻り値がIEnumerable、だけど平たくされる?)
EnumerableEx.Generate(
new { v1 = 0, v2 = 1 }, // initial
a => EnumerableEx.Return(a.v1), // resultSelector
a => new { v1 = a.v2, v2 = a.v1 + a.v2 }); // iterate
んね、素敵。
System.CoreEx.dll/System.Threading.dll
CoreEx.dllにはAction, FuncのT16まで版(.NET 4に搭載)やUnit(voidを表す型)が主なところ。他にIEventやNotificationがありますが、これらは主にRxで使うためのものですね。Threading.dllはLazy, Task, Parallelといった、.NET 4に搭載されるメソッドの先取りといった感じのものががが。真剣に追っかけると大変なのでスルー。
System.Reactive.dll
以前のに比べるとメソッドが増えているのは当然なのですが、目立つところではSystem.Joins.Patternクラスの追加が目新しいです。何に使うのかはまだ知りません。ふむ。自分でお題を探すのも大変なので、Forumで出てる内容を幾つか紹介します。
// 100,1,2,3,....,10
Observable.StartWith(Observable.Range(1, 10), 100);
// 順番が奇妙なのは拡張メソッドとして使えるようにしたため
Observable.Range(1, 10).StartWith(100);
ConsはStartWithに改名されました。ついでに順番がちょっと変わりました。先頭に付け足すのに、第二引数というのが違和感全開なのは否めない。これは、拡張メソッドとして利用できるようにしたためでしょうね。メソッドチェインを崩さずに、先頭に値を足すことが出来るようになりました。ObservableだけではなくEnumerableにもあります。
public static IObservable<IEvent<MouseEventArgs>> GetMouseDown(this Control control)
{
return Observable.FromEvent<MouseButtonEventHandler, MouseEventArgs>(
h => (sender, e) => h(sender, e),
h => control.MouseDown += h,
h => control.MouseDown -= h);
}
h => (sender, e) => h(sender, e)っていうのが混乱しますな。第一引数のhはEventHandler<MouseEventArgs>です。ここでhをMouseButtonEventHandlerに変換します。この辺も、ActionやFuncと同じく、EventHandler<TEventArgs>だけあればいいのに、その他のゴチャゴチャしたデリゲートは消滅してしまえばいいのに、とか思わなくもないのですがしょうがない。「sender,e => h(sender,e)」は引数がobject,MouseEventArgsで戻り値がvoidのよくあるイベント用のデリゲートです。素の状態でこのラムダ式を書くと型が決まらないので動作しませんが、FromEventの型宣言時にMouseButtonEventHandlerだと明示しているので、変換出来ます。ここで変換されるので、第二、第三引数のhはMouseButtonEventHandlerになります。
メソッド探訪の第一回で警告が出る、とか書いてしまったのですが、こういう風に記述すれば警告も出ず、文字列メソッド名を使わずに利用できたようです。言われてみればなるほど、って感じなのですが、気付けなかったなあ……。
ambはLISPのambを由来として、ambiguous(不明瞭)の略。だそうです。Rxでは、例えば……
var first = Observable.Range(1, 3).Delay(300);
var second = Observable.Range(4, 3).Delay(100);
var third = Observable.Range(7, 3).Delay(200);
Observable.Amb(first, second, third).Subscribe(s => Console.WriteLine(s));
Console.ReadLine();
Delayは発火を指定ミリ秒だけ遅らせるメソッドです。では、何が表示されるでしょうか。答えは、"4,5,6"です。んじゃあthirdをDelay(0)にしたら? "7,8,9"が表示されます。なるほど、分かってきた。つまり最初に到達したものを採用する、というわけです。この例ではわざとらしくDelayを足したシーケンスを投げてみましたが、例えば幾つかのイベントを並べて、最初にイベントが発火したものを。みたいな用途が考えられなくもない。
EnumerableEx.Amb(
new[] { 1, 2, 3 }
.Select(i => i + i)
.Select(i => i + i),
new[] { 7, 8, 9 }
.Select(i => i + i))
.Run(i => Console.WriteLine(i));
IObservableは分かるとして、何故かIEnumerableにもAmbがあります。上の例は何が表示されるでしょうか?答えは、8割は14,16,18です。残り2割は4,8,12です。チェインを沢山繋いだ方が原則的には「時間がかかる」ため、チェイン数の少ない方が採用される場合が多い。ただし、内部ではThreadを立てているので、必ずしもそうなるわけじゃない。というわけで、結果は非常に不確定で不明瞭で、使い道は完全に謎。
ver.2.0.1.0
- 2009-11-24
一部タイトル、例えばアジア版GoW2で実績が取得出来ないという不具合を修正しました。あと、今現在、私の方でもたまに「未知のエラー」が出るのは確認出来ているのですが、ちょっと原因が掴めていない状態なので修正にはもう少し時間がかかりそうです。それと、そもそもXbox.comへのログインに失敗するというのは全く分かってませんので、もう少しどころじゃなく時間がかかりそうです。
そういえば説明を忘れていたのですが、ver.2からver.1にあった「投稿の際ダッシュボードは無視」機能は削ってしまいました。これは、どうやっても綺麗に多言語対応と混ぜることが出来なかったので……。日本語だけに限定すれば決めうちで簡単なのですけどね。利便性的には多言語対応なんかよりもこっちのほうが遙かに上だろ!と突っ込みたい気持ちはとても分かりますが、そんなこんなな事情なので復活させることは恐らくありません。
もう一つ、ver.2からLiveのステータスが離席中になった際もオフライン扱いにしちゃっています。ver.1では中途半端な無視の仕方をしていて、潜在的なバグの危険性があったので、すっぱりとオフラインということにしてしまいました。本体を10分放置しているとスクリーンセーバーが動いて、Liveのステータスも自動的に離席中になるようなのですが、もし本体放置で離席中になるのを拒否したい場合はスクリーンセーバーをオフにすればLiveステータスもずっとオンラインのままになります。スクリーンセーバーの切り替えは本体設定から「システム設定→本体の設定→画面→スクリーンセーバー」で入れます。
ver.2.0.0.0
- 2009-11-20
XboxInfoTwitの認証数が岡本641本吉起を超えた記念、というわけでもないのですが大幅に変更しました。例によって全然テストしてないので動かないとか色々あるかもなので、生暖かい目で見守ってください。というか、ボソッとTwitterでxxで動かねえ、とでも言って貰えると非常に助かります。
今回の更新の主な内容は、クローラーを刷新しIEを使用しなくなった。です。それによって「メモリ消費量激減」(というか前が多すぎた、というか完全にメモリリークしてた) 「スクリプトエラー消滅」「IEでのログイン状態に左右されない」「ページ遷移のクリック音UZEEと無縁」などなど、まあ、これで安心して使えるかと思います。環境依存的に動かなかった人も動くようになった、はず、きっと。そんなこんなで、今回から中身が全く別物になっているので、環境依存、もしくはバグによる動かないケースが(また)増えそうなので、その辺は見つかり次第早めに対処したいと思います。当面は不安定かもしれませんがご了承ください。
挙動の変更としては、実績解除の投稿が必ず行われるようにしました。今までは実績解除後、投稿されるまでの間にXbox360の電源を落としたり別のゲームに変えてしまったりすると解除の投稿を行わなかったのですが、今回からは、電源を落としても別のゲームに変えても実績解除の投稿を行います。ちなみにまだ一回も実績解除を試してないので(デバッグ用にデータをごそごそ弄って解除したフリ、ぐらいはやりましたが)本当に上手く動いてくれるのかは謎です。
あと、エラーメッセージが親切になりました(今まで一律に通信エラーで理解不能だったので) でもタイミング次第では平然と「未知のエラー」とかいう素っ気ない応答しか出しません。酷い。この辺は追々直していこうかな、とは思ってるのですが。
機能追加その一、別言語からの取得が可能に!今まではja-JPだけでしたが、英語ならen-USを、台湾語(中国語?)ならzh-TWを指定することによって、他の言語のデータが投稿されます。別にja-JPしか使わないとは思いますが、将来的にアプリケーション自体を多言語対応にして海外版もリリースしたいなあ、と思っているので(そのタイミングでコードも公開しようと考えてます)そのための下準備の一つです。
機能追加その二、ハッシュタグの自動付加。新しくプレイしたタイトルはタイトル名が記録されて、設定画面のハッシュタグタブの一覧に自動的に追加されます(任意での追加は不可能です)。ここでリストに、例えば「モダン・ウォーフェア2」だったら「MW2」と入力すれば、モダン・ウォーフェア2をプレイ時の投稿全ての末尾に「 #MW2」が付加されます。#に関しては付けなくても自動的に付けます(なのでハッシュタグとしてではなく、フッタとしての利用は現状不可能です)
機能追加その三、バルーンによる投稿通知。私的にはどうでもいいと思ってごほごほ。
2.0.0.1
例によって不具合発覚。20-30分ぐらい使ってると未知のエラーで死ぬようです。あまりにもの未知のエラー祭りは酷すぎた、のでとりあえず様子見で暫定的に対処してみました。うまくいってるかは不明。たかだか10数分間連続利用のテストすらしていないという!すみませんすみません。とりあえず今日は発売日に買ったけど全然プレイしてない(実績数がそれを物語ってる)Fallout3(OBLIVIONは超はまったのにFallout3はさっぱり琴線に触れず)をじっくりプレイしながらテストします、はい。