AutoHotKeyによるマウスカスタマイズとマルチディスプレイのためのスクリプト
- 2009-11-17
タイトルがAutoHotKeyによるカスタマイズ、なのですが、前置きが長かったり、一部設定が新しく購入したマウス前提になってる部分もあります。とはいえ基本的には汎用的に使える設定を書いているので、自分にはあまり関係ないな、と思う部分は適当に読み飛ばしてください。
長年使ってきたロジクールのマウス、MX Revolutionが相当ガタがきていて(シングルクリックがダブルクリックになるとか、ドラッグアンドドロップ中にリリースされちゃうとか)、おまけに電池もヘタれて、その上に充電端子の接触が悪くて中々充電されないというイライラ。耐えられない。というわけで、マウスを買い替えました。MX-R自体は結構気に入っていたはずなのですが、如何せん晩年の状態が最悪すぎたので何だか印象が随分と悪くなってしまいました。SetPoint(ロジクールのマウスドライバ)クソだし。
新しいマウスはSteelSeries Xaiです。究極のゲーミングマウスとして突き詰められた性能は、確かにカッチリと動く。ただ、あまりPCでゲームしないのでそこまでは分からない。とりあえずインタビュー読んでたら欲しくなったというだけでして(笑) 売り文句が上手いです。ゲーミングマウスにありがちな派手派手しい外観じゃないのも好印象。マウスパッドもメーカー推奨のセットで揃えちゃいました。握った印象やポインティング性能は申し分ない。ホイールだけはMX-Rは群を抜いて素晴らしかったかな、まあ、しょうがないか。
マウス変更は無線->有線への回帰でもあるのですが、無線マウスはダメです。省電力のために細かくスリープに入り、マウスを動かすとワンテンポ遅れて復帰する。このワンテンポの遅れがイライラしました。晩年の、MX-Rがヘタれてしまった状態だからそうなのかもしれませんが、とにかく無線への印象は最悪。何のかんのでマウス重いし。軽さは、大事、だよ。と、Xaiを触りながら思ったのでした。思えばXbox360も絶対有線コントローラー主義者なわけで(無線コンは窓から投げ捨てよう!) ほんと、NO WIRE, NO COMPUTING.
Xaiのアサイン可能ボタン数は7。一時は多ボタンキチガイだった私であり、MX-Rのアサイン可能ボタン数11から、Xaiの7へとの大減少は些かどうしたものか、と思いましたが、そんなに多用しない、マウスと独立してる操作、例えばウィンドウのモニタ移動系なんかはキーボードのほうにマクロ割り振ればいいぢゃん?という思想に変わってしまっているので、7へと減少したことはそこまで痛手、ではありません。何でもマウスでやる中二病から、バランスよく割り振る高二病へと進化したわけでさあ。
マウスカスタマイズ
Xaiはドライバレスで動作します。設定用ソフトウェアはありますが、これはただたんに設定項目をマウスに送るだけであり、キーカスタマイズにせよ移動速度にせよ、項目は全てマウス本体に記録されます。なので、Xai+AutoHotKeyは究極のポータビリティを誇ります。いつでもどこでも自分のお気に入りの設定で扱える。OS再インストール時の設定作業等も不要。これは……嬉しいですね。再インストールの度にクソSetPointを触らなきゃいけないのは気が滅入る話だったので。
MButton::Shift
;左手前
XButton1::BackSpace
;左奥
XButton2::Send,!{F4}
+XButton2::Send,^!{F4}
;右手前
Pause::SearchSelectedText()
;右奥
ScrollLock::WinMinimize, A
+ScrollLock::WinSet,AlwaysOnTop,TOGGLE,A
;選択したテキスト内容でぐぐる
SearchSelectedText()
{
bk = %ClipboardAll%
Clipboard=
Send,^c
ClipWait, 1
if(Clipboard != "")
Run,http://www.google.com/search?q=%Clipboard%
Clipboard = %bk%
}
Xaiの形状は左右対称で、カスタマイズ出来る箇所は7つ。左右クリック・ホイールクリック・サイドボタンが左右に二つずつ。カスタマイズはAutoHotKeyで行うので、Xaiでの設定はほとんどしません、ほとんどデフォルトで。但しWindowsの扱えるボタン数は5つなので、Windows管轄外のボタン二つ(右のサイドボタン)には普段キーボードで使わないキーを割り振って、それで代替しましょう。具体的には、私はScroolLockとPauseを割り振りました。ソフトウェアドライバで制御に自由の効くものなら、F13-F24といったキーボードに存在しないキーを使うのですが、XaiではF13を認識しなかったので、しょうがなくの対処でした。
ほとんどデフォルトと言いましたが、マウスの中央ボタンだけは潰して、Shiftにしました。マウスの中央ボタンって大して使わないでしょう。第一級の位置にゴミキーを置いておくなんて勿体ない。せいぜい、マウスジェスチャーの起動キーとして使うぐらい?私はジェスチャーが大嫌いなので、他のマウスボタンとの組み合わせ用としてShiftを選びました。Ctrlのほうが便利と言えば便利なのですが、ScrollLockやBreakと組み合わせると思ったような挙動をしないので、しょうがなく妥協しました。
サイドボタン左側、BackSpaceは言わずもがなに便利。「戻る」だけじゃなく、「削除」として利用できるのが高ポイント。奥側のAlt+F4も便利。もう右上の×ボタンをクリックしにいく必要なんてないんですよ!後で述べますが、AutoHotKeyのアプリケーション固有設定で、タブのあるアプリケーションに対してはタブを閉じる(Ctrl+W)を振っておくと楽。ついでにShift+XButton2をAlt+F4にすれば、タブ有りアプリでも右上の×を使う必要はなくなる。
サイドボタン右側、選択文字列のGoogle検索は問答無用に便利。Firefox内でのみ、とかIE内でのみ、ではなくあらゆるアプリケーションからワンクリックでキーワード検索出来るというのは非常に大事です。実行するのに手間がかかると、どうしても人は躊躇ってしまうものなんですね。たかだかツークリックであってもダメ。無意識に重たいコストになってる。ちょっと気になったらとりあえず検索する。その習慣を根付かせるためにも、ワンクリックで出来る必要があります。
サイド右奥の最小化ですが、私は何のかんので結構使ったりするので設定してます。Shiftとの組み合わせは最前面に固定で、これもやっぱり頻繁に使ってます。無いと、困る。
さて、Shift+サイドボタン左右の手前はデフォルトでは何も割り振っていません。この二つを、各アプリケーション固有設定用ボタンとして使います。たった二つ!なのですけど、このぐらい潔く割り切った方が「覚えやすい」し、結局は「使いやすい」かな、と思っています。せっかく割り振ってもあまり使わなかったら、別にキーボードショートカットでいいぢゃん、ということになりますし。
; Firefox
#IfWinActive,ahk_class MozillaUIWindowClass
XButton2::Send,^w
^XButton2::Send,!{F4}
+XButton1::Send,^{Left} ;タブを左に移動(Firefox側でキーカスタマイズ済み)
+Pause::Send,^{Right}
;Visual Studio
#IfWinActive,ahk_class wndclass_desked_gsk
^w::Send,^{F4} ;タブを閉じる
XButton2::Send,^{F4}
+XButton2::Send,!{F4}
+XButton1::Send,^- ;定義から戻る
+Pause::Send,{F12} ;定義へ移動
IfWinActive以下にアプリケーション固有設定を記述。例としてFirefoxとVisualStudioのものを。左右なので、直感的には「移動」系がそれっぽく扱えるかな、と思っています。(今現在の設定だとFirefoxでのタブ移動が今一つ思った感じに動いてくれなくて要改善だったりはする)
マルチディスプレイのためのスクリプト
```ini ; 無変換との組み合わせによる十字キー vk1Dsc07B & e::Send,{Up} vk1Dsc07B & s::Send,{Left} vk1Dsc07B & d::Send,{Down} vk1Dsc07B & f::Send,{Right} ;矢印キー(マルチディスプレイでのウィンドウ移動) vk1Dsc07B & Left::SendToTargetMonitor(3) vk1Dsc07B & Right::SendToTargetMonitor(1) vk1Dsc07B & Up::SendToTargetMonitor(4) vk1Dsc07B & Down::SendToTargetMonitor(2)
; 指定番号のモニタサイズを取得する GetMonitor(monitorNo, ByRef mX, ByRef mY, ByRef mW, ByRef mH) { SysGet, m, MonitorWorkArea, %monitorNo% mX := mLeft mY := mTop mW := mRight - mLeft mH := mBottom - mTop }
;対象モニタにアクティブウィンドウを移動する(高さリサイズ) SendToTargetMonitor(monitorNo) { WinGetPos, x, y, w, h, A GetMonitor(monitorNo, mX, mY, mW, mH) Random, rand, 50, 200 WinMove, A,, mX + rand, mY, w, mH }
無変換なんて使わないでしょ?カタカナに変換したいならCtrl+Iがお薦め。というわけで、これを潰して無変換+ESDFを十字キーにするのは、もはやないと死ぬ。右手をホームポジションから放して十字キーとか遠すぎるし!というわけで、問答無用でお薦め。hjkl なんかよりもずっと直感的ですし。FPSライクでもある。WASDでなくESDFなのは、ホームポジションを崩さない位置だから。上記コードには載せていませんが、他に無変換+Q,AでのHome,Endや無変換+Z,CでのCtrl+←,Ctrl+→なんかを割り振っています。プログラミング時の文字移動が大分楽になります。
そして、ウィンドウの座標即時移動はマルチディスプレイ環境なら便利というか必需だと思います。私の環境は4画面なので、無変換 + ←↑→↓で各対応するモニタにウィンドウ座標を移動させます。ディスプレイの縦サイズがそれぞれ異なるので、縦サイズは対象ディスプレイの高さいっぱいに引き延ばすようにしています。私はほとんどのアプリケーションを縦いっぱいに広げて使っているのでこの仕様で問題ないというか、むしろそれであって欲しいのですが、高さが変化すると困る人は適当に改良してください。WinMoveでmYを指定しているところyにすれば、高さは変えない、になります。X座標は、モニタの左端を基準に、50-200の範囲でランダムにバラつくようにしています。これは複数のウィンドウを送り飛ばした時に、完全に重なると扱いづらくなるのを避けるためです。
```ini
;Windowsキー
;アクティブモニタの半分サイズにして左右に寄せる
#Left::
GetActiveMonitor(x, y, w, h)
WinGet, id, ID, A
WinMove, ahk_id %id%,,x, y, w / 2 , h
return
#Right::
GetActiveMonitor(x, y, w, h)
WinGet, id, ID, A
WinMove, ahk_id %id%,,x + w / 2, y, w / 2 , h
return
;最小化と復元
#Up::RestoreAll()
#Down::#d
;全てのアプリケーションを元に戻す
RestoreAll()
{
WinGet, id, list
Loop, %id%
{
StringTrimRight, this_id, id%a_index%, 0
WinRestore, ahk_id %this_id%
}
}
Windowsキー+十字キーも対応するようにカスタマイズ。Windows7からは、一応モニタ移動系が割り振られていますが無視して上書き。まず左右ですが、モニタの半分サイズにして右寄せ左寄せです。これは、2つのアプリケーションを並べて比較したい時に重宝します。
Win+下はお馴染みのデスクトップ表示。そして上は「全てのウィンドウを元に戻す」です。例えばデスクトップに置いてあるフォルダを開きたい場合、Win+Dを使ってデスクトップを表示してフォルダを開く。そこから先、作業を終えたら、全て復元したいはずです、が、素のWindowsだと出来ません。一度フォルダを開くと、状態保存がなくなってしまうので、再度Win+Dを使っても、元には戻らず再びデスクトップ表示になってしまう。こんな不満も、AutoHotKeyなら簡単に解決出来ます。今回は単純に、起動している全てのアプリケーションに対し「元に戻す」を実行するというものにしました。
無変換+矢印といい、Win+矢印といい、ウィンドウ移動操作を多用してるのですが、狭い領域を有効活用というよりも、モニタ領域が広すぎる(2560x1600+(2048x1152)x2+1920x1200)からこそ移動類を充実させざるを得なかった、という側面があったりして。センターディスプレイ(30インチ2560x1600)はマルチディスプレイの中でも、当然一番見やすいモニタになるので、長めの作業を行う際は、普段は別のモニタに置いてあるアプリケーションもセンターに寄せて使います。なので、ワンタッチでセンターに寄せたり、左右に並べたりしたいわけです。それと私はゲーム(Xbox360)もセンターディスプレイに繋いでいるのでゲーム中はセンターが潰れます。ブラウザで攻略サイトを見たい、と思った時にセンターにブラウザがあると(基本はセンターに置いているので)移動させるのが面倒くさいので、これもまたワンタッチでサイドディスプレイに寄せたい、となるわけです。ゲーム終えたら、またワンタッチでセンターへ戻す。
AutoHotKey.ini
現時点で私の使っているiniファイルを置いておきますので、ご自由に使ってやってください。環境依存の部分が少なからずあるので、その辺は適当に除去しといてください。
ver.1.3.0.8
- 2009-11-16
暴走してしまいました。大変申し訳ありませんでした。Xbox.comが半メンテナンスで、壊れたデータを放出していたのですが(例えばFallout3の最大実績が620になったり)、それを取得して解析していた結果、実績を超連続投稿するということが発生しました。今回のものは暴走抑止用の暫定対策版となっています。しかし確実に防げる保証はないので、利用はXbox.comが安定してからにしてください。また、お願いなのですがXboxInfoTwitが不調な場合は、またXboxInfoTwitのクソが不安定だぜ、と思うのは当然なのですけど、少しだけXbox.comのほうも疑ってあげてください。そして、Xbox.comが怪しかったら、その日は利用を控えるという形でお願いします。これからは、データが怪しい場合は弾くような処理も増やしていこうとは思いますが、それでも全ての怪しいケースを弾けるわけではないので。
暴走抑止のほか、とりあえず実績連続投稿の最大数は10に設定しました。5だと、場合によっては少ないケースも出てきそうなのでとりあえず10で。それと、今回のアップデートは全くテストしていないので、そもそも正常動作するかも分かりません。その辺は、Xbox.comが落ち着いたら見ていきたいと思っています。
あと、責任はとても重く感じています……。公開停止しようとも思ったのですが、誰もがこのサイトを見に来ているわけでは当然ないので、まずは修正して新しく起動する人が、自動アップデートで最低限の回避をするのが第一だと思いました。今後の公開停止ですが、既に相当数の利用者がいる状態なので、公開停止にしてメンテ放置するよりは、問題が起こった際にちゃんと面倒を見る方が重要だと考え、当分は公開を続けることにします。
ver.1.3.0.9
連続ですがまた更新。1.3.0.8では実績解除自体が100%投稿できない状態になってました。風呂に入って頭冷やしてたら思い違いに気づいてああああああ、となりました、はは。それと、暴走の原因らしきものが見えたので(原因自体はXbox.comのデータ壊れなのですが、どの部分がどういう風に壊れていたのか、というのが私自体が遭遇してないので想像でしかないのですよー) とりあえずそれへの対策を重点的に追加してみました。原因が見込み違いだったり、他の原因だった場合は、まあ、しょうがない。そういえば今日ダッシュボード機能追加なんですね。毎回、機能追加前はXbox.comも合わせてドタバタしますが、しかし今回ほど酷いこともなかった。ちなみにもう一つの実績解除ツールも暴走していたので、今回のは本当に本当に不測の事態というかXbox.comのデータの壊れ方が誰にとっても想定外でした。天下のMSなのだから、メンテ時でもしっかりやってくれ、というのは贅沢ですかね。
ver.1.3.0.7
- 2009-11-11
CoD4やMW2、L4Dなんかで顕著に見られるようですが、オンライン対戦時の他プレイヤーが「参加可能」時にデータの取得に失敗して、ゲーム名に参加可能が付いていたり実績が0/0になっていたりするような件を修正しました。実のところ、これver1.3.0.1の時に修正したはずだったんですが、いつのまにやらその時対策したはずのコードが元に戻ってました。あらららら……。
といったように、非常にいい加減な開発姿勢なので今回のリリースでも更にバグ埋め込んだりする可能性大です。バグ見つけたら怒ってやってください。Twitter検索でキーワード「XboxInfoTwit」を始終チェックしてますので、もし不具合があったらTwitterでの投稿時に「XboxInfoTwit」と文中に混ぜておけば、例えば「XboxInfoTwitクソ、***で動かねえ」とか言ってくれれば私の方で巡回して気づくと思いますので、気楽に苦情文句要望バグ報告してやってください。
そういえばというわけでもないのですけど、利用ユーザー数が500超えました。いやー、ビックリですね。当初は2桁台に行けばいいなあ、とか言ってたぐらいだったり、実際致命的な不具合があったのに3ヶ月放置してたり(誰も使わないので気づかなかった!)などだったはずが。嬉しいです。が、現在のコード品質は相当アレなので、なるべく早く、せめて今年中には全面的に書き換えたver2.0を出せるといいなあ、なんて思っています。
ちなみに、amazonアサマシゲイツポイントの購入者数はゼロに近かったりします(買ってくれた人は本当に本当にありがとうございます)。いやまあ、別にネタなのでいいんですけどね。はは。
F# TutorialをC#と比較しながらでF#を学ぶ
- 2009-11-09
F#はMicrosoft発の関数型言語で、Visual Studio 2010に標準搭載されます。Visual Studio 2010 Beta 2も出たことだし、話題の?新言語を少し勉強してみることにします。F#の新規プロジェクト一覧にTutorialというのが用意されているので、これの中身を、C#と比較しながら見ていきたいと思います。追記:Microsoft Visual Studio 2010 First Look Beta 2日本語版も公開されました。
基本
```fsharp open System let int1 = 1 let int2 = int1 + 3 ```
using System;
var int1 = 1;
var int2 = int1 + 3;
```</p>
名前空間の利用の設定と基本的な変数の代入方法。といったところでしょうか。そのまんまだし、別にC#と違いは特にないっぽい。C#ではvar、F#ではlet。どちらも推論が効くのでほとんど同じ。末尾セミコロンはいらないようです。#lightがどうたらこうたら、というのは略。それともう一つ、F#はこのように定義したint1に再代入は出来ません。int1 = 100とすると、比較になります(==ではなく=が比較)。再代入的なint1 <- 1000はコンパイルが通らない。不変(immutable)なのです。C#だとreadonly、はフィールドにしかつけられないので、同じことを再現するのは無理なよう。
Print
---
```fsharp
printfn "peekResult = %d" peekResult
printfn "listC = %A" listC
F# TutorialではPrintは最後にあるのですが、あのですね、出来ればprintは冒頭にしていただきたいです。なんというか、私がF#で一番戸惑ったのが、printfn int1ってのが出来ないことなんですね。いやほら、とりあえずlet int1 = 1って書いたじゃないですか、最初に。で、書いたらとりあえず表示して確認したいでしょ?Console.WriteLineにあたるのはprintfnか、って来るわけです。でも、書いても動かないの。で、まあ、つまるところstring.Format的なものであり書式指定が必要、というところまで行くわけですが、そこで書式って何を書けばいいの?ということになるわけです。"%d"とか予告なく言われても分からないし。もうブチ切れですよ。え、Cの書式指定と一緒だって?いやあ、Cの書式指定も全然覚えてられません、あれあんま良くないと思うんですが……(ついでに言えば私はC#の書式指定も全然覚えてない、必要な度にMSDN見に行ってる)。しかもF#のは書式も色々拡張されてない?より一層分からん!int1の出力で挫折する!
ということなので、もっと頭の方にprintfnのきちんとした解説を載せてくれないと辛いです。ただ、どうしてもアレならConsole.WriteLine int1とでも書けば動く。おお、いきなり.NET Frameworkがそのまま使えることの有難味が(笑) と、冗談はさておき、この「書式指定に何が使えるのか分からない」状態はひっじょーに気持ち悪いので、検索してすぐ分かるような場所に一覧が、欲しい、です。真面目にこれは挫折理由になってます。しょうがないので検索して出てきた Google BooksのExpert F#の解説を見てようやくホッとできた。
%b(bool), %s(string), %d(10進), %f(float), %O (Object.ToString()) それと%A(Any)を覚えておけば問題ない、でしょーか。ほんと予告なく%Aとか言われても困るんですよ、泣きたいですよ。%Oとの違いは、人間が見た時に良い感じに整形してくれるのが%A、でしょうか。文字列は""で囲まれ、配列は展開して出力してくれる。
C#ではcw->TabTab->変数名、といった感じにコードスニペットを活かして手早く記述出来たわけですが、それに比べるとF#は書式指定が必要な時点で、非常にカッタルイ。カッタルイのですが、かわりに、より型に厳格です。printfn "%s" trueとか書くとコンパイル通らない。良し悪し、でしょうか。でも学習用にやってる間は面倒くさいだけですね。どうしても嫌ならば「let p x = printfn "%A" x」とでも定義しておけば良いのでしょうけれど。
gdgd言う前にF C# 言語リファレンスを見ろ、って話なのかもしれない。私は情けないことにここから書式指定を記した部分を見つけられませんでしたが。あと、選択範囲で囲んでAlt+EnterでF# Interactiveに送られるのでそれ見て確認しろって話も少しはありそう。
関数
```fsharp let f x = 2*x*x - 5*x + 3 let result = f (int2 + 4) let rec factorial n = if n=0 then 1 else n * factorial (n-1) ```
Func<int, int> f = x => 2 * x * x - 5 * x + 3;
var result = f(int2 + 4);
Func<int, int> factorial = null;
factorial = n => (n == 0) ? 1 : n * factorial(--n);
```</p>
Tutorialには最大公約数を求めるものもありましたがfactorialと同じなので省略。F#は関数型言語ということで、やっぱ関数ですよね!キーワードはletのままで、ふつーの変数と区別なく定義できる。 C#では汎用デリゲートであるFuncとActionを使うことでそっくり再現できる。C#では型を書いてやらなければならないのだけど、F#ではより強力に推論が効くようで型の明示は不要、のようです。
再帰は、F#はlet recキーワードでそのまま書けるのに対し、C#では一度nullを代入して名前を事前に宣言しておかなければならない。というぐらいで、見た目はほとんど変わらない。そういえばifが式ですね。なのでelseは省略できないようです。else ifの連打はelifで。というわけで、このif式(?)はC#の三項演算子とほとんど同じような感じです。
```fsharp
let add1 x y = x + y
printfn "%A" (add1 1.0 2.0)
printfn "%A" (add1 1 2) // Compile Error
let add2 x y = x + y
printfn "%A" (add2 1 2)
printfn "%A" (add2 1.0 2.0) // Compile Error
少し脱線して型推論の話を。C#の推論は単純なだけ分かりやすくて、これは型書いてやらないといけないな、推論させるための材料を与えてあげないといけないな、というのが結構直感的だったんですが、F#だと強力な分だけ、どう推論されるのか難しい。今は漠然と、全体を見るんだなー、ぐらいにしか分かっていません。例のコードですが、add1はfloat->float->floatで、add2はint->int->intに推論されます。let add1 x y = x + yの時点ではxの型もyの型も分からないけれど、「最初に呼ばれた時に」引数の型は判明する、ということは戻り値の型も判明する。なので、その型で決定する。ということなのかなー、と。この部分はC#と全然違っていて、面白いし強力だなー、と。
Tuple
```fsharp let data = (1, "fred", 3.1415) let Swap (a, b) = (b, a) ```
var data = Tuple.Create(1, "hogehoge");
static Tuple<T2,T1> Swap<T1,T2>(Tuple<T1,T2> tuple)
{
return Tuple.Create(tuple.Item2, tuple.Item1);
}
```</p>
TupleはC#4.0から導入されます。F#は括弧で括るという専用記法があるので簡単に記述出来る。のに対して、C#ではふつーのclassなのでふつーにclassとして使うしかないのが残念。Swapですが、Tupleはimmutable(不変)なので、新しく生成する。だけ。です。temp用意して入れ替えて、などしない。潔く新しく作る。
Boolean, Strings
---
<p>```fsharp
let boolean1 = false
let boolean2 = not boolean1 && (boolean1 || false)
let stringA = "Hello"
let stringB = stringA + " world."
var boolean1 = false;
var boolean2 = !boolean1 && (boolean1 || false);
var stringA = "Hello";
var stringB = stringA + "world.";
```</p>
F#では否定が!ではなくnotなのですね。あとは一緒。
List
---
<p>```fsharp
let listA = [ ]
let listB = [ 1; 2; 3 ]
let listC = 1 :: [2; 3]
let oneToTen = [1..10]
let squaresOfOneToTen = [ for x in 0..10 -> x*x ]
var listA = Enumerable.Empty<object>();
var listB = new[] { 1, 2, 3 }.ToList();
var listC = Enumerable.Repeat(1, 1).Concat(new[] { 2, 3 }).ToList();
var oneToTen = Enumerable.Range(1, 10 - 1 + 1).ToList();
var squaresOfOneToTen = Enumerable.Range(0, 10 - 0 + 1).Select(x => x * x).ToList();
```</p>
リストを扱うとC#と大分差が出てきます。まず第一に、空リストは、C#だと該当するものは作れない。と思う。とりあえずobjectで代替することにしましたが、多分正しくありません。listBはただの整数リストなわけですが、F#だと;で区切るようです。一応、配列とリストは違うということで、C#側のコードはListにしていますがListとも違うので、まあ、気分だけ。listCの::はConsということで、一つの値とリストを連結するものです。C#に該当する関数はありません。しいていえばConcatが近いので、Repeat(value, 1)で長さ1のシーケンスを作って連結、という手を取ることにしました。
F#は[1..10]で最小値-最大値の連続したリストが作れるのですが、これはC#のEnumerable.Rangeとは、違います。Rangeの第二引数は最大値ではなく個数なので。正直言って、個数よりも最大値のほうが使いやすいと思うのだけどなー。というわけで、最大値-最小値+1 = 個数。ということにしています。最後のリスト内包表記は、うん、ええと、私は苦手です。値の動きが右行ったり左行ったりなのが嫌です。Linqのほうが好き。C#でイメージするなら、foreach (var x in [0..10]) yield return x * x; ってとこですかね。
パターンマッチ
---
<p>```fsharp
let rec SumList xs =
match xs with
| [] -> 0
| y::ys -> y + SumList ys
let listD = SumList [1; 2; 3]
Func<IEnumerable<int>, int> SumList = null;
SumList = xs => (!xs.Any())
? 0
: xs.First() + SumList(xs.Skip(1));
var sum1 = SumList(new[] { 1, 2, 3 });
var sum2 = new [] { 1, 2, 3 }.Sum(); // こらこら
```</p>
まず、listDとかF# Tutorialには書いてあるんですが、これintなのでlistじゃないでしょ!紛らわしい。さて、match with | ->という目新しい記述がパターンマッチという奴ですね? 引数のリストxs(リストは通常変数名にxsとかysとかを用いるようです)が空配列の時は0を、そうでない時はyとysに分解して、ysの方は再帰して足し合わせる。ふむぬん。C#に直すとif-else if-else ifの連打。値を返すから、三項演算子のネストですな。という程度の理解しかしていません。三項演算子ネストより綺麗に書けて素敵。という浅すぎる理解しか、今はしていません。まあ、そのうちそのうち。
y::ysという表記ですが、これは配列中の最初のものがy、それ以外がysになります。つまりLinqだとFirst()とSkip(1)ですね。let x::xs = [3..5]とすれば、xが3でxsが4,5になる。警告出ますが。基本はパターンマッチ時用ってことなのかしらん。この辺はちょっと良く分かりません。
C#のほうの、IEnumerableのままSkipをゴロゴロと繋げていくのは実行効率がアレな悪寒。かといってToArrayを毎回使うのもなあ、というわけで上手い落し所が見つからない。QuickSortのように一本の配列に対し、境界の数字を渡していくってのやるとゴチャゴチャするし。あ、でもF#のも結局ysってのはxsとは別の、新しい配列ですよね?C#で表すのならば、xs.Skip(1).ToArray()ということかしらん。だとしたら、この程度の「効率」なんて奴は、気にしたら負けだと思っている。でいいのかもしれない。よくないかもしれない。
配列・コレクション
---
<p>```fsharp
let arr = Array.create 4 "hello"
arr.[1] <- "world"
arr.[3] <- "don"
let arrLength = arr.Length
let front = arr.[0..2]
let lookupTable = dict [ (1, "One"); (2, "Two") ]
let oneString = lookupTable.[1]
var arr = Enumerable.Repeat("hello", 4).ToArray();
arr[1] = "world";
arr[3] = "don";
var arrLength = arr.Length;
var front = new string[3];
Array.Copy(arr, 0, front, 0, 3);
// もしくはSkip->Take. 実行効率は劣りますが、私はこちらの記述方法のほうが好き
var front2 = arr.Skip(0).Take(3).ToArray();
var lookupTable = new Dictionary<int, string> { { 1, "One" }, { 2, "Two" } };
var oneString = lookupTable[1];
```</p>
配列とlistとの違い。listは不変(immutable)で、配列は可変(mutable)ということかしらん。あと配列なら.NET Frameworkのメソッド・プロパティが全部使える。mutableなものへの値の再代入は=ではなく<-で行う。あとは、Array.createは中身がnullな配列ではなく、初期値を指定して全部それで埋めるメソッドのようです。ふむ。あ、最後のslicing notationはいいですね。C#だとArray.Copyを使うのが等しいでしょうけど、記述が冗長すぎてねえ……。どうせ実行時間に対して差は出ないでしょ、と思う場合はLinqでSkip->Takeにしたほうがすっきり書けて良い。あ、あとインデクサは.[]が対応してるようです。ドット。ドット。
辞書の初期化は、タプルを放り投げるだけ。素晴らしい!見た目に分かりやすくスッキリするのがいいです。C#だとコレクション初期化子で近い形にはなりますが、{ {と、全て波括弧で記述するのはどうかなあ、と思うところがあるので。あとは一応、[C# 3.0 における疑似 Map 生成リテラル - NyaRuRuの日記](http://d.hatena.ne.jp/NyaRuRu/20071211/p3)なんてことも出来ますけれど、やりませんものね。
関数(その2)
---
<p>```fsharp
let Square x = x*x
let squares1 = List.map Square [1; 2; 3; 4]
let squares2 = List.map (fun x -> x*x) [1; 2; 3; 4]
let squares3 = [1; 2; 3; 4] |> List.map (fun x -> x*x)
let SumOfSquaresUpTo n =
[1..n]
|> List.map Square
|> List.sum
public static IEnumerable<TR> Map<T, TR>(this Func<T, TR> selector, IEnumerable<T> source)
{
return source.Select(selector);
}
// ↑という拡張メソッドを定義して
Func<int, int> Square = x => x * x;
var squares1 = Map(Square, new[] { 1, 2, 3, 4 });
var squares2 = new Func<int, int>(x => x * x).Map(new[] { 1, 2, 3, 4 });
var squares3 = new[] { 1, 2, 3, 4 }.Select(x => x * x).ToArray();
// もしくは Array.ConvertAll(new[] { 1, 2, 3, 4 }, x => x * x)
Func<int, int> SumOfSquaresUpTo = n =>
Enumerable.Range(1, n - 1)
.Select(i => Square(i))
.Sum();
```</p>
関数が先で、それに適用する配列を渡す、という順序はC#ばかり触ってる身としては、新鮮な印象です。そういえば[Achiral](http://d.hatena.ne.jp/NyaRuRu/20080115/p1)にも同種のオーバーロードが沢山定義されているのですが、私は違和感から、IEnumerable始点のものばかり使っています。あとSelect->ToArrayはArray.ConvertAllで書けるのですが、私はLinqで書くほうが好き。というかArrayの静的メソッドは、基本Obsoleteなぐらいの気持ちでいたりいなかったりする。
ラムダ式は「fun 引数 -> 本体」ですね。C#のほうがキーワードが必要ない分だけすっきりしてガガガ。でもnew Func<型>という不格好なものをつけなければならなかったりする悪夢。var hoge = (int x) => x * xもダメなんですよねえ。理由は、例えば「delegate int Func2(int i);」というのが定義出来るから。引数intで戻り値intだから、Func<int, int>とFunc2は同じ。でも型は違う。なので、見分けがつかず推論できないので、どのデリゲートを使うか、まで指定する必要がある。これは、悲しくウザい話です。ActionとFunc以外のデリゲート型なんて滅びてしまえばいいのに。
「|>」という見慣れない演算子が、パイプライン演算子で、左から右に値を流す。C#だと、Listに対してはLinqで、値に対しては、そういえば前に書いたような……。[neue cc - ver 1.3.0.3 / ちょっとした拡張メソッド群](http://neue.cc/2009/07/16_177.html)のTapの一個目が近い感じでしょーか。いいですよね、こういうの。
Mutable
---
<p>```fsharp
let mutable sum = 0
for i in 0..10 do
sum <- sum + i
while sum < 100 do
sum <- sum + 5
var sum = 0;
foreach (var i in Enumerable.Range(0, 10))
{
sum += i;
}
while (sum < 100)
{
sum += 5;
}
```</p>
最初にF#の値はimmutableだと書きましたが、mutableにしたい時は、mutableキーワードを足せばおk。再代入時は<-演算子を使う、と。C#だとデフォルトがmutableなので、まんまです。そして、このforは、foreachですね。インデントが波括弧代わりなので、doだけどendは要りません。普通のforは「for i = 1 to 10 do」ですが、これならforeachでいいやあ、という気はする。
Types: unions
---
<p>```fsharp
type Expr =
| Num of int
| Add of Expr * Expr
| Mul of Expr * Expr
| Var of string
let rec Evaluate (env:Map<string,int>) exp =
match exp with
| Num n -> n
| Add (x,y) -> Evaluate env x + Evaluate env y
| Mul (x,y) -> Evaluate env x * Evaluate env y
| Var id -> env.[id]
let envA = Map.of_list [ "a",1 ;
"b",2 ;
"c",3 ]
let expT1 = Add(Var "a",Mul(Num 2,Var "b"))
let resT1 = Evaluate envA expT1
```</p>
F# Tutorialですが、ここで途端に説明が無くなって放り出されます。鬼すぎる。今までのわりとゆるふわなところから途端にコレです。意味分からないし。unionsとか言われても分けわからない。と、嘆いていても始まらないので理解するよう頑張ります。そういえば(env:Map<string,int>)も初出なのよね。推論じゃなく明示的に型を与える時は、こうするそうです。型定義がC#とは逆で、コロン後の末尾。違和感がシンドい。ActionScriptなんかも同じで非常にシンドい。
unionはC#だとenumが近いかなー、と思うのですが、enumがintのみなのに対し、F#のunionはそれぞれが別の型を持てる。といった認識。更に値は外から定義可能。というわけでenumとは全然違いますな。むしろ普通にclassに近い。of intで型を定義している(Expr * ExprはTuple)し、値は外から与えているし(コンストラクタのように!) けれど、値は一個。
じゃあclassで作れるかと言ったら、どうだろー。戻り値の型がバラバラになるので、interfaceで一個に纏められるわけでもなく上手いやり方ってあるのかしらん。パターンマッチと同じく、C#には無い概念、と素直にとらえた方が良いかも。一応、interface、じゃなくてダミーに近い型の下にぶら下げて、Evaluateのところでisで派生型を判定して分岐、といった感じでやってみましたが、ゴミですね……。
```csharp
public class Expr
{
// privateにしたいつもり(これは酷い)
public class _Num : Expr
{
public int Value { get; set; }
}
public class _Add : Expr
{
public Expr E1 { get; set; }
public Expr E2 { get; set; }
}
public class _Mul : Expr
{
public Expr E1 { get; set; }
public Expr E2 { get; set; }
}
public class _Var : Expr
{
public string Value { get; set; }
}
private Expr() { }
public static Expr Num(int value)
{
return new _Num { Value = value };
}
public static Expr Add(Expr e1, Expr e2)
{
return new _Add { E1 = e1, E2 = e2 };
}
public static Expr Mul(Expr e1, Expr e2)
{
return new _Mul { E1 = e1, E2 = e2 };
}
public static Expr Var(string value)
{
return new _Var { Value = value };
}
}
static int Evaluate(IDictionary<string, int> env, Expr exp)
{
return // どうしょうもなく酷い
(exp is Expr._Num) ? ((Expr._Num)exp).Value
: (exp is Expr._Add) ? Evaluate(env, ((Expr._Add)exp).E1) + Evaluate(env, ((Expr._Add)exp).E2)
: (exp is Expr._Mul) ? Evaluate(env, ((Expr._Mul)exp).E1) + Evaluate(env, ((Expr._Mul)exp).E2)
: (exp is Expr._Var) ? env[((Expr._Var)exp).Value]
: 0;
}
static void Main(string[] args)
{
var envA = new Dictionary<string, int> { { "a", 1 }, { "b", 2 }, { "c", 3 } };
var expT1 = Expr.Add(Expr.Var("a"), Expr.Mul(Expr.Num(2), Expr.Var("b")));
var resT1 = Evaluate(envA, expT1);
Console.WriteLine(resT1); // 確認
}
見なかったことにしてください。私の脳みそなんてこんなもんです。
Types: records
```fsharp type Card = { Name : string; Phone : string; Ok : bool } let cardA = { Name = "Alf" ; Phone = "(206) 555-8257" ; Ok = false } let cardB = { cardA with Phone = "(206) 555-4112"; Ok = true } let ShowCard c = c.Name + " Phone: " + c.Phone + (if not c.Ok then " (unchecked)" else "") ```
class Card
{
public string Name { get; set; }
public string Phone { get; set; }
public bool Ok { get; set; }
public Card() { }
public Card(Card with)
{
// structならthis=withで一発なのですが
// F#のrecordはstructじゃないとのことなので
this.Name = with.Name;
this.Phone = with.Phone;
this.Ok = with.Ok;
}
}
var cardA = new Card { Name = "Alf", Phone = "(206) 555-8257", Ok = false };
var cardB = new Card(cardA) { Phone = "(206) 555-4112", Ok = true };
Func<Card, string> ShowCard = c =>
c.Name + " Phone: " + c.Phone + (!c.Ok ? " (unchecked)" : "");
```</p>
こちらは割とすんなりと何なのか分かる。withでコピーが作れているところが面白い。ふーむ、C#だとむしろ匿名型のほうが近い感じに見えるかもしれない。
Types: classes
---
<p>```fsharp
type Vector2D(dx:float, dy:float) =
let length = sqrt(dx*dx + dy*dy)
member v.DX = dx
member v.DY = dy
member v.Length = length
member v.Scale(k) = Vector2D(k*dx, k*dy)
class Vector2D
{
public float DX { get; private set; }
public float DY { get; private set; }
public float Length { get; private set; }
public Func<int, Vector2D> Scale { get; private set; }
public Vector2D(float dx, float dy)
{
var length = (float)Math.Sqrt(dx * dx + dy * dy);
this.DX = dx;
this.DY = dy;
this.Length = length;
this.Scale = new Func<int, Vector2D>(k => new Vector2D(k * dx, k * dy));
}
}
```</p>
コンストラクタと定義が一体化していて、随分とシンプルに記述出来るようです。JavaScriptっぽい、なんて思ってしまったりして。C#で再現するとプロパティでメソッドかいな、という違和感があったりなかったり。private変数で蓄える必要がないから、定義が楽といえば楽。ところで思うのは、F#のv.DXとかの、vって何処から来てるの……? これ、別にhogehogeにしてもaaaaaaにしても動くので、何でもいいみたいですが……。
Types: interfaces
----
<p>```fsharp
type IPeekPoke =
abstract Peek: unit -> int
abstract Poke: int -> unit
type Widget(initialState:int) =
let mutable state = initialState
interface IPeekPoke with
member x.Poke(n) = state <- state + n
member x.Peek() = state
member x.HasBeenPoked = (state <> 0)
let widget = Widget(12) :> IPeekPoke
widget.Poke(4)
let peekResult = widget.Peek()
interface IPeekPoke
{
int Peek();
void Poke(int n);
}
class Widget : IPeekPoke
{
private int state;
public bool HasBeenPoked { get { return state != 0; } }
public Widget(int initialState)
{
state = initialState;
}
public int Peek()
{
return state;
}
public void Poke(int n)
{
state = state + n;
}
}
static void Main(string[] args)
{
var widget = (IPeekPoke)new Widget(12);
widget.Poke(4);
var peekResult = widget.Peek();
}
```</p>
interfaceはabstractな型定義を並べる。ということらしい。定義方法は「メソッド名:引数->引数->戻り値」ですねん。unitはC#でいうところのvoidみたいなもの。で、interfaceの実装は、そのまま中に記述してしまえばいいらしい。これは楽ちん。見慣れない「:>」はキャストの記号。とても、カッコイイです……。
結論
---
以上、複数回に分けようかとも思ったのですが一気にやってみました。最初F# Tutorialを開いて、少な!こんなんでチュートリアルになってるの?と思ったのですが、意外とギッシリ詰まってた感じです。しっかりチュートリアルになってました。ただ、やっぱチュートリアルなのでこれを覚えたぐらいじゃF#凄い!F#嬉しい!的にはなりません(比較対象がC#2.0だとなったかもしれませんが)でした。日常的に使って、手に馴染ませないと、良さの理解まではいけなさそうです。
あとまあ、やっぱほとんど説明のない、このTutorialのコードだけじゃ適当な理解になってそうで怖い。きちんと時間割いてMSDN見るなりしないと……。ただ、今のとこがっつし覚えよう!と思えてないところはある。本音として、C#でいいぢゃん、と思っているところがかなりあります。これがJava->Scalaの関係だったら違ったかもしれないんですが、うーん。まあ、あとVisualStudioの補完具合とかかな。IntelliSenseに乗ってゴリゴリ書けるような感触がF#にはないので。別に補完効いてないってわけじゃあないのですけど。
C#でTwitterのStreaming APIを使ってリスト自動追加
- 2009-11-05
XboxInfoTwitの認証数は現在450を越えて、近いうちに500には届きそうです。現在の実装はIEを裏で動かすという、しょーもないものになっていて、それに起因する不具合や、どうしょうもない点が幾つかあるため、クローラー部分は全面的に書き変えようと思っています。あと、エラーメッセージがド不親切とか至らない点だらけでした、すみません。そんな次期バージョンの作業は全然捗ってないのですが、せめて年内ぐらいには何とかしたいです。
@のお話
ゲーム名に@が含まれるものをポストする(例えばTHE IDOLM@STER)と、STERさんに@が飛んで迷惑。というお話を見たので検証してみました。@は行頭かスペース + @ + 数字/アルファベットのものがあると飛びます。つまり、@の前にアルファベットがあれば@は飛びません。なので、別にIDOLM@STERだからってSTERさんに@が飛びまくる、なんてことはありません。正規表現で表すと「(?<=^| )@[a-zA-Z0-9_]+」になります。ついでに、ハッシュタグのほうも軽く検証してみました。基本的には@と同じですが、英単語以外にもリンクが張られるようなので、正規表現は「(?<=^| )#[^ ]+」になるようです。
List
Twitterにリストが実装されました。そこで、XboxInfoTwitユーザーのリストを作ってみることにしました。手動で探して登録も大変なので、プログラムでクロールして追加していきましょう。パブリックイタイムラインからXboxInfoTwit利用者(Source=XboxInfoTwit)の人を片っ端からリスト登録するという方針で行きます。以下、C#でのTwitterストリーミングAPIの使用法と実際のコードになります。同じようなことをやりたい人は、適当に書き替えてどうぞ使ってください。突っ込みどころ多数なのでむしろ突っ込んで欲しい……。
2010/4/29 追記:このコードはストリームAPIの利用法にしては冗長すぎるので、書き直しました → neue cc - C#とLinq to JsonとTwitterのChirpUserStreamsとReactive Extensions ストリームAPI取得コードを参考にする場合は、新しいほうを見てください。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml.Linq;
using System.Collections.Specialized;
using System.Threading;
using System.Xml;
static class Program
{
// ザ・決めうち文字列s
const string UserName = "neuecc"; // 自分のアカウントのユーザー名を
const string Password = "password"; // 同じくパスワードを
const string ListName = "xboxinfotwitusers"; // リストを、入力ですです
const string StreamApi = "http://stream.twitter.com/1/statuses/sample.xml";
const string ListMembersApiFormat = "http://twitter.com/{0}/{1}/members.xml";
/// <summary>指定リストにメンバーを追加する</summary>
static void AddMemberToList(string userName, string listName, int id)
{
var url = string.Format(ListMembersApiFormat, userName, listName);
var wc = new WebClient { Credentials = new NetworkCredential(UserName, Password) };
wc.UploadValues(url, new NameValueCollection() { { "id", id.ToString() } });
}
/// <summary>指定リストのメンバーIDを全て取得する</summary>
static IEnumerable<int> EnumerateListMemberID(string userName, string listName)
{
var format = string.Format(ListMembersApiFormat, userName, listName) + "?cursor={0}";
var cursor = -1L;
var xmlReaderSettings = new XmlReaderSettings
{
XmlResolver = new XmlUrlResolver { Credentials = new NetworkCredential(UserName, Password) }
};
while (true)
{
using (var xr = XmlReader.Create(string.Format(format, cursor), xmlReaderSettings))
{
var xEle = XElement.Load(xr);
foreach (var item in xEle.Descendants("user").Select(x => (int)x.Element("id")))
{
yield return item;
}
cursor = long.Parse(xEle.Element("next_cursor").Value);
if (cursor == 0) yield break;
}
}
}
/// <summary>ストリームAPIのパブリックタイムラインから無限に取得</summary>
static IEnumerable<XElement> EnumeratePublicTimeline(StreamReader reader)
{
while (true)
{
var xmlString = reader.EnumerateLines()
.TakeWhile(s => s != "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
.Join();
if (xmlString == "") continue;
yield return XElement.Parse(xmlString);
}
}
/// <summary>サーバーが死んでないか確認</summary>
static bool IsServerStatusOK()
{
var req = WebRequest.Create("http://twitter.com/help/test.xml");
HttpWebResponse res = null;
try
{
res = (HttpWebResponse)req.GetResponse();
if (res.StatusCode == HttpStatusCode.OK) return true;
}
catch (WebException e) { Console.WriteLine(e); }
finally { if (res != null) res.Close(); } // どうでもいいと思っていたり
return false;
}
static void Main(string[] args)
{
ServicePointManager.Expect100Continue = false; // おまじない(笑)
var count = 0; // モニタリング用のカウント変数(動作的には別に使わない)
var following = new HashSet<int>(EnumerateListMemberID(UserName, ListName));
var webRequest = (HttpWebRequest)HttpWebRequest.Create(StreamApi);
webRequest.KeepAlive = true;
webRequest.Credentials = new NetworkCredential(UserName, Password);
LOOP:
using (var res = webRequest.GetResponse())
using (var stream = res.GetResponseStream())
using (var reader = new StreamReader(stream))
{
try
{
// 例外が発生しなければ、無限リピートになっているのでこの部分を永久に続けます
EnumeratePublicTimeline(reader)
.Do(_ => { if (++count % 100 == 0) Console.WriteLine("{0} : {1}", DateTime.Now, count); }) // 確認表示用
.Where(x => x.Name == "status")
.Select(x => new
{
Source = x.Element("source").Value,
ID = (int)x.Element("user").Element("id"),
Name = x.Element("user").Element("screen_name").Value
})
.Where(a => a.Source.Contains("XboxInfoTwit"))
.Do(a => Console.WriteLine("Found:{0}", a.Name)) // ここでも確認表示用
.Where(a => following.Add(a.ID))
.ForEach(a =>
{
AddMemberToList(UserName, ListName, a.ID);
Console.WriteLine("{0} : {1} : {2}", a.Name, DateTime.Now, count); // 確認表示用
});
}
catch (IOException e)
{
Console.WriteLine(e); // 接続が閉じられてたりするのでー。
while (!IsServerStatusOK())
{
Thread.Sleep(TimeSpan.FromMinutes(5)); // サーバー死んでたら5分間お休み
}
}
finally
{
webRequest.Abort(); // これ呼ぶ前にCloseするとハング
}
}
goto LOOP; // goto! goto!
}
// Extension Methods
public static IEnumerable<string> EnumerateLines(this StreamReader streamReader)
{
while (!streamReader.EndOfStream)
{
yield return streamReader.ReadLine();
}
}
public static string Join<T>(this IEnumerable<T> source)
{
return source.Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();
}
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
}
}
public static IEnumerable<T> Do<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
{
action(item);
yield return item;
}
}
}
ストリーミングAPIとは無関係のリスト関連の処理や、投げやりなtry-catch-gotoがあって、ちょっとゴチャゴチャしてますが、基本的にはusing三段重ねの部分だけです。無限にXMLが継ぎ足されてくるので、接続を切らさずひたすらReadLine。XMLの切れ目は、XML宣言部を使うことにしましたが、今一つスマートではないです。文字列にしてParseってのもあまり良い感じじゃなく。あ、あと例外処理は全然出来てませんので何かあると平然と死にます。
コードは、書き捨て感全開。例によって何でもLinq、何でもIEnumerable。コレクションになりそうな気配があると、すぐにじゃあyieldね、と考える癖がついてしまっていて。細かいことは後段に任せればいーんだよ、というのが楽ちんでして。リストメンバー全件取得の部分なんかは、わりとスマートに書けてるかと思うのですがどうでしょう。
なお、このストリーミングAPIは全件を漏れなく取得出来るわけではないので、それなり、というかかなり漏れが出ます。なのでXboxInfoTwit使ってるのに登録されねーぞ、という場合は、しょーがない。です。そのうち登録されると思います。あと、このプログラムはサーバー上で24時間動かしているわけじゃなく、私のローカルPC上で動かしているだけなので、私の気まぐれで動かしてたり動かしてなかったりします。私が寝てる間はPCがウルサイので動いてませんし、私が家に居ない時は省エネのために動いてません。なので、むしろ登録されるほうが珍しいです。レアです。効率的には20000件に1人登録出来るか出来ないか、って感じでした。一時間に一人見つかるかどうかも怪しいぐらいの頻度。とてもレア。ぶっちゃけgoogle経由で引っ張ってくるとかしたほうが遙かに効率良さそうですが、まあ、Streaming API使ってみたかったというだけなので。
そういえばですが、逆にリストに登録されてUZEEEE、という場合は、現状はリスト機能がベータのせいなのか拒否は出来ないようです。すみません。UZEEEE、と思っても我慢してください。どうしても嫌な場合は私の方にメッセージをくれれば、リストからの撤去と、プログラムから以後の追加をしないようなコードを入れたいと思っています。
Replace, Intersperse, Init
- 2009-11-01
static void Main(string[] args)
{
// 1,2,3,4,5,6,7,8,9,10
var source = Enumerable.Range(1, 10);
// 偶数を-1に置換する
var replace = source.Replace(i => i % 2 == 0, -1);
// 値を挟み込む(1,100,2,100,...9,100,10)
var intersperse = source.Intersperse(100);
// 末尾一個を省く(1..9)
var init1 = source.Init();
// 末尾三個を省く(1..7)
var init2 = source.Init(3);
}
public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, Func<T, bool> predicate, T replacement)
{
foreach (var item in source)
{
if (predicate(item)) yield return replacement;
else yield return item;
}
}
public static IEnumerable<T> Intersperse<T>(this IEnumerable<T> source, T value)
{
var isFirst = true;
foreach (var item in source)
{
if (!isFirst) yield return value;
yield return item;
isFirst = false;
}
}
public static IEnumerable<T> Init<T>(this IEnumerable<T> source)
{
return source.Init(1);
}
public static IEnumerable<T> Init<T>(this IEnumerable<T> source, int count)
{
if (count == 0)
{
foreach (var item in source) yield return item;
yield break;
}
var q = new Queue<T>(count);
foreach (var item in source)
{
if (q.Count == count) yield return q.Dequeue();
q.Enqueue(item);
}
}
という拡張メソッド。もういい加減いつになるのか分からなくなってしまって悲しいlinq.jsの次のリリースにはこれらを入れます。IntersperseとInitの元ネタはMono.Rocksから。それの更に元はHaskellのようですねん。
Initは何度となく欲しい!と思ったシーンがあるので、きっと便利。長さが不定で前から後ろに走るLinqでは、後ろから幾つ、というのは標準では出来ないんですね。ReverseしてSkipしてReverseするか、一度ToArrayしてから切り出したりしか手がなくて。最後の一個だけ省きたいとか、よくあります。しかしInitって関数名は意味不明で少々アレかも。Mono.RocksではExceptLastに改称されていました。そうですねえ、CarとかCdrを引き摺る必要はないように、ExceptLastのほうが良さそうですね。
実のところInitがあればIntersperseも標準Linq演算子で定義出来ます。
var intersperse = source
.SelectMany(i => Enumerable.Repeat(i, 1).Concat(Enumerable.Repeat(100, 1)))
.Init();
Initの便利さが分かる。そう、こういうのやると、どうしても末尾に一個ゴミが付いてきちゃて、それをスマートに除去するのは出来ないのですよね。あって良かったInit。そしてSelectManyの万能さは異常。Repeat(value, 1)とかRepeat(value, int.MaxValue)も超多用。記述があまりにも冗長になって泣けますが。
RxFrameworkにはObservable.Return(value)という、Repeat(value, 1)と同様のものが定義されていたりします。それとObservable.Cons(value,IObservable)というConcatの単体バージョンみたいなものもあります(lispのconsと同じイメージです)。だから、上のをRxでやるならば
var intersperse = Observable.Range(1, 10)
.SelectMany(i => Observable.Cons(i, Observable.Return(100)))
.ToEnumerable();
.Init();
となります。あまりスッキリしてない? まあ、そうかも。でも、決め打ちの1って書くの嫌なものなので、それが省けるってのは嬉しいものです。定数を使うなら(int)decimal.Oneという手もありますが、まあ、馬鹿らしい。私はstring.Emptyよりも""を使う派なので、それはちょっとありえない。ちなみに、""を選ぶ理由は、タイプ数が少ないという他に、文字列であることが色分けされて表示されるため、string.Emptyよりも遥かに視認性が良いからです。こういうのはIDEを含めて考えないとね。パフォーマンス云々の話は些細なことなので個人的にはどうでもいい。
Rxの記事は、細かいネタは溜まっているので、近いうちにまた書きたいと思います。VS2010 Beta2ではIObservableが標準搭載されていますし。
AnonymousComparer - lambda compare selector for Linq
- 2009-10-29
class MyClass
{
public int MyProperty { get; set; }
}
static void Main()
{
// 例として、こんな配列があったとします
var mc1 = new MyClass { MyProperty = 3 };
var mc2 = new MyClass { MyProperty = 3 };
var array = new[] { mc1, mc2 };
// Distinctは重複を取り除く。でも結果として、これは、2です。
var result = array.Distinct().Count();
// 参照の比較なので当然です。では、MyPropertyの値で比較したかったら?
// DistinctにはIEqualityComparerインスタンスを受け付けるオーバーロードもあります
// しかしIEqualityComparerはわざわざ実装したクラスを作らないと使えない
// そこで、キー比較のための匿名Comparerを作りました。
// ラムダ式を渡すことで、その場だけで使うキー比較のIEqualityComparerが作れます。
array.Distinct(AnonymousComparer.Create((MyClass mc) => mc.MyProperty));
// でも、長いし、型推論が効かないから型を書く必要がある
// Linqに流れているものが匿名型だったりしたら対応できないよ!
// というわけで、本来のLinqメソッドのオーバーロードとして、記述出来るようにしました
// ちゃんと全てのIEqualityComparerを実装しているLinq標準演算子に定義してあります
array.Distinct(mc => mc.MyProperty);
// 短いし、型推論もちゃんと効くしで素晴らしいー。
// 匿名型でもいけます(VBの匿名型はC#(全ての値が一致)と違ってKey指定らしいですね)
var anonymous = new[]
{
new { Foo = "A", Key = 10 },
new { Foo = "B", Key = 15 }
};
// true
anonymous.Contains(new { Foo = "dummy", Key = 10 }, a => a.Key);
}
と、いう内容のコードをCodePlexで公開しました。LinqのIEqualityComparerって使いにくいよね、を何とかするためのものです。DLLでも何でもなく、ただの100行のコードなのでコピペで使ってくださいな。メソッドはAnonymousComparer.Createしかありません。newを使わせないのは型推論のためです。メソッド経由なら引数の型を書くだけで済み、戻り値の型を書く手間が省けるので……。あとはLinq標準演算子でIEqualityComparerを使うオーバーロードの全てに、キー比較用ラムダ式を受けるオーバーロードが追加されています。使い方、使い道は、まあ、見た通りです。
わざわざzipをダウンロードするのも面倒、という人はCodePlexのソース直接表示でどーぞ。どうせ.txtと.csしか入ってないので。でもダウンロード数とかが増えてると少し嬉しいですね。linq.jsもようやく50超えましたよ、あまりの少なさに笑えない。
以前にも同様のものを書いてた LinqとIEqualityComparerへの疑問 のですが、今回やっと重い腰を上げてまとめてみました。GroupJoinのオーバーロードとか手書きだと死ぬほどダルいですからねえ。と、いっても、やっぱ手書きでやってたら洒落にならないほど面倒くさいので、機械生成でサッと作りました。全然サッとしてないんですけどね。むしろ泥臭い。Linqネタなのでワンライナーで強引に仕上げてみましたよ!
static string data = @"ここに定義へ移動で出てくるEnumerableのデータを貼り付けてね、と(4000行ぐらい)";
static void Main(string[] args)
{
var result = data
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => Regex.IsMatch(s, "public static.+IEqualityComparer"))
.Select(s => Regex.Replace(s, @"(<.+)(>\(this)", "$1,TCompareKey$2"))
.Select(s => Regex.Replace(s, @"IEqualityComparer<(.+?)>", "Func<$1,TCompareKey>"))
.Select(s => Regex.Replace(s, @"comparer", "compareKeySelector"))
.Select(s => s.Trim(' ', ';'))
.Select(s => new { Signature = s, Groups = Regex.Match(s, @"^(.+? ){3}(?<method>[^ ]+?)<.+?>\(this (?<args>.+)\)$").Groups })
.Select(a => new
{
a.Signature,
MethodName = a.Groups["method"].Value,
Args = a.Groups["args"].Value
.Split(new[] { ", " }, StringSplitOptions.None)
.Select(s => s.Split(' ')).Where(ar => ar.Length == 2).Select(ar => ar.Last())
})
.Select(a => string.Format("{1} {0} {{ return {2}.{3}({4}{5}); {0}}}{0}{0}",
Environment.NewLine,
a.Signature,
a.Args.First(),
a.MethodName,
string.Join(",", a.Args.Skip(1).TakeWhile(s => s != "compareKeySelector").ToArray()),
(a.Args.Count() == 2 ? "" : ",") + "AnonymousComparer.Create(compareKeySelector)"))
.Aggregate(new StringBuilder(), (sb, s) => sb.Append(s))
.ToString();
}
string dataのところにEnumerable.Rangeなんかを右クリックして「定義へ移動」で出てくるメタデータから、のものを全部コピーしてペースト。あとは、それをLinqでゴリゴリ加工すれば出来上がり。です。Select7連打は悪ノリですね。別にRegexの部分は.Replaceを繋げればいいのにね。あと、かなり決めうち成分強めなのと、正規表現が苦手であんまり上手く書けてないところが多かったりとで全く褒められたコードではありません。正規表現は本当に何とかしたいなあ……。
ああ、あと英語が酷い(笑) CodePlexのちょっとしたプロジェクト説明みたいな部分だけですら破綻しまくってる、単語すら繋げられない、これは酷い。
そういえば作ってから気づいたんですが、普通にリフレクションで取得した方が……遙かに……楽!綺麗に……仕上がる! と、気づいてしまったのだけど気づかなかったことにしようそうしよう。
return IEnumerableとyield return
- 2009-10-08
static void Main(string[] args)
{
var path = @"C:\test.txt";
var lines = EnumerateAllLines(path).ToArray();
}
static IEnumerable<string> EnumerateAllLines(string filePath)
{
using (var sr = new StreamReader(filePath))
{
return Enumerable.Repeat(sr, int.MaxValue)
.TakeWhile(s => !s.EndOfStream)
.Select(s => s.ReadLine());
}
}
これの実行結果はどうなるでしょうか。答えは、「閉じているTextReaderから読み取ることはできません。」という例外が発生します。当たり前ですか? すみません。Linqばかり触っていると、ついついIEnumerableだから遅延評価だね!と単刀直入に思ってしまっていたりしたのですが、IEnumerableは決して必ずしも遅延評価であるということでは、ない。配列だってIEnumerableなんだよ!という。当然のようなことですが、すっかり頭から抜け落ちていました。反省。
何で例外が発生するかと言えば、EnumerateAllLines(path)の時点でメソッドが呼ばれ、returnで返した時点でusingを抜けてストリームが閉じられてしまう。ので、ToArray()で閉じられたストリームに対して読み込みを始めて、南無。というわけです。ではどうすればいいかというと……
static IEnumerable<string> EnumerateAllLines(string filePath)
{
using (var streamReader = new StreamReader(filePath))
{
var seq = Enumerable.Repeat(streamReader, int.MaxValue)
.TakeWhile(sr => !sr.EndOfStream)
.Select(sr => sr.ReadLine());
foreach (var item in seq) yield return item;
}
}
yield returnを使ってやれば、コンパイラがイテレータを作るので、遅延実行される。EnumerateAllLines(path)の時点ではメソッド内部は一切通らない。MoveNextが呼ばれて初めてusingを通り、列挙が終わるかDisposeが呼ばれるまではusingを抜けない。という、なって欲しいであろう挙動を取ってくれるわけです。実行ファイルをReflectorで見ると、復元不可能なぐらいグチャグチャなものが出力されていて、あまりの難読化っぷりにビビりますが気にしないことにしませう。
そもそもEnumerable.Repeat(sr, int.MaxValue)のほうを改善してRepeatWithUsing作った方がいい、のではあるのですけど、まあ、それはそれということで。
無限リピート + SQL
上のはただの説明用の例でクソの役にもたたないので、もう少し実用的なものを一つ。
static void Main(string[] args)
{
var command = new SqlCommand();
command.CommandText = @"select hogehogehoge";
var result = command.EnumerateAll(dr => new
{
AA = dr.GetString(0),
BB = dr.GetInt32(1)
});
}
static IEnumerable<T> EnumerateAll<T>(this IDbCommand command, Func<IDataReader, T> selector)
{
using (var reader = command.ExecuteReader())
{
var seq = Enumerable.Repeat(reader, int.MaxValue)
.TakeWhile(dr => dr.Read())
.Select(selector);
foreach (var item in seq) yield return item;
}
}
static T[] ReadAll<T>(this IDbCommand command, Func<IDataReader, T> selector)
{
return command.EnumerateAll(selector).ToArray();
}
シーケンス無限リピートをSQLの読み込みに応用してみるとかどうでしょう。Linq to Sqlのように、とまでは到底行きませんが、匿名型も使えるし、何となくそれっぽい雰囲気は出てるんじゃないかしらん。EnumerateAllの後段にTakeWhileを付けて条件で途中で止めるとか、Take(10)で10件のみ取得とか、それなりに自由に動かせます。
ラムダ式の引数の名前
- 2009-10-05
Linqもそうですが、Linq意外でもFuncやActionのお陰様でラムダ式を渡しまくりな昨今。そうなるとちょろっとした引数の名前付けが面倒くさかったりします。ので、大抵は一文字で済ませるわけです。一文字ってどうなのよ、よくないんじゃないの?というと、ラムダ式のような極端にスコープが短いものは、しっかりした名前がついているもののほうが逆に見づらかったりするので、一文字でいいと思います。面倒くさいからって全て_だの__だので済ませると見づらいので一文字はなるべく死守します。但し引数を利用しない場合は_を使います。ネストしたり、式じゃなくて文になる場合はしっかりした名前を付けます。
a // AnonymousType
i // int
s // string
c // char
d // datetime
g // IGrouping
t // Type
e(ex) // exception
e(elem) // XElement
e(ev) // event
e(ie) // IEnumerable(あんま使わないですが)
ar // array(IEnumerableの時もこれ使ったりする(適当))
ar // IAsyncResult(うわ、被っとる)
fs // fileSystemとかラクダの頭文字を繋げる
x // 適当なのがない場合とか、x => xとか
xs // arrayやらIEnumerableやらどっちでもいー、とヤケクソな時
_ // 引数を式中で利用しない場合
大抵は型の頭文字一つです。AnonymousTypeは、前はtを使ってたんですが、IntelliSenseで出てくるのが'aだし、世間的にもaが主流だしTypeと紛らわしいので、最近はaで書くようにしています。Aggregateなんかは(a,b)=>って昔は書いていたのですが、aが匿名型なので紛らわしいから、(x,y)=>と書くようにしています。徹底はしてません。適当です。
それにしてもeの被りっぷりは酷い。こうなると、不人気頭文字を使いたい。というわけで、ライブラリからTypeの頭文字を取得してみる。アセンブリの取得部分は2009-08-17 - 当面C#と.NETな記録のコードをお借りしました。
// asmNamesは http://d.hatena.ne.jp/siokoshou/20090817#p1 から
// Groupingをバラしているのは、デバッガで中身を見る時にかったるかったから
var list = asmNames
.SelectMany(s => Assembly.LoadWithPartialName(s).GetExportedTypes())
.Select(t => t.Name)
.GroupBy(s => s.First())
.OrderBy(g => g.Key)
.Select(g => new { Key = g.Key, Types = g.ToArray() })
.ToList();
list.ForEach(a => Console.WriteLine("{0} : {1}", a.Key, a.Types.Length));
A : 319
B : 252
C : 773
D : 715
E : 275
F : 263
G : 172
H : 264
I : 887
J : 14
K : 58
L : 249
M : 424
N : 140
O : 189
P : 526
Q : 31
R : 312
S : 1039
T : 644
U : 169
V : 118
W : 272
X : 372
Z : 7
_ : 32
IはInterfaceだから数が多いのはしょうがない。Zは当然としてJ, K, Qは不人気ワードなので狙いどころですね!そしてSは人気あり過ぎ、と。別に考察でも何でもないです、すみません。CamelCaseを短縮結合して、一文字の場合のみを取り上げたら
// SplitCamelWordはasmNamesと同じくsiokoshouさんのコードから
.Select(t => SplitCamelWord(t.Name).Aggregate("", (x, y) => x + y.First(), s => s.ToLower()))
.Where(s => s.Length == 1)
ちゃんと(?)、eが一番人気でした。
C# Linqでクイックソート
- 2009-09-30
qsort [] = []
qsort (x:xs) = qsort elts_lt_x ++ [x] ++ qsort elts_greq_x
where
elts_lt_x = [y | y <- xs, y < x]
elts_greq_x = [y | y <- xs, y >= x]
これはHaskellのコードで、良く見かける定番のQuickSort。うん、短い。というわけでLinqでそれをやる。というネタは既出のン番煎じなのですが、気にせずやる。
// LinqでHaskell風のクイックソート
public static IEnumerable<T> QuickSort<T>(IEnumerable<T> source)
where T : IComparable<T>
{
if (!source.Any()) return source;
var pivot = source.First();
return source
.GroupBy(x => x.CompareTo(pivot))
.OrderBy(g => g.Key) // OrderBy使うのはどうかなー、というところはある
.SelectMany(g => (g.Key == 0) ? g : QuickSort(g));
}
GroupBy->SelectManyと流れるように書けて美しいー。これならHaskellにも引けを取らないですね!但し問題なのは、OrderByを使用しているところ。CompareToの結果である-1, 0, 1を並べ替えるために使っているのだけど、OrderByの中身はシュワルツ変換の挟まったクイックソートそのものなので邪道な感は否めない。OrderBy使うなら、そもそもsource.OrderBy(x => x)でもいいぢゃん、って話になってしまう。
// 非GroupBy版。LookupはGroupByの即時評価版と考えていい。
public static IEnumerable<T> QuickSort<T>(IEnumerable<T> source)
where T : IComparable<T>
{
if (!source.Any()) return source;
var pivot = source.First();
var lookup = source.ToLookup(x => x.CompareTo(pivot));
return QuickSort(lookup[-1]).Concat(lookup[0]).Concat(QuickSort(lookup[1]));
}
GroupByをToLookupに書き変えました。ToLookupはGroupByの即時評価版といった感じで、インデクサによるアクセスが可能。インデクサ使わないでそのままforeachで列挙する、なんて時はGroupByを使った方が良いです。今回はインデクサで順番を明示的に指定するため、ToLookupでパーティション切って、Concatで繋いでやれば出来あがり。やってることが非常に分かりやすくて良い。ソースの見た目も中々綺麗じゃないでしょうか。Haskellのものとも非常に近いです(ようするに++がConcatなので) ToLookupではなくWhereを使えば、見た目は更にHaskellに近づきますが、列挙が二回になるので、ここはToLookupで。
// 普通に?書いた場合。
public static void QuickSort<T>(IList<T> source, int lowerBound, int upperBound)
where T : IComparable<T>
{
var pivot = source[lowerBound + ((upperBound - lowerBound) >> 1)];
var left = lowerBound - 1;
var right = upperBound + 1;
while (true)
{
while (source[++left].CompareTo(pivot) < 0) ;
while (source[--right].CompareTo(pivot) > 0) ;
if (left >= right) break;
var temp = source[left];
source[left] = source[right];
source[right] = temp;
}
if (lowerBound < left - 1) QuickSort(source, lowerBound, left - 1);
if (right + 1 < upperBound) QuickSort(source, right + 1, upperBound);
}
今度は非Linqに一般的?な書き方で。あまりヘタな書き方するとアレだなあ、と思ったので404 Blog Not Found:javascript - Array#sortはオレquicksortより遅い by ChromeのコードをC#に移植しました。あたしこの書き方嫌いなのよね、という感じにゴチャゴチャした印象は否めないというか、まあ、嫌いなのよね。一本の配列で頑張るところが、ゆとりな私としてはしんどい。ビットシフトも嫌よね。
public static IEnumerable<T> QuickSort<T>(IEnumerable<T> source)
where T : IComparable<T>
{
var enumerator = source.GetEnumerator();
if (!enumerator.MoveNext()) yield break;
var pivot = enumerator.Current;
var less = new List<T>();
var equal = new List<T>();
var greater = new List<T>();
do
{
switch (enumerator.Current.CompareTo(pivot))
{
case -1: less.Add(enumerator.Current); break;
case 0: equal.Add(enumerator.Current); break;
case 1: greater.Add(enumerator.Current); break;
}
} while (enumerator.MoveNext());
foreach (var item in QuickSort(less)) yield return item;
foreach (var item in equal) yield return item;
foreach (var item in QuickSort(greater)) yield return item;
}
最後に、非Linqで、ToLookup版を再現してみたものを。do-whileがToLookupでforeachの連発がConcat。ようするに富豪的にListを作りまくるってわけなんですね!書きやすいし分かりやすいので、一本配列版よりも遥かに好き度高い。現代人はListを贅沢に大量に好きなだけ使うのです。まあ、Linq版がない状態でこの書き方が浮かぶ or 実行に移せるかどうかといったら、かなり無理ですけど。
.NET Reactive Framework メソッド探訪第五回:Scan
- 2009-09-28
var random = new Random();
Func<byte> nextByte = () => (byte)random.Next(0, byte.MaxValue + 1);
// CenterEllipseは真ん中の円のこと。
// 1000秒以内に3回クリックするとランダムで色が変わる
// GetMultiClickScan関数が今回の主題で、解説は後で。
CenterEllipse.GetMultiClickScan(3, 1000).Subscribe(() =>
CenterEllipse.Fill = new SolidColorBrush(Color.FromArgb(255, nextByte(), nextByte(), nextByte())));
円を1秒以内にトリプルクリックすると色が変わります。右側のログ表示はミリセカンド単位でトリプルクリック時の経過時間を表しています。つまり、これが1000以内ならば色が変わり、1000より上ならば色は変わりません。このサンプルのテーマは、トリプルクリックの検出です。トリプルだけではなく、クアドラプルでもクインタプルでも、1秒以内じゃなくて5秒でも10秒でも応用できる形で書くとしたら、どう書く? グローバル変数に、クリックイベントを格納する配列でも置くかしらん。それがきっと簡単で分かりやすくて、でも……。
今回はRx Frameworkの関数から、Scanを取り上げます。この例題は私が考えたわけではなく、Ramblings of a Lazy Coder : My solutions to the Reactive Framework Tripple-click puzzleという問題からです。URLリンク先は解答編、というわけで、この解答を解説します。
考え方としては、クリックイベントに「クリックされた時間」という情報を付加。更に、クリック回数分の「前のデータ」を参照して指定時間内で連続クリックかどうかを確認する。時間情報の付加は匿名型を作るだけなので簡単ですが、「前のデータ」を参照するのが厄介。Linqは前にしか進まないし、送られてくるデータは過去のものなど知らない。このことはReactive Frameworkだけの話ではなく、以前にもLINQで左外部自己結合やScan?という記事で書きましたが、そこで出てくる解決策がScan。そうそう、ScanはAchiralにあるので(ということでlinq.jsにもあります)、動作は分かってます。ようするにAggregateの計算過程吐きだし版です。とりあえずlinq.js ReferenceでE.Range(1,10).Scan("x,y=>x+y")と打ってみてください(宣伝宣伝)
Scanならば「一個前」の情報が手に入る。しかし「複数個前」の情報はどうすれば? 答えは配列使えばいいぢゃない。だそうです。過程のデータをとりあえず配列に入れて、次に送り出してやれば、そりゃ簡単に取り出せますね。何だか邪道な気がしますが、気にしない気にしない。URL先の解答例では生の配列を使って、インデックスをゴニャゴニャとしていて非常に正しいとは思いますが、あまり生の配列のインデックスは扱いたくないので、Queueを使ってみました。別にQueueのClear()のコストなんてたかが知れてるっしょ(中ではArray.Clearを呼んでいて、Array.Clearは……以下略)という割り切りで。
public static IObservable<MouseButtonEventArgs> GetMultiClickScan
(this UIElement element, int count, int multiClickSpeedInMilliSeconds)
{
return Observable.FromEvent<MouseButtonEventArgs>(element, "MouseLeftButtonDown")
.Select(e => e.EventArgs)
.Scan(new
{
Times = new Queue<DateTime>(count),
Hit = false,
Event = (MouseButtonEventArgs)null // ダミーなのでnullをキャストするのが楽
}, (a, e) =>
{
var isHit = false;
var now = DateTime.Now;
a.Times.Enqueue(now);
if (a.Times.Count == count)
{
var first = a.Times.Dequeue();
Debug.WriteLine((now - first).TotalMilliseconds); // Debug
if ((now - first).TotalMilliseconds <= multiClickSpeedInMilliSeconds)
{
isHit = true;
a.Times.Clear();
}
}
return new
{
Times = a.Times,
Hit = isHit,
Event = e
};
})
.Where(a => a.Hit)
.Select(a => a.Event);
}
ScanはAggregateと全く同じで、accumlatorのみの実行の他に、第一引数でseedを渡すこともできます。Scanのseedで、変数と判定を行うためのフラグを保持するクラスを作り、accumlatorで判定と変数の持ち越しを行い、Whereで判定をフィルタリング。このコンボは非常に強力で、幾らでも応用が効きそう。LinqでのAggregateはほとんど使われませんが、RxにおけるScanはよく見かけることになるのではないかと思います。
で、理屈は分かったけれど、何だかゴチャゴチャとしてない? という感想は否めない。ただ、グローバル領域に変数を置く必要なくフラグを閉じ込められていること、そして、応用の効きそうな柔らかさが見えたり見えなかったりしませんか? 応用的なものも、追々考えていきたいです。
次元の狭間へ
Scanが出たので、Aggregateもついでにおさらい。これはLinq to ObjectsのAggregateと変わりません。ちなみにRx Frameworkでの内部実装はScan().Last()だったりするので、Scanとほんとーに丸っきり変わりません。じゃあ、無限リピートのFromEventにたいしてAggregateって、実行するとどうなるの?というのは気になるところですが、答えは次元の狭間に入ってしまって、その行からコードが一切進まなくなります。AggregateだけじゃなくCountやLast、ToEnumerableなど全てを列挙してから答えを返す系のメソッドは全て同じ結果になります。コンソールアプリの簡単なコードで試してみると、こうなる。
class MyClass
{
public event EventHandler<EventArgs> Ev;
public void Fire()
{
Ev(this, new EventArgs());
}
}
static void Main(string[] args)
{
var mc = new MyClass();
var count = Observable.FromEvent<EventArgs>(mc, "Ev")
.Count(); // ここで次元の狭間にダイブする
mc.Fire(); // ここに到達することは未来永劫無い
}
恐ろしや。ただ、前段階でTakeやTakeWhileを挟めば、無限リストは有限リストとなるので、面白い感じに制限が出来ます。この辺も応用例として、そのうち紹介していければと思います。
カウンター
もう一度Scanを見ます。トリプルクリックの例題は捻りすぎな感が否めないので、もっとストレートに、Scanの「前の値を保持し続けることが出来る」という点を見せる例題を一つ。クロージャの例なんかでも定番のカウンターで。
Observable.FromEvent<RoutedEventArgs>(CounterButton, "Click")
.Scan(0, (x, y) => ++x)
.Subscribe(i => CounterButton.Content = i + "Clicked");
とても簡潔で、変数が全て閉じ込められていて、綺麗……。
Linqは美人
- 2009-09-25
public class Stamp
{
public int Year { get; set; }
public string Name { get; set; }
public Stamp(int year, string name) { this.Year = year; this.Name = name; }
public override string ToString() { return this.Year + ":" + this.Name; }
}
public class StampCollection : IEnumerable<Stamp>
{
private Dictionary<string, Stamp> stamps = new Dictionary<string, Stamp>();
public void Add(Stamp s) { stamps.Add(s.Name, s); }
public void Add(int year, string name) { stamps.Add(name, new Stamp(year, name)); }
// yield returnを通さなくてもそのままreturn可能
public IEnumerator<Stamp> GetEnumerator()
{
return stamps.Values.OrderBy(s => s.Year).GetEnumerator();
}
// GetEnumeratorと名乗りながらGetEnumeratorじゃないのが少しアレですね
public IEnumerable<Stamp> GetEnumerator2()
{
return stamps.Values.OrderByDescending(s => s.Year);
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
// 利用時例
static void Main(string[] args)
{
// コレクション初期化子は、複数引数時は更に{}で囲む
var stamps = new StampCollection()
{
{1998, "hoge1"},
{1999, "hoge2"},
{2000, "hoge3"}
};
// GenericsにしておくとCast<T>を使わずLinqでコンボ出来る
stamps.GetEnumerator2().Select(s => s.Name).ToList().ForEach(Console.WriteLine);
}
memo:C#の反復と例外 - ニート=>(vb=>..なんて無かった)=>ネトゲ屋のものを少し書き換えました。コレクション初期化子と、yield returnせずにそのまま投げ返すように変えただけですが。Linqは美人。ですです。
JavaScriptでString.Format的な超簡易テンプレート置換
- 2009-09-18
// String.Format的な超簡易テンプレート置換関数
var Format = function(template, replacement)
{
if (typeof replacement != "object") // 可変長引数時はreplacementを詰め替え
{
replacement = Array.prototype.slice.call(arguments, 1);
}
return template.replace(/\{(.+?)\}/g, function(m, c)
{
return (replacement[c] != null) ? replacement[c] : m
});
}
// 例。可変長引数渡しでも配列渡しでもオブジェクト渡しでも可。
var case1 = Format("<div id={0}>{1}</div>", "あいでぃ", "要素");
var case2 = Format("<div id={0}>{1}</div>", ["あいでぃ", "要素"]);
var case3 = Format("<div id={ID}>{ELEM}</div>", { ID: "あいでぃ", ELEM: "要素" })
.NET FrameworkのString.Formatは文字を連結するのに、非常にお手軽で良いです。というわけでJavaScriptでもそれをやる。ついでにテンプレート置換風にオブジェクト渡しにも対応させる。単純な置換時は数字で、長ったらしい置換時はオブジェクトで。両方に対応させなければ、詰め替えが必要ないので正規表現でカカッと一行なんですねえ。詰め替えも別にslice.callで一発だし。以前にlinq.jsを絡めてgdgdとやってたのですが、二度もね!、あんなにgdgdやらずとも、もんのすごーく単純明快に書ける。無理やり使おうとして、無駄に複雑になるのはイクない。
と、恥ずかしくなったので今回載せました。あと、JavaScriptは文字連結面倒くせー、って時にササッとコピペで取り出して使いたい時のために(笑) ちゃんとDateTime等も含めたフォーマット変換に対応させるとか、テンプレートだったらちゃんとテンプレートエンジンな感じでforやifも動くように、とかの話は知りません。
そういえば、置換部分の関数ですけど、最初は格好つけて「return replacement[c] || m」って書いたんですが、これだとマッチがハッシュ内に見つからなかった場合(undefinedになってる)だけでなく、 空文字列の場合もfalse扱いになってしまってダメなんですね。C#の??のように使いたいのですが、例えば数字だと「var i = 0 || 3」だったら3になるしで使いづらい。というわけで、結局==nullばかり使うことになる。===undefinedって書けって話でもありますが、まあ、==nullのほうが色々考えなくて済むから楽で。
ログ吐き骨組み
- 2009-09-16
デモ大事。Subscribeの時にConsole.WriteLine並べて、実行結果想像つきますよね、というのがいまひとつすぎたので、出力が見える骨組みを作りました。今後のReactive Frameworkの紹介時にソースコード上のDebug.WriteLineは、こーいうことなんですねー、と思ってください。毎回これ乗っけてると長ったらしいので、暗黙の、ということで。
<UserControl x:Class="SilverlightApplication4.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Name="ExecuteButton" Content="Execute" />
<Button Name="ErrorButton" Content="Error" />
<Button Name="ObservableButton" Content="Observable" />
<Button Name="EnumerableButton" Content="Enumerable" />
</StackPanel>
<ScrollViewer Grid.Column="1">
<TextBlock Name="LogBrowseTextBlock"></TextBlock>
</ScrollViewer>
</Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;
using System.Reflection;
namespace SilverlightApplication4
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
Debug.Set(LogBrowseTextBlock, Dispatcher);
ExecuteButton.GetClick().Subscribe(() =>
Observable.Range(1, 10).Subscribe(
i => Debug.WriteLine(i),
e => Debug.WriteLine(e),
() => Debug.WriteLine("completed")
)
);
ErrorButton.GetClick().Subscribe(() =>
Observable.Range(1, 10)
.Do(i => { if (i == 5) throw new Exception(); })
.Subscribe(
i => Debug.WriteLine(i),
e => Debug.WriteLine("onError"),
() => Debug.WriteLine("onCompleted")
)
);
ObservableButton.GetClick().Subscribe(() =>
GetMethodNames(typeof(Observable)).ToList().ForEach(s => Debug.WriteLine(s))
);
EnumerableButton.GetClick().Subscribe(() =>
GetMethodNames(typeof(Enumerable)).ToList().ForEach(s => Debug.WriteLine(s))
);
}
IEnumerable<string> GetMethodNames(Type type)
{
return type.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Select(mi => mi.Name)
.OrderBy(s => s)
.Distinct();
}
}
public static class Debug
{
private static TextBlock textBlock;
private static Dispatcher dispatcher;
public static void Set(TextBlock textBlock, Dispatcher dispatcher)
{
Debug.textBlock = textBlock;
Debug.dispatcher = dispatcher;
}
public static void WriteLine(object message)
{
if (textBlock != null)
{
dispatcher.BeginInvoke(() =>
textBlock.Text = message.ToString() + Environment.NewLine + textBlock.Text);
}
}
}
public static class ControlExtensions
{
public static IObservable<Event<RoutedEventArgs>> GetClick(this ButtonBase button)
{
return Observable.FromEvent<RoutedEventArgs>(button, "Click");
}
}
}
幾ら簡易的なものだから、という言い訳がましさを考えても、かなり微妙なコードな気がする。原型はWPFでTraceListenerにTextBlockに書きだすものを追加して、Trace.WriteLineで処理していたもの。SilverlightにはTraceがなかったので、そのまま静的クラス・メソッドに置き換えて、Silverlightにも本来あるDebugを塗り替えちゃうという……。ようするにそのままコピペしても動くよね!的な感じでやりたいな、というところなわけです。ダメ?
ついでにSubscribeの中でObservableって、汚い、ように見えるかも。でも実際これはなんてことなくて、ようはJavaScriptっぽいんですよね、ほんと。DOMContentLoadedにイベント登録のaddEventListener並べるのと一緒で。じゃあ実際こうしてグチャグチャ並べるかというとそうではないようで、実際は拡張メソッドへ記述する、という形で分散していくようですが、まだまだ分からず。Microsoft側の実例やドキュメントが整ってくれないと何とも言えない感じ。
作業環境
画像クリックで原寸サイズ。最近思うところあってVisual Studioの配置をごにょごにょと弄っています。今は、こんな感じに落ち着きました。左にエラー一覧・検索など。右にソリューションエクスプローラー・クラスビュー・スタートページなど。そして左右にそれぞれコードウィンドウを分割。原則的にメインウィンドウは左。コード定義ウィンドウを右ウィンドウに開いて常時表示。もしくはXAML編集と並列したり。といったところです。コード定義ウィンドウはデカい画面で常時表示で初めて効果を発揮しますね、素晴らしく便利。
30インチ 2560x1600の無駄遣いが火を吹く!というわけですが、やっぱ広いって便利、エディタウィンドウ2面同時表示って便利、です。30インチでなくても、横2560は19インチ1280x1024のデュアルで行けます。ただ、実際はこれに加えてデバッグ時のプログラム本体なりブラウザなりを置いておく場所が欲しいので、その場合はデュアルじゃ足りないですね……。グラフィックボードが一枚でトリプルをサポートしてくれれば、というか、するべき、ですよね。ATIのEyefinityにはとても期待してます。
.NET Reactive Framework メソッド探訪第四回:メソッド一覧
- 2009-09-14
前回が少し、明らかに説明不足でした……。Subscribeが原則としてIObserverを受ける(直接ラムダ式でonNextを記述するのはただの省略記法)ということは、IObserverを別に作っておける。もっと進めると、Subscribeで実行されるコード本体は自由に入れ替え可能。ということになります。ここが、イベント直記入に対するReactive Frameworkの強みの一つ。それ○○パターンだって?そうですね?その○○パターンがデフォルトで何も考えず使えるのならば、素晴らしいと思います。
では、ここからようやくメソッド探訪。の前に、全メソッド一覧を見てみます。IObservable<T>拡張メソッド一覧。そう、最初感動したのは、ドット打ってIntelliSenseに並んだこのメソッド名を見て、なのよね。いかにも素晴らしいことが出来そうな予感だったという。
Aggregate
Amb
AsObservable
Catch
CombineLatest
Concat
Cons
Contains
Count
Create
Defer
Delay
Dispatch
Do
Empty
Finally
First
FirstOrDefault
Flatten
ForkJoin
FromEvent
Generate
GetEnumerator
HoldUntilChanged
Interval
Last
LastOrDefault
Latest
Let
LetRec
LongCount
Merge
MostRecent
Never
Next
Post
Range
Reify
Repeat
Retry
Return
Sample
Scan
Select
SelectMany
Send
Single
SingleOrDefault
Skip
SkipWhile
Spawn
Start
Subscribe
Synchronize
Take
TakeWhile
Throttle
Throw
Timeout
ToAsync
ToBehavior
ToEnumerable
ToObservable
ToSubject
Until
WaitUntil
Where
Zip
かなり多い。68個ある。名前から想像つくのもあれば全くつかないものも。順番はどうしようかな、LinqではGenerating, Projection and Filtering, Join, Set, Ordering, Grouping, Aggregate, Paging, Convertで分けられたので、何らかの指針でもって分けた方が良いのは間違いないのですが、実際に中を突っつかないと何が何なのか全く分からない。
Func<Type, IEnumerable<string>> GetMethodNames =
type => type
.GetMethods(BindingFlags.Static|BindingFlags.Public)
.Select(mi => mi.Name)
.OrderBy(s => s)
.Distinct();
var enumerable = GetMethodNames(typeof(Enumerable));
var observable = GetMethodNames(typeof(Observable));
observable.Except(enumerable).ToList().ForEach(Console.WriteLine);
メソッド名一覧はobservableのみを吐いたもので、上のコードはEnumerableとの差分も取ってみたものです。Enumerableと重複しないものは47個でした。そんなに被ってる、というわけではないですね……。ちなみに、Enumerableは50個。覚えきれているので、もっと少ないと思ってたんですが、意外と多かった。Linq to Objectsもきちんと理解して使えるようになるのに半年ぐらいかかってしまっているので、今回も長期戦かなー。ちんたらやってる間にドキュメントが出てくるのを期待したい。