/ 最近 .rdf 追記 編集 設定 本棚

脳log[20091206] つれづれに書き連ねるとリンクしにくいらしいので……(誰の話でしょうね。それにあそこははてな) | メソッドの困った上書き | 1引数ラムダの冗長さ



2009年12月06日 (日) [C++]「ヘッダファイルの依存を減らす」<端緒。パフォーマンスの本といえば『Efficient C++』だけかと思っていた(『Boost』にそのように書かれていた)ら、目当ての本の横に『C++パフォーマンス戦略』という本が。これまでのところ大垣書店はすごくよくやっている。Amazonで注文する前にとりあえずのぞいてみて、一度も期待を裏切られていない。

最終更新: 2013-05-21T00:39+0900

[tDiary] つれづれに書き連ねるとリンクしにくいらしいので……(誰の話でしょうね。それにあそこははてな)

<h4>と <h5>にもアンカー(<a name="..."></a>)を与える。href属性や中のテキスト(&nbsp;)は URLをコピーしやすくするためのおまけ。そもそも <h4>や <h5>に id属性を付加するのでなく <a>を利用しているところからが利便性目的以外のなにものでもない。<h4>と <h5>の中に <a>を置くことには、highlight.rbを修正しなくてもハイライトが機能するというメリットもある。デメリットは見出し語の先頭に余分な空白が挿入されている、ということ。セクションタイトル右端の編集用リンク「✍」は、<h3>に含まれないように気をつかっているのですよ。

 core/tdiary/wiki_style.rb, TDiary::WikiSection#do_html4

def do_html4( date, idx, opt )
	strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) }

	r = @html.lstrip
	r.sub!( %r!<h3>(.+?)</h3>!m ) do
		"<h3><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></h3>"
	end or r.sub!( %r!^<p>(.+?)</p>$!m ) do
		"<p><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></p>"
	end

	serial = [nil, nil, nil, '%02d'%idx, '00', '00']
	dumped_param = strdump.call "#{date.strftime( '%Y%m%d' )}#p#{'%02d' % idx}"
	header_t = Struct.new(:opentag, :level)
	bqlevel = 0
	r.gsub!( %r!(<blockquote\b)|(</blockquote>)|<h(4|5)[^>]*>!i ) do
		bqlevel += 1 if $1
		bqlevel -= 1 if $2
		next $& if bqlevel != 0 or $3.nil?

		h = header_t.new($&, $3.to_i)
		serial[h.level, serial.length-h.level] = Array.new(serial.length-h.level){|i| i==0 ? serial[h.level].succ : '00' }
		frag = 'p' + serial[3 .. h.level].join('.')
		%!#{h.tag}<a name="#{frag}" href="<%=anchor(#{dumped_param}).gsub(/#p#{'%02d'%idx}/, #{strdump.call('#'+frag)}) %>">&nbsp;</a>!
	end

	r.gsub( /<(\/)?tdiary-section>/, '<\\1p>' )
end

2段階に評価されるせいで、すんごく読みにくい。

ハッシュテーブルのキーがシンボルか文字列か気にしたくないのと、括弧や引用符やコロンがじゃまくさいので、. ひとつで済む Structに登場してもらった。

lambdaの .call() も嫌いだなあ。[] で () を代用するおぞましさよりはマシだが。

試してないけど、「end or r.sub!(...」を複数の行に分けるとシンタックスエラーになりそうなのも嫌。

破壊的メソッドが返す nilを利用するコードを初めて書いた!

 @2013-05-21 URLが変わる大きな修正

H4のシリアルナンバーが増加したときに H5のシリアルをリセットするように変更した。以前はこういう連番だった。

  • p01.01
    • p01.01.01
  • p01.02
    • p01.02.02

最後が p01.02.01になるように。


 @2009-12-09

URLフォーマットが変わると影響範囲が広くて困る。自分の日記へのリンクを検出する正規表現をあちこちで書き直した。

Index: core/tdiary/wiki_style.rb
===================================================================
--- core/tdiary/wiki_style.rb	(リビジョン 43193)
+++ core/tdiary/wiki_style.rb	(作業コピー)
@@ -149,7 +149,7 @@
 			hikihtml = HikiDoc::HTMLOutput.new
 			html.gsub!( %r!<a href="(.*?)">(.*?)</a>! ) do
 				k, u = hikihtml.unescape_html($2), hikihtml.unescape_html($1)
-				if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)[^\d]*?#?([pct]\d+)?$/ =~ u then
+				if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)\D*([pct]\d+(?:\.\d+)*)?$/ =~ u then
 					%Q[<%=my '#{$1}#{$2}', '#{escape_quote CGI.escapeHTML k}' %>]
 				elsif /:/ =~ u
 					scheme, path = u.split( /:/, 2 )
