【Ruby】【メタプログラミング】callerメソッド。method_missingを使ったメソッド呼び出しのトレーシング
■topic summary
study about meta-programming with Ruby.
- 作者: まつもとゆきひろ,David Flanagan,卜部昌平(監訳),長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/01/26
- メディア: 大型本
- 購入: 21人 クリック: 356回
- この商品を含むブログ (124件) を見る
分かるかな。。
callerメソッド
引数なしの場合は、スタックフレームが1個省略された形になる
class Hoge def meth1 meth2 end def meth2 p caller p caller(0) end end Hoge.new.meth1
■実行結果
["C:/Users/Hoge_ADMIN/Desktop/test/fizzbazz.rb:4:in `meth1'", "C:/Users/Hoge_ADMIN/Desktop/test/fizzbazz.rb:12:in `<main>'"] ["C:/Users/Hoge_ADMIN/Desktop/test/fizzbazz.rb:8:in `meth2'", "C:/Users/Hoge_ADMIN/Desktop/test/fizzbazz.rb:4:in `meth1'", "C:/Users/Hoge_ADMIN/Desktop/test/fizzbazz.rb:12:in `<main>'"]
なるほど。
method_missingを使ったメソッド呼び出しのトレーシング
class Object def trace(name, stream=STDOUT) return TraceObject.new(self, name, stream) end end class TraceObject #TraceObjectの既存インスタンスメソッドを消す instance_methods.each do |m| next if m == :inspect || m == :object_id || m == :__id__ || m == :__send__ undef_method m end def initialize(o, name, stream) @o, @n, @trace = o, name, stream end def method_missing(*args, &b) p method_name = args.shift arglist = args.map{|ar| ar.inspect}.join(",") @trace << "Invoking: #{@n}.#{method_name}(#{arglist}) called at #{caller[0]}\r\n" begin result = @o.send(method_name, *args, &b) @trace << "Returning: #{result} from #{@n}.#{method_name}(#{arglist}) called at #{caller[0]}\r\n" result rescue Exception=>e @trace << "Raising: #{e.class}:#{e} from #{@n}.#{method_name}(#{arglist})}\r\n" raise end end def __delegate @o end end a = [1,2,3].trace("target") p a #<= TraceObjectオブジェクトは出力できない p a.__delegate a.<<(4) a.fetch(10)
■実行結果
Invoking: target.inspect() called at fizzbazz.rb:34:in `p' Returning: [1, 2, 3] from target.inspect() called at fizzbazz.rb:34:in `p' [1, 2, 3] #<= 委譲元オブジェクトが出力される [1, 2, 3] Invoking: target.<<(4) called at fizzbazz.rb:36:in `<main>' Returning: [1, 2, 3, 4] from target.<<(4) called at fizzbazz.rb:36:in `<main>' Invoking: target.fetch(10) called at fizzbazz.rb:37:in `<main>' Raising: IndexError:index 10 outside of array bounds: -4...4 from target.fetch(10)}
注意(?)
委譲先オブジェクトを出力したいときは、
pメソッド内で実行されるinspectメソッドを消去対象からはずす。
next if m == :inspect || m == :object_id || m == :__id__ || m == :__send__
■実行結果
#<TraceObject:0x2e3e138 @o=[1, 2, 3], @n="target", @trace=#<IO:<STDOUT>>> [1, 2, 3]
ふむふむ。
【Ruby】?記法、応用編 視力検査
さっき、クエスチョンマークが連続した問題を解いたけど
Goroさんが応用問題を作ったんだって。
Q. ??.a??????:?? はvalidな #Ruby プログラムです。これの構文を解説してください。 ( https://t.co/mT1aoEf4rr の応用問題)
— Fuji, Goro (@__gfx__) November 15, 2013
これは、さっきの?記法が分かったら簡単だねー。
ポイント
最初の「Q. 」は、質問って意味で、構文には含まれない!
これ嵌った。
もー。
回答
?記法と条件演算子の組み合わせ。
構文全体
??.a??????:??
を分けて考えると、
まず 頭の部分、
??.a???
は
"?".a?("?")
なわけで、こう書くと分かりやすい。
class String def a?(str) p str end end ??.a???
■実行結果
"?"
そうすると構文は、
"?"???:??
となって、これはもう条件演算子。
Rubyはnil以外はtrueになる。
こうやって書き直すと分かりやすい。
p "?"? "hoge":"aa" p !"?"? "hoge":"aa"
■実行結果
"hoge" "aa"
なるほどねー。
視力検査みたいだね。
【Ruby】クエスチョンマークが三つで何になる? ??? ?記法
■topic summary
what means -> a???
タイトル意味分からないね。
そらはさんのツイートでこんなのを見つけたよ。
Q. foo.a??? が undefined method "a?" になるのは何故でしょうか。 (ruby) (理由意外にしらない人がおおかったので)
— そらは (@sora_h) November 14, 2013
へー?
なんでなんだろう?
わからないなー。
しらべてみよう!
本当?
まずは本当かしらべてみよう
class Q def self.foo end end Q. foo.a???
■実行結果
:in `<main>': undefined method `a?' for nil:NilClass
おお!
たしかに!
クエスチョンマークが3つっていうのが怪しいよね。
2つだとどうなるんだ?
■実行結果
syntax error, unexpected end-of-input
へー!?
クエスチョンマークをつかったリテラル記法
数値リテラル
http://docs.ruby-lang.org/ja/2.0.0/doc/spec=2fliteral.html
?a
文字 a を表す String
こんなヒントをみつけた。
なるほどね!
p ??
■実行結果
"?"
だから、この↓結果は
a(??)
■実行結果
undefined method `a' for main:Object (NoMethodError)
これは、aメソッドに引数「?」を渡していると解釈されているんだね。
括弧でくくらないと、クエスチョンマークはメソッド名の一部としてパースされる
p a??
■実行結果
syntax error, unexpected end-of-input
これは、a?メソッドに渡す引数が?記法を使っていると解釈されていて、
その?記法に対する引数が期待されているのに渡してないから、
シンタックスエラーになってしまう。
本題の
a???
は、
a?(??)
と書き直すと分かりやすく、
■実行結果
undefined method `a?' for main:Object (NoMethodError)
つまり、引数として「?」を渡したa?メソッドを実行しようとしているけど、
a?メソッドががみつからないよ、というエラー。
なるほどー。
【HELP済】【JavaScript 】bind関数を引数なしで呼ぶと。bindでクロージャー。
■topic summary
what means -> callback.bind() ???
この本を読んでるんだけど、すごいボリューム。
パーフェクトJavaScript (PERFECT SERIES 4)
- 作者: 井上誠一郎,土江拓郎,浜辺将太
- 出版社/メーカー: 技術評論社
- 発売日: 2011/09/23
- メディア: 大型本
- 購入: 24人 クリック: 588回
- この商品を含むブログ (12件) を見る
引数なしのbind呼び出し?
21章にこんなサンプルコードが。
event_source.on('foo', callback.bind()); event_source.removeListener('foo', callback.bind());
本には、
これらのコードは、イベントハンドらを意図通り削除できていません。なぜなら2つのbindの呼び出しはそれぞれ別の関数オブジェクトの参照を返すからです
とある。
なんでbindメソッドを引数なしで呼んでいるの??
うーん、、
The value is ignored if the bound function is constructed using the new operator.
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Mozilla仕様にはこう書いてあるけど、
EventEmitter.onメソッドの引数でbindメソッドが実行されると
newを使ってるのと同等とみなされているのかな。。?
よく分からないな。
引数の省略はできないとIEには書いてあるし。。
bind メソッド (Function) (JavaScript)
まだ自分はthisの概念が分かってないのかもしれない。
とりあえずメモしておこう。。。
追記
ピタゴラさんからコメントをもらったよ!
なるほど。
bindが別々の関数オブジェクト(クロージャ)を返すことを表現したかったのか!
別にNode.jsは関係のない話なんだね。
なら手元で実際に動かして試してみよう。
ありがとうピタゴラさん。。!
bind関数、引数あり
fn=function(){ console.log(this); } var a={x:"aaa"}; fn.bind(a)();
■実行結果
Object {x: "aaa"}
fnメソッドのレシーバーはaオブジェクト。
bind関数、引数なし(ケースA)
fn=function(){ console.log(this); } var a; fn.bind()();
■実行結果
Window {top: Window, window: Window, location: Location, Proxy: Object, external: Object…}
なるほど。
エラーになるわけではないのか。
ただ、thisがWindowオブジェクト参照になるのは
クライアントサイドJavaScriptでのグローバルオブジェクトが
Windowオブジェクト だからであって、
ここに依存したコーディングは避けたほうがよさそう。
bind関数、引数がundifined(ケースB)
fn=function(){ console.log(this); } var a; fn.bind(a)();
■実行結果
Window {top: Window, window: Window, location: Location, Proxy: Object, external: Object…}
引数なしと同じ結果。
参考
上記のケースA ケースB について、
通りすがりさんから、コメントをもらったよ。
ふむふむ。通りすがりさんありがとう!
なるほど。
thisがwindowオブジェクトになった場合は
引数でレシーバの指定が出来てない可能性があるかもしれない、と頭に入れておこう。
別の関数オブジェクトの参照を返す?
以上を踏まえて、本に書いてあった
2つのbindの呼び出しはそれぞれ別の関数オブジェクトの参照を返す
について、実際に確かめてみよう。
素朴なクロージャー
まずは素朴なパターン
countup = function(){ var cnt = 0; return function(){console.log(++cnt);} } var fn = countup(); fn(); fn();
■実行結果
1 2
あえてのbind
bindを使ってみると、
countup = function(){ var cnt = 0; return function(){console.log(++cnt);} } countup.bind()()(); countup.bind()()();
■実行結果
1 1
丸括弧が3つあるのは、
bindメソッドの引数
bindメソッドで返却される関数オブジェクトの引数
bindメソッドで返却される関数オブジェクトの実行
だよ。
ふむふむ。
2つのbindの呼び出しはそれぞれ別の関数オブジェクトの参照を返す
というのは別に、bindだからどうこうって話ではなくて、
単にメソッドを複数回呼び出したら、
そのたびにオブジェクトが生成されて返却されます
というだけの話か。
bindの返り値を変数に参照させておいても、
実行時に同じコンテキストを見ることはできない。
countup = function(){ var cnt = 0; return function(){console.log(++cnt);} } var fn = countup.bind(); fn()(); fn()();
■実行結果
1 1
同じコンテキストを見たかったら、こう!
countup = function(){ var cnt = 0; return function(){console.log(++cnt);} } var fn = countup.bind(); var gn = fn(); gn(); gn();
■実行結果
1 2
なるほどー。
用語がふわっとしててごめんなさい。
そしてピタゴラさん、ありがとう!
【JavaScript 】【Ruby】 && と ||の便利な合わせ技
■topic summary
what's come out? => console.log(a && b || "none")
この記事を読んで、jQueryのコードを追っているよ。
第2回 jQueryライブラリ(172行目~469行目):jquery.jsを読み解く|gihyo.jp … 技術評論社
頻繁に出てくるのが、こういう構文。
return this.setArray( // HANDLE: $(array) selector.constructor == Array && selector || // HANDLE: $(arraylike) // Watch for when an array-like object, contains DOM nodes, is passed in as the selector (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || // HANDLE: $(*) [ selector ] );
Rubyでも||を使うことによって、
nilの場合の値を指定する構文があるのはよく見るけど、
&&ってなんだ?
確かめてみよう。
var a = "aaa"; var b = "bbb"; var c; console.log(a && b || "none"); console.log(c && b || "none");
■実行結果
"bbb" "none"
なるほど。
これはRubyでもできるのかな?
a = "aaa" b = "bbb" c = nil p (a || b) p (c || a) p (c || a && b)
■実行結果
"aaa" "aaa" "bbb"
なるほどー。
【HELP】【Ruby】【アルゴリズム】8パズル dehashしないVer
■topic summary
coding without dehash function
dehash function is here
だめだ。
高橋さんのコードはdehashしていないから早いのかなー
と思ってまねしてみたけど大して早くならなかった。
処理時間
2回ずつ計測。
■前のコード user system total real 5.024000 0.000000 5.024000 ( 5.025287) user system total real 5.382000 0.015000 5.397000 ( 5.404309) ■今のコード user system total real 5.086000 0.000000 5.086000 ( 5.085291) user system total real 4.976000 0.016000 4.992000 ( 5.002286)
とくに変わってないよ!
なんでだろう。
変更したコード
require 'set' require 'benchmark' #表示用 def show(now_pattern) state = now_pattern while state #puts state.hash_code.to_s[0..3] #puts state.hash_code.to_s[4..7] #puts "------" state = state.parent end end class Pattern attr_accessor :nums, :parent def initialize (nums, parent) @nums = nums @parent = parent end def hash_code @nums.join("").to_i end end class PatternQue < Array def add_next_pattern(now_pattern, checked_set) blank = now_pattern.nums.index(9) if blank < 4 copy_nums = now_pattern.nums.dup copy_nums[blank], copy_nums[blank+4] = copy_nums[blank+4], copy_nums[blank] checkdone_and_add(checked_set, Pattern.new(copy_nums, now_pattern)) elsif copy_nums = now_pattern.nums.dup copy_nums[blank], copy_nums[blank-4] = copy_nums[blank-4], copy_nums[blank] checkdone_and_add(checked_set, Pattern.new(copy_nums, now_pattern)) end if blank != 3 && blank != 7 copy_nums = now_pattern.nums.dup copy_nums[blank], copy_nums[blank+1] = copy_nums[blank+1], copy_nums[blank] checkdone_and_add(checked_set, Pattern.new(copy_nums, now_pattern)) end if blank != 0 && blank != 4 copy_nums = now_pattern.nums.dup copy_nums[blank], copy_nums[blank-1] = copy_nums[blank-1], copy_nums[blank] checkdone_and_add(checked_set, Pattern.new(copy_nums, now_pattern)) end end def checkdone_and_add(checked_set, candidate) return if checked_set.index(candidate.hash_code) checked_set << candidate.hash_code self << candidate end end def solve(nums) que = PatternQue.new first_pattern = Pattern.new(nums, nil) que << first_pattern checked_set = [] checked_set << first_pattern.hash_code _solve(que, checked_set) end def _solve(que, checked_set) while now_pattern = que.shift return show(now_pattern) if now_pattern.hash_code == 91234567 que.add_next_pattern(now_pattern, checked_set) end puts "not solve" end Benchmark.bm do |x| x.report { solve([2, 7, 4, 1, 5, 9, 3, 6]) } end
ふむむむ。
こんど師匠に聞いてみよう。
【Ruby】【アルゴリズム】関数呼び出しはコストが高い
■topic summary
study about 8puzzle.
see more
今日のブログは感動だよ!(当社比)
またいつものように高橋さんのアルゴリズムをもとにお勉強。
やわらか頭でアルゴリズムを10倍生かす - 第3回 8パズル:ITpro
問題
1から8までのパネルと一つの空欄で構成される、3×3のスライドパズルがあります。パネルがばらばらに並べられた状態から始めて、左上から1、2、…、8と並べられたら完成です(図1)。このパズルを解くプログラムを作成してください。
7パズルは以前解いたから余裕
と思っていたけど、甘かった。
発見がいろいろありました。
以前の実装
幅優先&キューで解いた。
以前のコードでは、「Queから要素をとりだす」をやっていなかった。
つまりQueは要素分だけ、たらたらと長くなっていく実装。
i番目の要素を検証し、iをインクリメントさせていく。
def _solve(idx, que) i = 0 while i < que.length if que[i].val == [9,1,2,3,4,5,6,7] #goal else que.add_next_pattern(i) i += 1 end end end
なぜこうしたかというと、過去の状態を取得したかったから。
Queのインデックスをさかのぼることで、過去状態を取得できる。
でも、Queを使った実装は、
Queから先頭要素を取り出していくことで実装するのがオーソドックスなやり方。
高橋さんのコードも
Queの要素を取り出していく方式。
じゃあ過去状態をどうやって取り出すのか?
というと、
Patternクラスの要素としてPatternクラスインスタンスを持つ。
マトリョーシカのイメージ?
これを見たときに、自分は
メモリが爆発しないのかな???ってすこし不思議に思った。
でも考えたらそんなことはないね。
あくまで参照(ポインタ)を渡しているだけだから、メモリを食うことはない。
ってスマートに書いてるけど
最初はどうしても理解できなくてコードを全量写経したけど。
秘密だから!
なぜ高橋さんのコードはこんなに早いのか
自分の実装したコード(※1)と大した違いはなさそうなのに、
実行時間が雲泥の差。
どうしてか考えてみるに、
お手本コードはhash化するものの、dehashしていない。
私のコードはこうやってdehashしている。
def _dehash(int) int.to_s.split("").map!{|ch| ch.to_i} end
お手本では、ゼロの位置を別要素としてインスタンスに持っている。
最初見たとき「どうしてわざわざ重複する情報を持つんだろう」と思ったけど
こうすることで処理が早くなるのかもしれない。
次の記事でやってみよう。
関数呼び出しのコスト
そんなこんなで、インスタンスに過去状態を持つことで
オーソドックスなQue使用パターンを実装してみました。
そしたら、stack level too deep。
原因は、whileの中で再帰を使っていたため。
■NGパターン
stack level too deepで落ちちゃう。
def _solve(que, checked_set) now_pattern = que.shift if now_pattern.val == 91234567 #goal else que.add_next_pattern(now_pattern, checked_set) _solve(que, checked_set) #<= 再帰にしていた end end
■OKパターン
def _solve(que, checked_set) while now_pattern = que.shift return show(now_pattern) if now_pattern.val == 91234567 que.add_next_pattern(now_pattern, checked_set) end puts "not solve" end
queをwhileでまわして、shiftで先頭を取っていく。
変に再帰を使うから混乱してしまった。
「過去状態がマトリョーシカだからメモリが足りないのか??」と混沌としていた。
ごめんね。。
師匠に喜びのメールを打つと、、
うわー!
再帰を使ってたらstack level too deepだったのが
ただのwhile文に書き直したら動くようになった!!!!
関数呼び出しはコスト高いんですよ 普段は気にならないレベルやけど、関数呼び出しが深くなるとコストが目に付く
ふむふむ。
まさに今のパターンだね。
なので、関数呼び出しを消せると判断すると 関数呼び出しを消すテクニックは昔から知られています
コンパイルする段階でということ?
コンパイルする段階でやる場合と 実行中にやる場合があります
なるほどね。
※1
実装コード
こんなコードになりました。
#表示用 def show(now_pattern) state = now_pattern while state puts state.val.to_s[0..3] puts state.val.to_s[4..7] puts "------" state = state.parent end end class Pattern attr_accessor :val, :parent def initialize (val, parent) @val = val @parent = parent end end class PatternQue < Array def add_next_pattern(now_pattern, checked_set) now = _dehash(now_pattern.val) blank = now.index(9) if blank < 4 copy = now.dup copy[blank], copy[blank+4] = copy[blank+4], copy[blank] checkdone_and_add(checked_set, _hash(copy), now_pattern) elsif copy = now.dup copy[blank], copy[blank-4] = copy[blank-4], copy[blank] checkdone_and_add(checked_set, _hash(copy), now_pattern) end if blank != 3 && blank != 7 copy = now.dup copy[blank], copy[blank+1] = copy[blank+1], copy[blank] checkdone_and_add(checked_set, _hash(copy), now_pattern) end if blank != 0 && blank != 4 copy = now.dup copy[blank], copy[blank-1] = copy[blank-1], copy[blank] checkdone_and_add(checked_set, _hash(copy), now_pattern) end end def checkdone_and_add(checked_set, candidate, now_pattern) return if checked_set.index(candidate) checked_set << candidate self << Pattern.new(candidate, now_pattern) end end def _hash(arr) return arr.join.to_i end def _dehash(int) int.to_s.split("").map!{|ch| ch.to_i} end def solve(val) first_hash = _hash(val) que = PatternQue.new que << Pattern.new(first_hash, nil) checked_set = [] checked_set << first_hash return _solve(que, checked_set) end def _solve(que, checked_set) while now_pattern = que.shift return show(now_pattern) if now_pattern.val == 91234567 que.add_next_pattern(now_pattern, checked_set) end puts "not solve" end solve([2, 7, 4, 1, 5, 9, 3, 6])
長くなってきたのでいったん終了。