-
Notifications
You must be signed in to change notification settings - Fork 2
/
ruby-metaprogramming1.html
343 lines (258 loc) · 11.2 KB
/
ruby-metaprogramming1.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<meta name="description" content="Akkunchoi.github.com : " />
<link rel="stylesheet" type="text/css" media="screen" href="/stylesheets/rainbow-github.css">
<link rel="stylesheet" type="text/css" media="screen" href="/stylesheets/stylesheet.css">
<title>Ruby メタプログラミング (1. クラス) | akkunchoi@github</title>
<link rel="alternate" type="application/rss+xml" title="RSS Feed for mysite.com" href="/feed.xml" />
<script type="text/javascript" src="/javascripts/rainbow-custom.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function($){
//$('pre > code').highlight({source: 1, zebra: 1, indent: 'space', list: 'ol'});
});
</script>
</head>
<body>
<!-- HEADER -->
<div id="header_wrap" class="outer">
<header class="inner">
<a id="forkme_banner" href="https://github.com/akkunchoi">View on GitHub</a>
<h1 id="project_title">
<a href="/">akkunchoi.github.com</a>
</h1>
<h2 id="project_tagline"></h2>
</header>
</div>
<!-- MAIN CONTENT -->
<div id="main_content_wrap" class="outer">
<section id="main_content" class="inner">
<article class="posts">
<header>
<h1>Ruby メタプログラミング (1. クラス)</h1>
<ul class="tags">
<li class="inline archive_list"><a class="tag_list_link" href="/tag/ruby">ruby</a></li>
</ul>
</header>
<div class="posts-content">
<p>書籍「メタプログラミングRuby」を参考に、メタプログラミングの方法を簡単にまとめます。</p>
<p><a href="http://www.amazon.co.jp/gp/product/4048687158/ref=as_li_tf_il?ie=UTF8&camp=247&creative=1211&creativeASIN=4048687158&linkCode=as2&tag=ac0689-22"><img border="0" src="http://ws.assoc-amazon.jp/widgets/q?_encoding=UTF8&ASIN=4048687158&Format=_SL160_&ID=AsinImage&MarketPlace=JP&ServiceVersion=20070822&WS=1&tag=ac0689-22" /></a><img src="http://www.assoc-amazon.jp/e/ir?t=ac0689-22&l=as2&o=9&a=4048687158" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></p>
<h2 id="section">文法</h2>
<p>書籍「メタプログラミングRuby」には文法に関する説明はありません。
クラスに関する文法は <a href="http://doc.ruby-lang.org/ja/1.9.3/doc/spec=2fdef.html">http://doc.ruby-lang.org/ja/1.9.3/doc/spec=2fdef.html</a> を参考にして下さい。</p>
<h2 id="section-1">オープンクラス</h2>
<p>Rubyは既存のクラスを拡張することができます。</p>
<pre><code data-language="ruby"># Stringクラスを拡張する
class String
def to_alnum
gsub /[^\w\s]/, ''
end
end
</code></pre>
<p>ただし、それが<strong>できる</strong>とはいえ、やる<strong>べき</strong>かどうかはまた別。</p>
<ul>
<li>やるべきかどうかの基準
<ul>
<li>(Stringの例の場合)すべての文字列が持っていても問題ない汎用的な機能だろうか?</li>
<li>特定のドメインに特化したメソッドを追加しようとしてないだろうか?</li>
</ul>
</li>
<li>他の方法には…
<ul>
<li>別の AlphanumericString クラスを定義する</li>
<li>特異メソッドを使って、特定の String インスタンスにだけメソッドを追加する。</li>
</ul>
</li>
</ul>
<h2 id="section-2">再オープン</h2>
<p>クラス定義とその他のコードには違いがありません。</p>
<p>クラス定義も式です。<code>class</code>にもきちんと戻り値があります。</p>
<pre><code data-language="ruby"># classの戻り値
class A
'hoge'
end
=> "hoge"
</code></pre>
<p>同じクラスに対して何度も<code>class</code>で呼ぶことができます。</p>
<pre><code data-language="ruby"># クラスCを3回定義している?
3.times do
class C
puts "Hello"
end
end
# Hello
# Hello
# Hello
</code></pre>
<p>この場合、定義を繰り返しているのではなく、</p>
<ul>
<li>クラスが存在していない時は、クラスを定義する。メソッドがあればメソッドも定義する。</li>
<li>クラスが存在している時は、既存のクラスを<strong>再オープン</strong>して、メソッドを追加する。</li>
</ul>
<p>という動作を行います。</p>
<p>class キーワードはクラス宣言というより、スコープ演算子のようなもの。</p>
<p>既存のクラスを再オープンして、いつでもその場で修正できることを<strong>オープンクラス</strong>と呼びます。</p>
<h2 id="section-3">オープンクラスの問題点: モンキーパッチ</h2>
<p>オープンクラスはすでに用意されているメソッドを簡単に書き換えることができます。
そのため、知らぬ間に依存している機能の動作を変更することで、バグにつながりやすい。</p>
<p>クラスへの安易なパッチを蔑称して<strong>モンキーパッチ</strong>と呼びます。</p>
<p>モンキーパッチは「意図せずに起きる」のと「意図的に適用」する場合があります。</p>
<p>便利だけど危険がつきまとうので、
独自のメソッドを定義する前に、クラスに既存のメソッドがないかを注意深く確認することが大事です。</p>
<h2 id="section-4">インスタンス変数とインスタンスメソッド</h2>
<p>インスタンス変数はハッシュのキー・バリューのようなもの。セットして初めて存在します。
インスタンス変数はオブジェクトに、インスタンスメソッドはクラスに住んでいます。</p>
<pre><code data-language="ruby">#
class MyClass
def my_method
@v = 1
end
end
obj = MyClass.new
obj.instance_variables # [@v]
obj.methods # ["my_method"]
</code></pre>
<h2 id="section-5">クラスはオブジェクト</h2>
<p>(もう少し説明足したい)</p>
<p>クラスは Class クラスのインスタンスです。</p>
<pre><code data-language="ruby"># classメソッド
"hello".class # => String
String.class # => Class
Class.class # => Class
</code></pre>
<p>Classはnew, allocate, superclassを追加したモジュールに過ぎない!</p>
<pre><code data-language="ruby"># Classに定義されているインスタンスメソッド
inherited = false
Class.instance_methods(inherited) # => [:superclass, :allocate, :new]
</code></pre>
<p>Stringのクラス階層</p>
<pre><code data-language="ruby"># Stringのクラス階層
String.superclass # => Object
Object.superclass # => BasicObject
BasicObjct.superclass # => nil
</code></pre>
<p>Classのクラス階層</p>
<pre><code data-language="ruby"># Classのクラス階層
Class.superclass # => Module
Module.superclass # => Object
Object.superclass # => BasicObject
BasicObjct.superclass # => nil
</code></pre>
<p>クラス階層とインスタンスの関係をまとめてみる。
(<code><====</code> は is_a 関係、<code><----</code>はクラスの親子関係)</p>
<pre><code> BasicObject
|
v
Class <==== Object
|
|--> String <==== "Hello"
|
|--> MyClass <==== myObj (= MyClass.new)
|
`--> Module
|
`--> Class
# もちろん Object だけでなく BasicObject, String, MyClass, Module, Class も Class のインスタンス
</code></pre>
<h2 id="section-6">定数</h2>
<p>大文字で始まる参照は、クラス名やモジュール名も含めて、すべて定数になります。
定数はモジュールで階層化することができ、名前が同じでもパスが違えば違う定数になります。
ファイルシステムのような階層構造です。</p>
<pre><code data-language="ruby"># 定数
module A
B = 'external'
class C
B = 'internal'
end
end
A::B # => 'external'
A::C::B # => 'internal'
</code></pre>
<p>パスの指定の方法もファイルシステムみたい。</p>
<pre><code data-language="ruby"># 定数のパス
module A
B = 'external'
class C
B = 'internal'
# コロン2つで始めれば、絶対パスで指定できる
::A::B # => 'external'
end
# 内部に書けば相対パスで指定できる
C::B # => 'internal'
end
</code></pre>
<p><code>constants()</code>メソッドでモジュールに定義されている定数を取得できます。</p>
<pre><code data-language="ruby"># 定数の一覧
A.constants # => [:B, :C]
Module.constants # => [:Object, :Module, ...]
</code></pre>
<p>定数は警告が出るけど、上書きできます。</p>
<pre><code data-language="ruby"># 定数の上書き
module M
end
module N
end
M = N # warning: already initialized constant M
M # => N
</code></pre>
<h2 id="load">load</h2>
<pre><code data-language="ruby"># load
load('foo.rb') # foo.rbで定義された定数が読み込まれる
load('foo.rb', true) # 無名モジュールを作成して、foo.rbを取り込むので汚染しない
</code></pre>
<ul>
<li>load はコードを実行するために使う。</li>
<li>require はライブラリを読み込むために使う。</li>
</ul>
<h2 id="section-7">その他クラスに関して</h2>
<p>Rubyのクラスは慣習としてキャメルケースを使います。</p>
<ul>
<li>NG => TEXT</li>
<li>OK => Text</li>
</ul>
<p>先にmoduleが定義されてあればclassは定義できません。
クラスだったらオープンクラスで上書きできるけど。</p>
<pre><code data-language="ruby"># TypeError
module Text
end
class Text # Text is not a class
end
</code></pre>
<p>例えばActionMailerをロードしたら Text モジュールが定義されてしまう。
これを避けるには、別のネームスペース(module)を作ります。</p>
<pre><code data-language="ruby">#
module Bookworm
class Text
# ...
end
end
</code></pre>
</div>
<footer>
<p>Last updated at <time>2013-04-02 13:42:23 +0900</time></p>
<!-- <p>Published at <time>2013-01-31</time></p> -->
</footer>
</article>
</section>
</div>
<!-- FOOTER -->
<div id="footer_wrap" class="outer">
<footer class="inner">
<p>Published with <a href="http://pages.github.com">GitHub Pages</a></p>
</footer>
</div>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-36469923-5']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
</html>