Index: core/plugin/00default.rb
===================================================================
--- core/plugin/00default.rb	(リビジョン 43193)
+++ core/plugin/00default.rb	(作業コピー)
@@ -480,7 +480,7 @@
 # make anchor string
 #
 def anchor( s )
-	if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then
+	if /^([\-\d]+)#?([pct][\d\.])?$/ =~ s then
 		if $2 then
 			"?date=#$1##$2"
 		else
@@ -540,7 +540,7 @@
 # make anchor tag in my diary
 #
 def my( a, str, title = nil )
-	date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4}|)\D*([pct]\d+)?$/ )[0]
+	date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4})\D*([pct]\d+(?:\.\d+)*)?$/ )[0]
 	anc = frag ? "#{date}#{frag}" : date
 	index = /^https?:/ =~ @index ? '' : @conf.base_url
 	index += @index.sub(%r|^\./|, '')
Index: plugin/html_anchor.rb
===================================================================
--- plugin/html_anchor.rb	(リビジョン 43193)
+++ plugin/html_anchor.rb	(作業コピー)
@@ -14,7 +14,7 @@
 if @conf.index.empty? or /\/$/ =~ @conf.index
 
 def anchor( s )
-	if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then
+	if /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then
 		if $2 then
 			"#$1.html##$2"
 		else
Index: plugin/my-sequel.rb
===================================================================
--- plugin/my-sequel.rb	(リビジョン 43193)
+++ plugin/my-sequel.rb	(作業コピー)
@@ -487,7 +487,7 @@
 	alias :my_sequel_orig_my :my unless defined?(my_sequel_orig_my)
 	def my(*args)
 		if @my_sequel_active and @my_sequel_date and @my_sequel_anchor and @mode != 'preview' then
-			dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+))?$/)[0]
+			dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+)(?:\.\d+)*)?$/)[0]
 			if dst_date and dst_date < @my_sequel_date then
 				dst_anchor = "#{dst_date}#{frag ? "#p%02d" % frag.to_i : ''}"
 				@my_sequel.add(@my_sequel_anchor, dst_anchor)
Index: plugin/my-ex.rb
===================================================================
--- plugin/my-ex.rb	(リビジョン 43193)
+++ plugin/my-ex.rb	(作業コピー)
@@ -13,9 +13,10 @@
 
 unless @conf.mobile_agent?
 
+alias :my_overwritten_by_my_ex :my
 def my( a, str, title = nil )
 	m = /^(\d{8})\D*(?:([pct])(\d+))?$/.match( a )
-	return '' unless m
+	return my_overwritten_by_my_ex( a, str, title ) unless m
 	_, date, place, frag = *m
 
 	if title.nil? and date and frag and @diaries[date] then

 @2010-03-08: 「ツッコミを入れる」リンクが無効になってしまっていた。

misc/plugin/html_anchor.rbと plugin/00default.rbプラグインを再び修正。

元々if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then
修正ミスif /^([\-\d]+)#?([pct]\d+(?:\.\d+)*)?$/ =~ s then
OKif /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then

「ツッコミを入れる」リンクは YYYYMMDD#c へのリンクなので、cの後ろに数字が続いていない。これをうっかり除外してしまっていた。

最終更新: 2014-12-18T13:09+0900

[Ruby] メソッドの困った上書き

すぐ上のセクションで出てきた(俺が書いたんだけど)こういうの、

alias :my_overwritten_by_my_ex :my
def my
  ...
end

こういう「エイリアス&上書き」が嫌いで、不完全ながらこういうものを書いたことがある。(今でも使っている。セミコロンを抑制する努力をしていない頃に書いたもののようだ。Rubyのバージョンで nameをシンボルにするか文字列にするか振り分けているが、今ならやらない。先頭要素を取り出してそれがシンボルか文字列かを調べる。そして、代入を ifで囲うのではなく ifの結果を代入する)

 overwrite.rb

class ::Object
	def overwrite(name, &block)
		if('1.9.0'<=RUBY_VERSION); name = name.to_sym; else name = name.to_s; end
		if(self.singleton_methods(false).member?(name))
			$dirty = [name, block];
			class <<self
				name, block = *$dirty;
				old = instance_method(name);
				remove_method(name);
