(続)implicit defが2回評価される件 → 正確には structural typingな戻り値に対するメソッド呼び出しの問題
前回の "なんで2回newされてるの?" については id:kmizushima さんや id:SiroKuro さん、 @kinaba さんといった識者の皆さんも想定外の動作だったようで、逆コンパイル(逆アセンブル)するという形で調査をされておりました。なるほど、こうやって追跡すればよかったですね。デバッグスキル足りなさすぎる。大変勉強になりました。
そして、その過程は皆さんのblogエントリを直接呼んでいただくとして、結論としては以下のようなものと思われるということでした。
結論として、今回の問題は、structural typeに対するメソッド呼び出しにおいて、レシーバーの式が2回評価されるという実装に起因するものだと言えそうです。
ふつうにバグのような予感。本当なら下のようになるはずなのにね。
Object object = hogeable("abc"); Class clazz = object.getClass(); Method method = reflMethod$Method1(clazz); method.invoke(object);
確かにバグの可能性はあるというか、むしろそっちの方が可能性高い気もしなくもないけど、このレベルのバグが未だに残ってるもんかなーという疑問があったのでした。Structural Typeが入ったのって、もう2年以上前の話ですし。ともあれ、はっきりしないのは何なので、MLに質問投げてみました。
さらに追記2:
MLに投げた質問の返答が返ってきました。それによると、Scala 2.8ではFIXされてるよーとのこと。結局、バグだったってことみたいですね。
ああーっ、これバグだったのか… Scalaはまだ不慣れだということで「これはこれで正しいのだろう」と思っていました。
そして、私の元のエントリでは implicit def に限定した話のように見えましたが、そうではなく、structural typingなインスタンスを戻すメソッドの呼び出し、また戻り値を直接レシーバとしてメソッド呼び出しをするようなケースにおいてはいつでも起こりうる、ということのようです。
このstructural typingはScalaの強力さの源泉の一つでもあるので、このbugfixは2.8に期待することとして、置いておきましょう。
implicit def のケースは氏が指摘されているように、「副作用をおこさなければいい」と思われます。入出力、破壊操作などの副作用もそうですが、「時間がかかる」というような作用も2回働きますから注意したいところ。implicit defでパフォーマンスが問題になるようなコードを書くことはあり得ないというか、すべきでないでしょう。
識者の皆様、ありがとうございました!*1
*1:この手の問題を「まあそういう仕様もあるのかなあ」としておくのではなく、こう追いかけるのだ、という具体的なケースとして本当に勉強になりました
突然oracleプロセスのCPU利用率が100%になってデータベースが固まる事象について
ある時、突然、サーバ上のoracleプロセスのCPU利用率が100%になって、問い合わせはおろかログインもできなくなる…そんな事象に遭遇しました。
前提:うちの環境は8コア(後述) 専用サーバモードで動作/利用していると思ふ
SQL> select * from v$version; BANNER ---------------------------------------------------------------- Oracle Database 10g Release 10.2.0.3.0 - 64bit Production PL/SQL Release 10.2.0.3.0 - Production CORE 10.2.0.3.0 Production TNS for Linux: Version 10.2.0.3.0 - Production NLSRTL Version 10.2.0.3.0 - Production
ある時、突然データベースサーバがうんともすんともいわなくなりました。正確には、SQL問い合わせを投げると帰ってこない。ログインもできない。
で、調べてみると、pingやsshなどは普通に通ります。topコマンドその他を確認してみると、CPU利用率のうち「あるCPU(コア)のみがusertime 100%になっている」ことが解りました。wait%はほぼゼロ。その他のコアの利用率は数%程度、といたって平穏です。
なのに、他のコアに割り当てられているはずのoracleプロセス群の問い合わせもすべて通らない。IO待ちではないことも明らか。どうやらoracleの中で「固まって」いるようです。
結局、待機系にfail-overしてこの時は事なきを得た(ことなきってことはないか)。で、結果としては以下のようなものでした。
結論:「Oracle10gのバグ ただしPSRは公開済み」
要するに
- PSR10.2.0.4あてればよろしい
もう少しまとめると
- PSR10.2.0.4 では修正されている。それ以前(うちの10.2.0.3とか)では発生する
- STATSPACKを利用/仕掛けていて、statspack snapをレベル6以上で取得しているとだめ。
- レベル5に設定していれば出ないらしい
- "実行計画の取得"がレベル6以上では実施されてしまう。で、バグの発生条件に触れる。
- 固まる理由は "library cache latch"の保持しっぱなし
- 保持しっぱなしの理由はOracle内での利用メモリの枯渇・・らしい
- 利用メモリの枯渇の理由は、どうやら「メモリリーク」らしいが細かい説明割愛
同種の問題に遭遇しているかもしれないあなたへ、
- trcファイルとかで MMONの情報で
memory info: free memory = 0.00M
とか
swap info: free = 0.00M alloc = 0.00M total = 0.00M
とか出ている場合はあやしいです
なんかいまいち情報がオープンにされていないのですが、 PSR10.2.0.4を適用していなくて、STATSPACK利用している人は遭遇する可能性がありますので、エントリとしてまとめておきました。
既存クラスを拡張(したように見せる)ひとつの方法
Rubyの特異メソッド/特異クラスみたいなことが出来ないのか、と思って調べていたところ、初期段階として「既存のクラスを拡張できるか」という点を掘ってみることになりました。
Scalaはもともと、RichStringというクラスがあり、java.lang.Stringクラスに存在しない便利メソッドが定義されており、必要に応じてRichStringに変換されて呼び出せるようになっています。
scala> "100" res24: java.lang.String = 100 scala> "100".r res25: scala.util.matching.Regex = 100
java.lang.Stringには r というメソッドは定義されていません。ここでは暗黙のうちにRichStringクラスへの変換が行われています。どのようなimplicit defが起こり得るか、を実行時にうまく知る方法があればいいのですが、私はよくわかりません。いろいろ実験してみた結果。
scala> val a = "100".r _ <console>:5: error: _ must follow method; cannot follow scala.util.matching.Regex val a = "100".r _ ^ scala> val a = ("100":RichString).r _ a: () => scala.util.matching.Regex = <function>
いささか明示的に追いかけてみたところですが、前者がエラーになるのが悲しいところです。
後者ではどのクラスに変換されうるかを知っていて、それを明示的に示している例です。実際には "r"メソッドを期待しているということは、何か特定のclass/traitを相手にしていることは間違いないのでさほど問題はないのですが。
さて、上記の例を見ると Stringクラスに rというメソッドを追加/拡張しているように見えます。これと同じことを自分でもやりたい!
$ ruby class String def hoge; 100 end end puts "hoge".hoge ^D 100
とかやりたい!という時にどうするか、が今回のテーマです。
いろいろ方法はありそうですが、RichStringの例に倣ってみます。
scala> class HogeString(val self:String) { | def hoge = 100 | } defined class HogeString scala> implicit def stringAsHogeString(src:String):HogeString = new HogeString(src); stringAsHogeString: (String)HogeString scala> "fuga" res1: java.lang.String = fuga scala> "fuga".hoge res2: Int = 100
うまくいきましたね!キモは implicit def です。この宣言がされたスコープ内では引数型と関数の戻り値の型の間で暗黙のうちに変換がおこなわれる、という仕掛けのようです。正直、かなりコワい機能でもあると思いますが。
さらに突っ込んで簡単にする方法もあります
scala> implicit def stringToHogeable(src:String) = new { def hoge = 100 } stringToHogeable: (String)java.lang.Object{def hoge: Int} scala> "hogehoge".hoge res0: Int = 100
ここまで来るとかなり激しい感じがしますね。
上記の例では implicit defを解釈した時点で、戻り値となっている無名のクラスが hogeメソッドをもっていることはコンパイル時点で分かっているようです。これならどのimplicit def を呼び出すのかはScalaにはわかるわけですね。
そこで、こんな実験をしてみました
scala> implicit def hogeable(src:String) = new { | println("hogeable.new src="+src); | | def hoge = 100 | } hogeable: (String)java.lang.Object{def hoge: Int} scala> implicit def fugable(src:String) = new { | println("fugable.new src="+src); | | def fuga = 200 | } fugable: (String)java.lang.Object{def fuga: Int} scala> "abc".hoge hogeable.new src=abc hogeable.new src=abc res0: Int = 100 scala> "def".fuga fugable.new src=def fugable.new src=def res1: Int = 200 scala> "ghi".naiyo <console>:7: error: value naiyo is not a member of java.lang.String "ghi".naiyo ^
というわけで、
- implicit def後も存在しないメソッドを呼び出すようなケースでは、定義済みのimplicit defは試されない (これは当たり前というか実行効率上好都合ですね)
- 存在しないメソッドを呼ぶ際には、必要な変換に該当するimplicit defただ1つを特定して呼び出す
- 謎なのが、なぜ上記の例では new が2回走っているのかということ。つまり implicit def は二回評価されているのか...?
という感じになりました。
最後の「なぜ2回呼ばれているのか」はまだわかりません… 識者のコメントがいただけるとハッピーです!
ちなみに無名クラスでなければ一回のようです。
scala> class Test { | println(this); | | def piyo = 100 | } defined class Test scala> implicit def toTest(a:String) = new Test toTest: (String)Test scala> "hoge".piyo line5$object$$iw$$iw$Test@10c0fa7 res1: Int = 100
(traitの)defをvalでオーバーライド/実装する
Liftの Loc実装を見ていて気がついた。
scala> trait Hoge { | def fuga:String; | def piyo:String = fuga | } defined trait Hoge scala> val a = new Hoge { | def fuga = "abc" | } a: java.lang.Object with Hoge = $anon$1@1f2a9da scala> val b = new Hoge { | val fuga ="zzz" | } b: java.lang.Object with Hoge = $anon$1@1e3bfb6 scala> a.piyo res3: String = abc scala> b.piyo res4: String = zzz
なるほど、こういうことも出来るわけですか。Scalaの名前空間の仕様、及び、def==val==varみたいな感じの仕様に依っている、のかな?
メソッドpiyoは、そのコンパイル時点では関数としてのfugaしか知らないわけで、そう考えるといろいろ面白いなあ。
ちなみに
def piyo:String = hoge()
はエラーになった。
ちなみに、
scala> trait Hoge { | def fuga():String; | def piyo = fuga() | } defined trait Hoge scala> val a = new Hoge { | def fuga = "abc" | } a: java.lang.Object with Hoge = $anon$1@14a8f44 scala> a.piyo res9: String = abc scala> val b = new Hoge { | val fuga = "zeg" | } b: java.lang.Object with Hoge{def fuga: java.lang.String} = $anon$1@8825a5 scala> b.piyo res10: String = zeg
でもある。
def hoge():String は コンソールの表示でも "hoge: ()String"みたいな型ででてくるから、これをvalで実装しちゃうってのも出来る。
で、気になったので
class Hoge { val a:String = "abc" }
を scalac して jad して見てみると
(略) public String a() { return a; } (略) private final String a = "abc"; (略)
になってた。valへのアクセスはメソッドを通して実施されている。
Box[A]=Aができる不思議→ガセネタかもしれません
で、その後いろいろ試しても全然うまくいかなくて、そりゃそうだよな、共通の親なら相互に代入できるとかないわーと思って、サンプルコードをとりあえず入力してみたらコンパイルエラー
なんじゃそりゃ。
Productとか全然関係なかったです \(^o^)/ オワタ
Box[A]=A が出来る不思議
Liftのファイルアップロードのサンプルコードで
var fileHolder : Box[FileParamHolder] = Empty (略) bind(... "receipt" -> SHtml.fileUpload(fileHolder = _),
という部分がある。SHtml.fileUpload()の仕様を見ると、引数型は FileParamHolder => Any ということで、関数リテラルの内容は、というと、型的には以下のような関係になるはず。(これはあくまで例なので意味のあるコードではありません)
Box[FileParamHolder] = FileParamHolder
なんでこんなこと出来るのかがいまのところよくわからない。さっそく調べてみて気がついたこと
- Box[T] は Product trait を実装している
- FileParamHolder は Product trait を実装している
- FileParamHolderのソースには with Product などとは書かれていない
そもそも上記のような代入が出来るのはなぜか、という疑問に Product trait が関係しているかどうかはまったく不明。
でも、Productって何? なんで勝手に with Product になってるのと思っていろいろ実験してみた。
scala> case class A(); defined class A scala> (new A).isInstanceOf[Product] res1: Boolean = true scala> class B(); defined class B scala> (new B).isInstanceOf[Product] res2: Boolean = false
どうやら case class は自動的に with Product とされるようだ。ふむふむ。で、Productって何よ、という話はまだ勉強中。
Chuck入門 #10 - SndBufでwavファイルを鳴らす
なんだか ChucK by Exampleというべき様相を呈している本入門記事ですが、ついに第10回を迎えることとなりました。私の興味や、思いつきの実験精神にしたがってソースコードを作成しているので、どうしてもヘンな(妙に突っ込んだ)例が多いため理解の妨げになっている可能性もあるかと思います。その点はご容赦ください。
いままではオシレータを使って音を出してきたわけですが、今回は wavファイルを鳴らしてみましょう。多分、aiffなんかもいけるんだと思いますが試してません。
今回の完成品はこちら。まずはソースコードをどうぞ
Hid hid; HidMsg msg; 0 => int device; if (hid.openMouse(device)==false) { <<< "cannot open mouse" >>>; me.exit(); } SndBuf l1; l1.read("フルパス/loop2.wav"); 1 => l1.loop; SndBuf l2; l2.read("フルパス/loop4.wav"); 1 => l2.loop; 1.0 => l1.gain; 0.0 => l2.gain; l1 => dac; l2 => dac; while (true) { hid => now; while (hid.recv( msg )) { if (msg.isMouseMotion()) { msg.deltaX / 500.0 => float g; l1.gain() + g => float l1g; if (l1g >= 1.0) { 1.0 => l1g; } else if (l1g <= 0.0) { 0.0 => l1g; } l1g => l1.gain; 1.0 - l1g => l2.gain; } } }
今回のキモは、l1とl2という SndBufクラスのインスタンスです。ほかのコードは基本的に、2つの波形のミックス比率をマウスで操作する、というクロスフェーダーごっこのためのコードです。
上記の例を動かすためには、どこかで、所謂「ループ素材」を2種類用意してきて実行してみてください。もちろん、BPMも同じでへんな前後マージンがないものを用意しないといけませんが、うまくいけば、ビートミックス的な感じになってなかなか気持ちいいです。私はVengeanceの適当なループ素材を使ってやってみましたが、なかなかいい感じになりました。
さて、コードの説明。Hidうんぬんは前回説明しましたから思いっきり割愛します。SndBufに関するところのみに絞ります。
SndBuf l1; l1.read("フルパス/loop2.wav"); 1 => l1.loop;
はい。もう構文的には大丈夫ですよね。SndBufクラスのインスタンスを作成して、l1にその参照を代入しています(というとなんだかものものしいですが)。そして、そのインスタンスに対して 例では フルパス/loop2.wav を読み込ませています。
SndBufのloopというプロパティに1を格納すると、自動的にループ再生してくれます。今回のサンプルコードみたいな内容だと、1にするわけですが、ワンショットの効果音なんかの場合は適宜設定してください。
SndBufのl2については省略。
1.0 => l1.gain; 0.0 => l2.gain;
はい、SndBufはユニットジェネレータ(UGen)です。なので、gainプロパティを(オシレータなどと同じく)持っています。この例では、まずは l1というSndBufのゲインを1.0に、l2をゼロ(鳴っていない)状態にしています。
l1 => dac; l2 => dac;
と、これで l1,l2のUGenから dacに接続し、ただちに再生が開始されます。ちなみに、ただちに再生を開始したくない場合はどうするか、というと再生ポジションのプロパティがありますので、そちらにサンプルの長さ+1 とかおかしな値を入れておけば再生されませんので、必要なときに 0 にすると鳴りはじめます。
while (true) { hid => now; while (hid.recv( msg )) { if (msg.isMouseMotion()) { msg.deltaX / 500.0 => float g; l1.gain() + g => float l1g; if (l1g >= 1.0) { 1.0 => l1g; } else if (l1g <= 0.0) { 0.0 => l1g; } l1g => l1.gain; 1.0 - l1g => l2.gain; } } }
これは、前回同様ですが、今回の処理では、マウスの移動量にしたがって、l1のゲインを動かし、l1+l2=1.0 となるように双方のゲインを設定しています。これで半々になったり、l2だけに鳴ったりしますね。そんな内容のコードです。
今回はこれにて終了です。SndBufでループ素材を鳴らして、それをLPFに通して・・・などということももちろん出来ますから、ループ素材とオシレータとフィルターですでに夢が広がりまくりですね!
ではまた次回!