Rubyでわかるマギカ世界の魔法少女システム論
「まどか☆マギカから考えるメカニズムデザイン」も合わせてご参照ください。
class Hope attr_reader :value def initialize(value) @initial_value = value @value = value value_changed end def add(value) @value += value value_changed end def despair @value = - @initial_value end def value_changed=(block) @on_value_changed = block end def value_changed @on_value_changed.call(self) if @on_value_changed end end class Human def initialize h = Hope.new(10) h.value_changed = ->(hope) do check_hope end self.singleton_class.class_eval do define_method :hope do h end end end def check_hope end def to_s "Human:#{object_id}" end end class QB private_class_method :new def self.generate @qb ||= new end def sign(girl, contract) girl.singleton_class.class_eval do include Magica end contract.call(girl) end module Magica def use_magic hope.add(-1) end def give_hope(person, value) hope.add(-value) person.hope.add(value) end def check_hope become_witch unless hope.value > 0 end def become_witch self.singleton_class.class_eval do include Witch end hope.despair freeze end def to_s "Magica:#{object_id}" end end module Witch def scatter_despair(person) person.add_hope(-person.hope.value) end def self.included(mod) mod.freeze end def to_s "Witch:#{object_id}" end end end qb = QB.generate all_magica = [] 100.times do magica = Human.new qb.sign(magica, ->(magica){}) 10.times{ magica.use_magic } all_magica << magica end madoka = Human.new all_magica << madoka qb.sign(madoka, ->(girl) do QB::Magica.class_eval do define_method :check_hope do if hope.value < 0 girl.give_hope(self, -hope.value) freeze end end end all_magica.each do |m| if m.hope.value < 0 girl.give_hope(m, -m.hope.value) end end end)
少し補足
- singleton_classは特異クラスを返します
- includeは不可逆です
- QBはシングルトンパターンに従いました
- このプログラムは無限再帰します。
RubyOnRailsとjQueryで簡単クロスドメインajax
Railsのサーバーと非RailsのWebサービス(どちらも自前で管理)のクロスドメインajaxをやってみたら、思いのほか簡単だったので、記事にしてみます。
なお、クロスドメインに関するセキュリティ的な話題はいろいろあると思いますので、そのあたりは自分で調べてみてください。
サンプルコードに関して
こちらにサンプルコードを載せたので、もしよければ参考にしてください。 https://github.com/sakairyota/jsonp_example
本文中のコードは下記のファイルに含まれています
app/controllers/entries_controller.rb config/routes.rb public/test_jsonp.html
public/test_jsonp.html はRailsのサーバーから開くと同一ドメインの通信、ファイルシステムから開くとクロスドメインとなり、気軽にそれぞれを試すことができるようになっています。
クロスドメイン制約
JavaScriptにはクロスドメインの制約があります。たとえは、example.comにあるファイルを、はてな(hatena.ne.jp)のページに配置したJavaScriptから通信して取得することはできないようになっています。
JSONP
しかし、JSONPを使うとクロスドメインの壁を超えることができます。JSONPではsrcにファイルのURLを含めたscriptタグを生成して、JavaScriptを実行させます。たとえば、hatena.ne.jpで、srcにexample.com/hogeのファイルを指定したscriptタグを生成すると、生成した時点で、example.com/hogeのJavaScriptが実行されます。
JavaScriptでの通信はドメインを超えることができないのに対し、Scriptタグで外部のドメインのスクリプトを読み込むことができることを利用しています。
RailsでJSON
Railsでは、標準ではviewsに配置されているhtmlのテンプレートを元にhtmlを返しますが、respond_toを使うと、JSONやxmlを返すことができるようになります。
class EntriesController < ApplicationController def show @entry = {id: params[:id], title: "entry#{params[:id]}", content: "This is content ##{params[:id]}"} respond_to do |format| format.json { render json: @entry} end end end
説明のため @entry にハッシュを代入していますが、実際には Entry.find params[:id] などが入ります。
resources :entries, only: :show
ルーティングを上のように設定すると、http://localhost:3000/entries/1.json というように.jsonを付けてJSONを取得することができます。
{ "id": "1", "title": "entry1", "content": "This is content #1" }
RailsでJSONP
そこで、コードを次のように変更します。
class EntriesController < ApplicationController def show @entry = {id: params[:id], title: "entry#{params[:id]}", content: "This is content ##{params[:id]}"} respond_to do |format| format.json { render json: @entry, callback: params[:callback]} end end end
差分
- format.json { render json: @entry} + format.json { render json: @entry, callback: params[:callback]}
すると、JSONPが使えるようになります。http://localhost:3000/entries/1.json?callback=hoge でアクセスすると次のようなJavaScriptが返ってきます。
hoge( { "id": "1", "title": "entry1", "content": "This is content #1" } )
参考にした記事(2年も前ですが): http://labs.s-koichi.info/blog/archives/2008/03/04/2231-60.php
jQueryからajax(JSONP)
jQueryでは $.ajax を使って、簡単にAjaxを実装することができます。
例えば、次のように $.ajax に url と success のコールバックを指定すると、先ほどのjsonを読み込んで、htmlに書き出すことができます。
$.ajax({ url: 'http://localhost:3000/entries/1.json', success: function(json){ $('#entry .entry_id').text(json['id']); $('#entry .title').text(json['title']); $('#entry .content').text(json['content']); } });
jQueryからajax(JSON)
そこで、$.ajax で dataType のパラメータにjsonpを指定します。すると、JSONPでのアクセスとなり、クロスドメインのAjaxが実現できます。
$.ajax({ url: 'http://localhost:3000/entries/1.json', dataType: 'jsonp', success: function(json){ $('#entry_jsonp .entry_id').text(json['id']); $('#entry_jsonp .title').text(json['title']); $('#entry_jsonp .content').text(json['content']); } });
差分
+ dataType: 'jsonp',
ね、簡単でしょう?
最後に
ajaxでデータを取得するところまでは簡単にできました。
これを実践した時には、この後の受け取ったデータを処理するところに手間取ったということは、「簡単」をうたっているのに対して水をさすので、 この記事では書かないでおきます。
JQueryのソースコードを読んでJavaScriptを勉強!
最近JavaScriptをさわる機会が多く、もうちょっと勉強しないといけないなと思っています。なので、jQuery(1.7.1)のソースコードを読んでみました。
読んで思ったところをまとめてみたいと思います。勉強したことのまとめです。
jQueryの全体像
(function( window, undefined ){ // Use the correct document accordingly with window argument (sandbox) var document = window.document, navigator = window.navigator, location = window.location; var jQuery = (function() { // (省略) // jQuery の本体のfunctionを作ってreturnする })(); // (省略) // jQueryのいろいろな機能の実装 window.jQuery = window.$ = jQuery; // (3行分くらい省略) })( window );
基本骨格はこんなところでしょうか?全体が無名関数になっていて、直後に呼びたすという、JavaScriptのお決まりのパターンになっています。
無名関数に window を渡して、引数windowで受けているので、処理本体中の「window」は引数のwindowになっています。第2引数は、渡されていないので、引数undefinedはundefinedとなります。
意図が完全にはわからなかったので、うまく説明できないですが、このあたりを読んだらなんとなく納得しました。
- http://d.hatena.ne.jp/gnarl/20110213/1297523372
- http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1040179045
その後、ローカル変数jQueryに機能を実装していって、最後にjQueryをwindow.jQuery、window.$ に代入して、jQueryを公開しています。このあたりも定石だと思いますが、グローバルの名前空間への影響を最小限にする方法として参考になります。
jQuery.extendによる構造化
jQuery.extend({ noConflict: function( deep ) { //(省略) }, isReady: false, //(省略) });
jQuery.extend({ Deferred: function( func ) { //(省略) });
jQuery.fn.extend({ //(省略) });
ひたすらjQuery.extend、そして、jQuery.fn.extendです。コアとなるjQueryのオブジェクトを作って(jQuery.extendも含まれる)、そこに機能を追加していくという実装になっているようです。
機能のまとまりごとに構造化されているので、私みたいな人でも、9000行あるこのソースコードの必要な箇所だけ読むとかできそうです。
(9000行くらい読めとか言われてしまいそうですが><、それは置いといても必要な箇所だけ読めばいいようにしておくことは大切な事だと思います。)
JavaScriptの論理演算子
RubyやPHPと違ってJavaScriptには詳しくないので、基本的なことですがはじめて知りました。JavaScriptはそろそろ、ちゃんと言語の勉強しないといけない感じになってきました。
0 || 1
は1として評価されます。trueではありません。
演算子は、まず左側が評価されて、trueならば左側の値が返ります。falseならば、右側の値が評価されて返されます。 |
これは、Rubyと一緒ですね^^。ちなみに、PHPは||演算子はbooleanを返します。
PHPのごとく
$foo = $foo ? $foo : 'default値';
とかやらなくても
foo = foo || 'default値';
とかできるので便利です。
ちなみに、Rubyだと、
foo ||= 'default値'
です*1。短いですね。
callとかapplyとか使いこなせる気しない
toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, //(省略) jQuery.fn = jQuery.prototype = { //(省略) toArray: function() { return slice.call( this, 0 ); }, //(省略) };
このあたりで、callとかapplyとか使われてます。
とかを読むと、Arrayの機能を使うのにcallとか使うと書いてあって、まさにという感じですね。
このあたりは要勉強です。配列っぽいオブジェクトが作りたくなった時、このあたりのお話を思い出そうと思います。
split + each
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
文字列を+で連結して改行してるのが見やすくていいですね。
そして、Rubyの%wが便利だと改めて思いました。
%w(blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu).each do |name|
というわけで、JavaScriptの記事に見せかけた、Rubyの布教記事でした。
Rubyのnilはおともだち
Rubyのnilは便利ですよというお話です。
Ruby人口を増やしたいなぁということで、とても簡単な話題でいきます。(これでハードルが下がった)
Ruby Advent Calendar jp: 2011 の18日目の記事です。17日目はser1zwさんです。
インスタンス変数
さて、おもむろにirbを開いて、@hogeと打ってみましょう。nilが返ってきます。
@hoge
定義されていないインスタンス変数を参照すると、nilが返ってきます。
では、次のコードをご覧ください。printメソッドの中でインスタンス変数を利用する際に、nil?でnilのチェックをしてから利用しています。
class Message attr_accessor :to, :message def print @to = default_to if @to.nil? @message = '' if @message.nil? puts "To: #{@to}" puts @message end def default_to 'sakairyota' end end m = Message.new m.message = 'hello' m.print
重要なことは、あらかじめインスタンス変数に値がセットされてなくても、printメソッドが使えることです。
printはもっと簡単に書けます。
def print @to ||= default_to puts "To: #{@to}" puts "" puts @message end
if文が ||= に変わりました。これは、
@to = @to || default_to
の意味で、@toがnil(またはfalse)の時だけ、@toにdefault_toを入力するという意味になります。Rubyではデフォルト値の代入でよく使う書き方です。
今さりげなく「またはfalse」と書きましたが、Booleanを扱う時は||=ではなくnil?を使いましょう。
遅延読み込み
class Movie def initialize(filename) @file = load_file(filename) end def use_file # @fileを使った処理 end def load_file(filename) # とても重たい処理 end end
上のコードでは、インスタンスの初期化時に重たい処理を行なっています。しかし、もしかすると、初期化だけして結果的にはファイルを使わないかもしれません。そこで、下のようにします。
class Movie def use_file @file ||= load_file(filename) # @fileを使った処理 end def load_file(filename) # とても重たい処理 end end
最初に use_file を実行するときには @file はnilなので、load_fileが実行されますが、2回目以降は@fileに中身が入っているので、load_fileはパスされます。また、use_fileが実行されなければ、load_fileは一回も実行されません。
小ネタ
残りは小ネタです。
nil.to_i とか、nil.to_sとか、良い感じに取り計らってくれるようです。便利です。(参考... http://doc.okkez.net/static/192/class/NilClass.html )
空のメソッドはnilを返します。
def hoge end p hoge #=> nil
defとかclassとかもnilを返すようです。
Rubyではif文も値を返しますが、
a = if true 'true' else 'false' end p a #=> 'true'
条件に引っかからなければnilを返します。
a = if false 'false' end p a #=> 'nil'
if文で値を代入することはあまりないかと思いますが、こんなのはどうでしょうか?
def hoge if false 'hoge' end end
if文全体はnilを返すため、メソッドhogeもnilを返します。
1行で書くとこんな感じです*1。
def hoge(condition, value) value if condition end
これを理解すると、次のコードが間違いだということが分かります。(これでハマったことがあります。)
# 戻り値として、@record を返す def hoge @record @record.save if some_condition # @record.save は @record自身を返す end
some_conditionがfalseの時、@recordではなくnilが返ってしまいます。
*1:可読性的にはどうなんでしょう?
まどか☆マギカから考えるメカニズムデザイン
メカニズムデザインとは、Wikipediaによると。「ゲームにおいてある特定の目的を達成するために、ルールを設計することである。」とのことです。大学の時に少し勉強したような気がする…。
まどか☆マギカの世界では、キュゥべえ(以下QB)はエントロピーの増大という目的のために、魔法少女を魔女にするよう落とし入れようとしているのに対し、改変後の世界ではQBと魔法少女らが協力する穏やかな世界になっています。改変前の世界ではQBによって悲劇が次々と起こったのに対し、改変後にはそのようなことはなくなりました。この違いを、メカニズムデザインの観点から考えてみたいと思います。
クラスのみんなには内緒だよっ!
注意事項
- まどか☆マギカのネタバレが含まれています
- 数式が出てきますが難しくないです
- いっぽうで厳密な議論ではないです
まどか☆マギカのおさらい
- QBは少女に対し「僕と契約して魔法少女になってよ」とせまります。
- 少女は契約すると、魔法少女になって希望をひとつ叶えることができます。
- しかし、魔法少女は魔女とたたかう必要があります。
- 魔法少女は絶望すると、魔女になります。魔法少女が魔女になる時、QBはエネルギーを得ることができます。これがQBの目的です。
- 希望と絶望は差し引き0です。
- まどかの世界を改変して、魔女が生まれる前に消え去る世界になった
- まどかの願いによる改変後の世界では、魔獣を倒すという目的で魔法少女とQBは協力しています。
それぞれについて考えてみましょう。
僕と契約して魔法少女になってよ
QBは少女に契約を迫っています。ここで重要な点は、少女に契約するか否かを決定する権利があることです。QBが重要な事実を黙っているにも関わらずです。
どういうことかというと、この時点で、希望を叶える代わりに魔女と戦う必要があるとことは明かさるため、少女は、希望を叶えるメリットと、魔女と戦うデメリットを考慮して魔法少女になるかどうか、自分で決めることができます。これはとても重要な点です。
重要なので数式にしてみましょう。大丈夫です。中学生にも分かる式です。
少女の利益さやか = 希望さやか - 魔女の恐怖さやか
ちなみに、右下に「さやか」という文字を付け加えたのは、「さやかが得る利益」という点をはっきりさせる為です。そのため、杏子ならば、
少女の利益杏子 = 希望杏子 - 魔女の恐怖杏子
になります。
これで、もし利益があるのであれば、少女は契約をした方がいいし、そうでなければ契約しないほうがいい。契約の基本ですね。
希望と絶望の差し引きゼロ
しかし重大な事実が隠されています。「希望と絶望の差し引きゼロ」の原理です。さやかが最期に「誰かの幸せを祈ったぶん、他の誰かを呪わずにはいられない」と言っているように、魔法少女は希望を得たのと同じだけ、絶望することになります。数式で表すと次のようになります。
希望さやか - 絶望さやか = 0
この新事実をもとに、少女の利益の式を改めて立ててみます。なお、魔女の恐怖も絶望に含まれると考えてみます。
(本当の)少女の利益さやか = 希望さやか - 絶望さやか = 0
絶望さやか = 魔女の恐怖さやか + それ以外の絶望さやか
このことから、契約しても、とくにデメリットもないと言えます。
ところで、「誰かの幸せを祈ったぶん」と言っていますが、もう少しこれもきちんと表してみましょう。さやかがAさんの幸せの祈ることを 希望さやか, A と表してみます。これは、これと同等の絶望さやか, Aと対になっています。
希望さやか, A - 絶望さやか, A = 0
これにより、Aさんは希望を得て、さやかは絶望を得ます。つまり、さやかは希望を得ずに、絶望のみを得ます。これも先ほどに式に取り込んでみましょう。
少女の利益さやか = 希望さやか - 絶望さやか - 絶望さやか, A = - 絶望さやか, A
ついに、利益が0以下になりました。お人好ししなければいい*1とは言え、契約するメリットはありませんね。
ちなみに、Σ が分かる人には、こちらの方がより正確です。
少女の利益さやか = 希望さやか - 絶望さやか - Σ 絶望さやか, i = - Σ 絶望さやか, i
エントロピー
さて、次はQBについて考えてみましょう。QBは第9話でいろいろ御託を並べていますが、この議論で重要な部分は「…エネルギーを探し求めてきた」「希望と絶望の相転移」「ソウルジェムとなった魂は、燃え尽きてグリーフシードへと変わる瞬間に膨大なエネルギーを発生させるんだ」あたりです。
単純化して言えば、絶望がすなわちQBの利益です。
QBの利益 = 絶望さやか + Σ 絶望さやか, i
QBの利益にはさやかちゃんしか現れていません。より正確には他の魔法少女について考える必要があります。ただし、この考察では、QBと魔法少女の2人を考えれば、メカニズムを解き明かすには十分です。だから、
その必要はないわ。
グリーフシード
少し脇道にそれますが、QBがグリーフシードを「きゅっぷい」と食べてるシーンがあります。作品から分かる事実と言うよりは、私の個人的な仮説ですが、QBはグリーフシードからもエネルギーを得ることが出来るのではないかと思っています。改変後の魔獣との戦いは、まさにそのような原理なのではないかと思っています。とはいえ、希望と絶望の相転移のほうが効率がよさそうです。
さやか得たグリーフシードによるエネルギーをグリーフシード(さやか)として、これを数式化してみます。
QBの利益 = 絶望さやか + グリーフシードさやか ≒ 絶望さやか
ゲーム構造
長かったですが、これですべての情報が出揃いました。
QBと魔法少女の利益は次のようになっています。
QBの利益 = 絶望さやか
本当の少女の利益さやか = 希望さやか - 絶望さやか - Σ 絶望さやか, i = - Σ 絶望さやか, i ≦ 0
これによれば、少女は魔法少女にならないのが合理的で、その結果QBは利益を得ることができません。という均衡状態になるはずでした。
しかし、ここで問題があるのです。少女はこの構造を知らされていません。少女は次の部分しか知らないのです。
少女の利益さやか = 希望さやか - 魔女の恐怖さやか
もし、これが0を上回れば、少女は契約した方がいいと判断してしまいます。事実とは異なるのにも関わらずです。
ここで、もう一つ重要なポイントがあります。QBが少女に正しい説明をするべきか否かという点です。
QBの視点に立って考えると、QBが正しい情報を伝えると、少女は契約しないことが合理的となり、その結果、その少女の絶望というQBの利益を得ることができなくなってしまいます。一方で、QBが正しい情報を伝えないと、少女は契約をすることになり、結果としてQBは利益を得ることができます。
つまり、QBにとっては正しい情報を伝えない方が合理的なのです。
「正しいことを言わない(あるいは嘘を言う)ことが合理的」という状況は、メカニズムとしてはよくないのです。メカニズムデザインの観点からは、このような問題は避けなければいけません。
女神まどかと改変後の世界
さて、カニズムがよくないのであれば、新しいメカニズムを考えなければならなりません。
まどかは、「わたしは、すべての魔女を生まれる前に消し去りたい。すべての宇宙、過去と未来のすべての魔女を、この手で」という願いで魔法少女になりました。
これが新しいメカニズムです。式にしてみます。
少女の利益杏子 = 希望杏子 - 絶望杏子 - Σ 絶望杏子, i + 希望まどか, 杏子
そして、
希望まどか, 杏子 = 絶望杏子 + Σ 絶望杏子, i
ゆえに、
少女の利益杏子 = 希望杏子
となりました。
QBには本当のことを言わないメリットがなくなりました。
さらに、QBの利益を考えてみると、
QBの利益 = 絶望杏子 + Σ 絶望杏子, i
のはずだったのですが、女神まどかにかき消されてしまいました。
QBの利益 = 絶望杏子 + Σ 絶望杏子, i - 希望まどか, 杏子 = 0
ただし、改変後の世界でグリーフシードの代わりのキューブからQBはエネルギーを得られるようです。
そのため、QBの利益は、
QBの利益 = キューブ杏子
となります。
まとめると、
少女の利益杏子 = 希望杏子 QBの利益 = キューブ杏子
となります。QBは本当のことを言っても、少女は契約した方が合理的で、QBにとっても利益となります。
前よりも、いいメカニズムになりましたね。
なお、QBの利益に関しては、おそらく次の式のほうが正確だと思います。
QBの利益 = 絶望杏子 + Σ 絶望杏子, i - Σ 希望i, 杏子 + キューブ杏子
円環のお断り
ところで、立式には怪しげな点が少なくとも一ヶ所あります。そこがゴニョゴニョしてるとことが奇跡なんだと思っていますが。
その奇跡を解き明かしてみましょう。
希望まどか, 杏子 = 絶望杏子 + Σ 絶望杏子, i
という式がありましたが、これはまどかにも当てはまります。
希望まどか, まどか = 絶望まどか + Σ 絶望まどか, i
希望まどか, まどか は、まどかがまどか自身に与える希望ですね。これが、「私だって絶望する必要なんてない!」の裏付けとなる部分です。
さて、Σ 絶望まどか, i には、絶望まどか, まどかも含まれます。そして、絶望と希望は差し引きゼロです。
Σ 絶望まどか, i = Σi≠まどか 絶望まどか, i + 絶望まどか, まどか = Σi≠まどか 絶望まどか, まどか + 希望まどか, まどか
これを元の式に代入すると、
希望まどか, まどか = 絶望まどか + Σi≠まどか 絶望まどか,i + 希望まどか, まどか
両辺から 希望(まどか,まどか) を引くと
0 = 絶望まどか + Σi≠まどか 絶望まどか,i = 絶望まどか + Σi≠まどか 絶望i + Σ 絶望i, j
ところで、ここまであえては述べなかったのですが、絶望 > 0 ですよね。とするなら、矛盾してませんか?いいえ、奇跡です。
最後に
もしかすると、一部の数式の立て方には疑問があるかもしれません。しかし、ここで伝えたい事は、「数式に落として考える」という考え方の重要性です。そして、数式を拠り所にして、システム全体のデザインを考える重要性です。
このように現実世界を解析するためには、多少細部を削ぎ落してでも簡潔な数式にした方がいいです。メカニズムデザイン等のお話はただでさえ複雑なので、もとのモデルが複雑だと手に負えなくなってしまいます。
あと、もろもろの事情によりさやか(と一部あんこ)を例にして説明しましたが、まどほむ派です。誤解なきようお願いいたします。
test.html
久しぶりにWindowsのPCを開いたら、test.html というのがあって、中身を確認してみようと思ったら、ブラウザが固まりましたw
腹が立ったので、ファイルの中身を日記に書きます。ちなみに、このファイルを書いたのはもちろん自分自身です。
<script langage='javascript'> var x = function(y){ return function(z){ document.write('test<br />'); return y(z); }; }; while(true){ x = x(x); } </script>
原文ママです。タイポやツッコミどころがある気がしますが、そっとしておいてください。langageじゃなくてlanguageだろとか、htmlのタグはダブルクォーテーションだよねとか、そういうツッコミは不要です。
追記
シングルクォーテーションでもいいらしい。突っ込まれた…
スクリプト解説
このスクリプトを書いたのは、これをやりたかったからだと思います。↓
x = x(x);
「JavaScriptって、関数を渡して関数を返す関数が書けるのか。すごーい!」というノリで書きました。
ちなみに最初のxについて説明すると、xに引数をひとつ取る関数yを渡すと、引数をひとつとって、その値をyに与えて実行する関数が返ってきます。つまり、xに関数yを渡すと、yを実行する関数が返ってきます。
xを簡潔にいうなら、「引数として受け取った関数を実行する関数を返す関数」です。
だから、xにxを渡すと、「『引数として受け取った関数を実行する関数を返す関数』を実行する関数」が返ってきます。これをxに代入しています。
そして、どさくさに紛れて「test<br />」も出力しています。
ちなみに、2週目は、「『引数として受け取った関数を実行する関数を返す関数』を実行する関数」を実行して、その引数に、「『引数として受け取った関数を実行する関数を返す関数』を実行する関数」を渡しています。すると、「引数として受け取った関数を実行する関数を返す関数」が実行されて、その引数は「『引数として受け取った関数を実行する関数を返す関数』を実行する関数」になります。そのため「『「引数として受け取った関数を実行する関数を返す関数」を実行する関数』を実行する関数」が返ってきます。
整理してみると分かりやすいですね*1。
しかし、このプログラムを書いた人が、ここまで理解して書いていたとは思えません。
最後に補足
ちなみに、このプログラムが止まらないのは、上のトリックは全く関係なく、ただのwhile(true)による無限ループです。
*1:だけど、私にはよくわかりません >
TDDBC横浜の感想
TDDBC横浜にスタッフとして参加してきました。
TDD自体がはじめてなわけではないですが、まだまだ経験が浅いので、いい勉強になりました。
個人的に勉強になった点を中心にまとめます。ある程度TDDをやり始めた人がハマる点、この手があったかと思う点が中心になると思います。
なお、個人的には野球に詳しくなかったり、好きじゃなかったり、苦手だったりするので、別の例で書きます。
レッド、グリーン、『リファクタリング』
TDDというと、まずテストを書いてレッドになるのを確認し、実装をしてグリーンにし、その後リファクタリングをしてコードを整理します。これが、レッド、グリーン、リファクタリングです。
レッド、グリーンには気が向いていたのですが、その直後にリファクタリングをするということにはこれまで意識がまわっていませんでした。
(無意識に多少はやっていたような気もしますが。)
仮実装
テストコードもバグります。テストを書いたことがある人なら誰でも経験済みだと思います。
例えば、文字列の数を3倍して文字列で返すTripleモジュールのtripleメソッドを作ろうとしましたが、9とqをタイポしてしまいました。
describe Triple do describe '.triple' do subject {Triple.triple '3'} it {should == 'q'} end end
実装前にテストを書いたら、Tripleという定数がないと言われてしまいますし、空のtripleメソッドを作ったところでも、戻り値が違うと言われて、順調に進んでいるように見えます。しかし、いざ実装してみても、なぜかグリーンになりません。
そこで、仮実装です。
module Triple def self.triple(n) end end
の状態から、一気に実装せずに、
module Triple def self.triple(n) '9' end end
こうします。こんなの当然テストが通るはずです。が、通りません。
ここまでシンプルなメソッドだと、メソッドがバグっているというよりも、テストがバグっていることを疑うことのほうが自然です。
ここでエラーメッセージをよく見てみると、'q'とか書いてあります。これで、テストコードの間違いも見つけることができました。
describe Triple do describe '.triple' do subject {Triple.triple '3'} it {should == '9'} end end
ここでnが4の場合のテストも書くと再びレッドになるので、そこから実装を始めます。このような方法を、三角測量というそうです。
describe Triple do describe '.triple' do context 'when n is 3' do subject {Triple.triple '3'} it {should == '9'} end context 'when n is 4' do subject {Triple.triple '4'} it {should == '12'} end end end
module Triple def self.triple(n) (n.to_i * 3).to_s end end
パラメタライズドテスト
しかし、このようなテストを書くと、冗長な気がしてしまいます。
subject {Triple.triple '3'} it {should == '9'}
と
subject {Triple.triple '4'} it {should == '12'}
は重複しているように見えます。
そこで、RSpecではletを使うんだということを教えてもらいました*1。
describe Triple do describe '.triple' do subject { Triple.triple n } context 'when n is 3' do let(:n) { '3' } it {should == '9'} end context 'when n is 4' do let(:n) { '4' } it {should == '12'} end end end
先ほどよりも、'3' を渡したら '9' が返ってきて、'4' を渡したら '12' が返ってくるということが明確になったと思います。beforeの前処理があると、さらにありがたみが分かる気がします。
テストをパラメタ化して、ある値に対してある値が返ってくるという組みを渡すとテストされることを、パラメタライズドテストというようです。
その観点からすると、この例だと少し弱い気がします。
もっと良い書き方が分かる人がいたら教えて下さいm(_ _)m
パラレルチェンジ
リファクタリングをすると、これまで動いていたテストが真っ赤っかになることがあります。クラスやメソッドの仕様を変えると、当然そのクラスやメソッドのテストは落ちることになります。
そこでよくやりがちなのが、えいやとテストコードにも手を加えることです。テストのリファクタリングも重要なのですが、両方を一気に変えてしまうのは危険だなと思っていました。
そこで、パラレルチェンジをするといいそうです。
まず、古いコード、テストを残したまま、それと同等の新しいコードでのテストを書きます。そして、リファクタリングします。両方合わせてテストが通ることを確認してから、古いコードを消します。
すると、安全になりました。
最後に
スタッフとして参加しましたが、スタッフというのはおいしい立場ですね。
ぜひ参加したいイベントがあるのならば、スタッフに応募するといいというのは、やっぱり本当ですね。
番外編(Rubyのカッコ)
(うろ覚えなので、セリフはイメージです。脚色があるかもしれません。)
「Rubyを使っていると、Javaでカッコを忘れる」という話から…
@sakairyota「昔はRubyのカッコを省略出来るだけ省略してたけど、どうなんだろう?最近はすこし書くようになったけど。」
@1syo さん 「@sakairyota はカッコを省略しすぎ。」
@sakairyota 「Rubyでカッコをどこまで書くか最近迷ってるんですけど、どうなんですか?」
@t_wada さん 「メソッド定義のカッコを書くとか、」
@sakairyota 「メソッド定義のカッコは最近書くようになったけど、メソッド呼び出しは迷ってます。」
@t_wada さん 「メソッド呼び出しとして書くときはカッコを付けて、宣言的に書くときはカッコを付けない。カッコをつけるとgrepしやすい」
@sakairyota (!! grepという観点があったか!)
@sakairyota 「Rubyの『(』はメソッドの直後に書かないといけないから、『(』もつけてgrepするんですね!」
@t_wada さん 「そうそう」
最近、git grepとか使い始めるようになってきたけど,さて、どうしようかな…
*1:Groovyだときれいに書けるとかなんとかという話が