#				define_method(name){|*params|
#					block.call(old.bind(self), *params); # ブロックは渡せない? 1.9ならできる?
#				}
				define_method("__overwrite__#{name}__#{block.object_id}", &block);
				define_method(name){|*params|
					send("__overwrite__#{name}__#{block.object_id}", old.bind(self), *params);
				}
			end
		else
			if(true != self.class.ancestors.each{|klass|
				if(klass.methods(false).member?(name))
					klass.instance_eval{
						old = method(name);
						remove_method(name);
#						define_method(name){|*params|
#							block.call(old, *params); # ブロックは渡せない? 1.9ならできる?
#						}
						define_method("__overwrite__#{name}__#{block.object_id}", &block);
						define_method(name){|*params|
							send("__overwrite__#{name}__#{block.object_id}", old, *params);
						}
					}
					break true;
				end
			})
				# failure
				raise;
			end
		end
	end
	private :overwrite
end

グローバル変数は赤色でよく目立つね。$dirtyっていう変数はスコープの断絶を乗り越えるために使ってるんだろう。汚いやり方だ。

もはや自分でも読み解けないが、こうして使う(んだったかな? define_methodしたときはブロックで returnが使えた気がする)。

overwrite(:my){|old, *params|
  # 新しいmyを以下に……

  # 必要なら古い定義も呼び出せる。
  return old.call(*params)
}

継承による overrideであれば、隠された同名のメソッドを superで呼び出せるけど、メソッドの上書きではそれができなくて古い定義を呼び出せない。かといって「alias :foo_bar :foo」「alias :foo_baz :foo」式の無秩序なやり方は見るに堪えない。overwrite.rbがやってることも機械的に名前を付ける以外は同じではあるけども(まだましでしょ?)。

<追記@2009-12-19>コメント部分がなんでコメントアウトされているのかやっと思い出せた気がする。奇しくも自分でその理由を書いていたりするのだが。>「define_methodしたときはブロックで returnが使えた気がする」つまり、新メソッド定義(overwriteメソッドに与えられたブロック)を define_methodでメソッド化しておかなければ returnが使えないからだ。かといって古いメソッド定義を渡すために一段余計な処理を挟まなければいけないから、新メソッド定義(ブロック)をそのままの名前でメソッドにすることもできない。ブロックが保持する bindingに変数(旧メソッドを指している)を注入する方法があれば、ダミーの長ったらしい名前を持つメソッドをこっそり定義する今のやり方から抜け出せるかも。</追記>

俺が知らないだけで 1.9以降では解決策があるんだろうか。こういう現実の問題に対処してほしいなあ。わりと見かける aliasの使い方で、問題含みだと思うんだけど。


一人見つけた。

super=承継元クラスの同名メソッドを呼び出す=とは別に、 overridden=上書きされたメソッドを呼び出す=というのを作るというのはどうでしょう?

こちらはちょっと違う。

aliasによるメソッドの再定義は危険なのでUnboundMethodかextendを使おう - (rubikitch loves (Emacs Ruby CUI))

再ロード対策は考えていなかった。

extendは知らないではないけど……(これが求めていたものなんだろうか?)。


新しい名前を導入しないように extendでやってみた。エラーは出ないけどリンク先のサブタイトルが取得できていない(my-exによる拡張が機能していない)。instance_evalを module_evalにしてみてもだめ。

 my-ex.rb

extend Module.new{|mod| mod.instance_eval{
   define_method(:my){|*params| a, str, title = *params
      # myプラグインを拡張し、リンク先が自分の日記のとき、
      # 可能ならサブタイトルやコメントを取得して title属性にセットします。
   }
}}

オーソドックスにやってみても同じこと。

module MyEx
   def my( a, str, title = nil )
   end
end
extend MyEx

extendする対象を間違えてるんだろうか。Pluginのインスタンスに特異メソッドを定義してるつもりでいるんだけど。いずれにしろ答えにたどり着けないのでは使えないよ。def...end と書いてメソッドの定義を上書きするよりも安全で洗練された代替が欲しいんだから、def...endと書く以上の複雑さは受け容れられない。


 @2009-12-11 aliasとコロン

なんでも、aliasするときにメソッド名にコロンを付けてはいけないらしい。

http://www.loveruby.net/w/RubyCodingStyle.html

なぜ付けるかといえば、ほとんどすべてが「実行される」Rubyにおいて、クラスやメソッドの定義(class...end, def...end)ですら実行時の条件によってふりわけられる Rubyにおいて、構文であること、特別扱いされることが許せないからだ。「alias はメソッド呼び出しではなくて構文である。だからこそ引数の間にカンマがないのだ。そこはわかっていながらなぜエセメソッドのような使いかたをするのか。」それは構文であることへの抵抗なのです(俺の場合)。

「ちなみにどうしてもコロンが付けたければ alias_method を使えばいいんではないかと思う。」

そいつ( alias_method )は知らなかった。コロンだけでなくメソッド名とメソッド名の間にカンマを置くこともできるんだから、喜んでそちらを使いますとも。alias_methodと aliasは使える場所や対象が完全には一致していないけども、aliasでしかできないことをした経験はないから問題はなし。


「aliasでしかできないことをした経験はないから問題はなし」なんて書いたそばからこんな例が……

特異メソッドをalias_methodを使用して別名をつけようとするとエラーになる。(中略) 特異メソッドに別名をつけて退避したい場合は、alias_method (_chain)メソッドではなくてaliasをinstance_evalのブロック内で使用するとよい。

特異メソッドは別、でした。想像だけど extendでプラグインメソッドの上書きができなかったのも特異メソッドがらみだと思うな。


 @2009-12-12

昨日の文章はおかしいな。class...endや def...endという構文を認めながら aliasだけを白眼視する説明になっていないような。

class MyClass; endと書いた場合、MyClassは Classオブジェクトの入った定数(という言語のビルディングブロック)だと認識している。def my_method; endと書いた場合、my_methodはメソッド(という言語の(略)であり、以後、my_methodや my_method()と書いた場合それはメソッドの呼び出しである、というのが Rubyという言語の約束だと認識している。「alias alias_of_my_method my_method」という構文は他の構文との共通性がなく例外的なルールに感じられ、(同じコンテクストで用いられる)includeや private、attr_accessorが Moduleのプライベートメソッドとしてあるなかで構文である必然性が認められず、メソッド名に関するルールを破ってさえいる。そこが許せない。aliasの(メソッドではないから正確には違うが)引数の「alias_of_my_method」や「my_method」を言語としてどう位置づけるのか、そこををはっきりさせないのならばそれは使えない、という主張が aliasのメソッド名部分にコロンを付ける理由。

undefや defined?にも aliasと同じ疑問がある(ただし、undefは defの対称として容易に受け入れられるし、defined?の場合、必然性だけは認められる)。JavaScriptの typeof演算子や delete演算子にすら同じ違和感を覚えている*。コンパイル型言語のように、コンパイル時に評価されるのだという言い訳ができないインタプリタ型言語の弱みだろうか。こういうものを見ると Perlに感じたように言語が突然平板なものに見えてしまって嫌なのだ。そこには対象となるメモリやオブジェクトが存在せず、ソースコードの字面しかないでしょう。

* きちんと定義はされています >「[[8.7 Reference 型 (Reference Type)|http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/8_Types.html#section-8.7]]」

最終更新: 2009-12-28T23:38+0900

[Ruby] 1引数ラムダの冗長さ

もうひとつ、最初のセクションからネタ拾い。こんなのや、

 strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) }

別の場所から持ってきたこんなの

 jsstr = lambda{|s| s.gsub(/["\\]/){|x| "\\#{x}" } }

を、自分はときどき書くようだ。[1,2,3].map{|x| ... } みたいなのを書くことはもっとある。「|x| x.」の部分が冗長だね、っていう話。

省きたいけど、単純に考えるとブロックの中が x. で始まっていなければいけない。適用範囲が狭いんじゃないか?と疑問がわく。Rubyは「オブジェクト指向スクリプト言語」をうたっているからメソッドチェインを促す意味でサポートを行う名目はあるだろう。

メソッド呼び出しの構文をとりながら自身を引数にしてよそのメソッドを呼べるようにしたり(C#3.0の extension method風)、Hikiがすでにやっているように

class String
  def escapeHTML
    CGI::escapeHTML(self)
  end
end

本当にインスタンスメソッドにしてしまったり。CGI::escapeHTML("str") より "str".escapeHTML にしておいた方が(1引数ラムダでの利用に)便利ですよ、という動機付けを言語として行ってもいいと思う。あとは引数の束縛やらラムダの .callと []以外の呼び出し方、ブロックやラムダを用いて定義したブロックを引数にしてメソッドを呼び出したりできれば何かができそうな気がしなくもない予感がなきにしもあらず。

全然関係あるのかないのか、Haskellのポイントフリースタイル(初めて目にしたのは 12月15日頃)を検索してみた。$ で順々に処理をつなげているものがあれば . を使って、途中の処理で必要な引数を押し出していくようにしながら一つの処理に書き換えるのだろうか。ポイントフリーなのにポイント塗れとはこれ如何に? 「firstNLines = (.lines).((unlines.).take)」のように明示されなくなった引数 Nはわかりにくくない? 2引数の関数はポイントフリースタイルで書けるの?『ふつける』を通し読みしただけで、モナドもわからず、一行たりとも Haskellコードを書いたことのない人間には疑問がいっぱいです。