Ruby クロージャを使ってインスタンス変数を減らす
インスタンス変数は悪である。
だって、クラス内のどこからでも、値を改変できるから。
そこで、インスタンス変数を減らす手立を考えてみた。
以下、ゲッターもどき。
構造体を返す。構造体はラムダをgetメンバに持っている。
ラムダはクロージャになる。変数immutable_varは保持される。
変数の中身(yield)は、後ほどブロックで渡す。
def make_getter immutable_var = yield struct = Struct.new(:get) struct.new( -> { immutable_var } ) end
以下、ゲッター兼セッターもどき。
getメンバ、setメンバを持つ。もちろんクロージャ。
def make_accessor immutable_var = yield struct = Struct.new(:get, :set) struct.new( -> { immutable_var }, -> new_var { immutable_var = new_var } ) end
以下で変数の中身を定義する。
'白石麻衣'、'平手友梨奈'となっているブロックの中身は、がっつり処理を書いてよい。
それら変数をまとめて、こちらも構造体で返す。
def make_immutables struct = Struct.new(:nogi, :keyaki) struct.new( make_getter { '白石麻衣' }, make_accessor { '平手友梨奈' }, ) end
コンストラクタで、上記構造体をインスタンス変数に入れる。インスタンス変数を使ってしまうが、これはしょうがないと思う。
def initialize @immutables = make_immutables end
変数を呼び出す。
呼び出し方が冗長な気もするが、憎きインスタンス変数を減らせたので、良しとする。
def execute p @immutables.nogi.get.call # 白石麻衣 p @immutables.keyaki.get.call # 平手友梨奈 @immutables.keyaki.set.call('長濱ねる') # 長濱ねるをセット p @immutables.keyaki.get.call #長濱ねる end
うーん、他に良い方法あるのかなぁ…。
Ruby ブロック付きメソッドの活用
普通は、スクリプトを読みながら、どういう順序・条件で処理が進むのかを調べる。
それがつらい。処理の詳細を見ないと、実行順や分岐の条件が分からないのがつらい。
だから、実行順や分岐の条件を、処理の詳細から分離する。
OOPのTemplateパターンやStateパターンを使えば、実現できる。だが、他に良い方法は無いだろうか。
if group == '乃木坂46' func1 func2 func3 elsif group == '欅坂46' func1 func3 func4 end
上記のように書けば、実行順と分岐が分離できたことになる。
うーん、でも何と言うか…。メソッド間の結合が弱いというか。
func1 と func2 の順番を逆にすることもできるし、間に処理をスッと挟むことも出来る。簡単に改変できるのが気持ちが悪い。
そこで、以下のような形を考えた。
def f1 p 1 yield end def f2 p 2 yield end def f3 p 3 end f1 do f2 do f3 end end # 1 # 2 # 3
ブロック渡しをする。PHPやJavaScriptだと、無名関数でネストさせることになる。
副次的なメリットとして、次のメソッドに渡したい引数を、返り値に含める必要がない。
# 返り値経由で渡す result = func1 func2 result[:arg1] # 返り値に含める必要がない def func1 arg1 = 'foo' yield arg1 end func1 do |arg1| func2 arg1 end
うーん、イマイチかなぁ…。
CoffeeScript Privateメンバ
= で定義すれば、private
: で定義すれば、public
となる。
Privateメンバは、コンストラクタ関数内のローカル変数として、
クロージャによって保持されている…と考えることが出来る。
class Sony # public pubVar: 'nogi46' pubFunc: -> console.log '乃木坂46' # private priVar = 'keyaki46' priFunc = -> console.log '欅坂46'
sony = new Sony() console.log sony.pubVar # nogi46 sony.pubFunc() # 乃木坂46 console.log sony.priVar # undefined sony.priFunc() # エラー
AltJS、ECMA6といっても、JavaScriptの根本的な概念は変わっていないので…。
ECMA5、勉強しといて良かったよ!
今さらCoffeeScriptでクロージャ
仕事でCoffeeScriptを使っている。
練習も兼ねて、クロージャを書く。
CoffeeScript、Rubyと似ている…。
素のJavaScriptと比べて、色々と省略できすぎるため、初見では何を書いているかさっぱり分からない。
引数を2倍にする関数、3倍にする関数を返す。
factory = (type, num) -> { double: -> num = num * 2 console.log num triple: -> num = num * 3 console.log num }[type]
numは、関数スコープの外側に保持される。
double関数, triple関数はそれぞれクロージャとなる。
素のJavaScriptに書き換えると、以下になる。
var factory = function(type, num) { return { double: function() { num = num * 2 console.log(num) }, triple: function() { num = num * 3 console.log(num) } }[type] }
クロージャをそれぞれ実行。
変数numが保持されていることが分かる。
double = factory('double', 1) double() # 2 double() # 4 double() # 8 triple = factory('triple', 3) triple() # 9 triple() # 27 triple() # 81
elseif・switchは撲滅して連想配列
前回に引き続き、今回も同じテーマで。
以下、憎くて仕方ないelseif文。
id = 'nogi' if id == 'nogi' then name = '乃木坂46' elsif id == 'keyaki' then name = '欅坂46' else name = 'ソニーミュージック' end p name # 乃木坂46
本当にキモい。
たいした処理でもないのに、なぜ、こんなに行数を使うのか理解できない。
そこで、今日、良い記法を思いついた。
[ Ruby ]
id = :keyaki name = { nogi: '乃木坂46', keyaki: '欅坂46', }[id] || 'ソニーミュージック' p name # 欅坂46
存在しない id を指定すると、左辺が nil になる。
結果、右辺が実行される。短絡評価。
PHPで書くと、
<?php $id = 'keyaki'; $name = [ 'nogi' => '乃木坂46', 'keyaki' => '欅坂46', ][$id] ?? 'ソニーミュージック'; echo $name; // 欅坂46
PHP7の ??(Null合体演算子)を使うとスマート。
うーん、かなりイケてると思うんだが…。
elseifやswitch文は連想配列に置き換えてポリモーフィズム
elseifやswtich文は出来るだけ避けたい。
一連のスクリプトから状態を排除したいし、後々の改修で誰かがネストしまくるのが想像できる。
置き換えるには、Stateパターン等を使う必要があると思っていたが、実は連想配列で解決できることに気付いた。
以下、よく見るコード。Rubyで書く。
who = 'shizuka' if who == 'dora' then p '僕ドラえもん' elsif who == 'nobi' then p '僕のび太' elsif who == 'shizuka' then p '私しずか' end
とにかくキモい。このメソッド内で、dora / nobita / shizuka の分岐が、他に何度も繰り返されるのが目に浮かぶ。
これを連想配列にする。
hash = { dora: ->{ p '僕ドラえもん' }, nobi: ->{ p '僕のび太' }, shizuka: ->{ p '私しずか' }, } who = :nobi hash[who].call
ポリモーフィズムにもなっていて、結構イケてると思うんだけど…。
この、簡単に手を入れられない(深いネストを書けない)ところが良い。
実はelseif文は、連想配列に書き換えられる、という気付きでした。ちゃんちゃん。
Ruby 委譲
Rubyの便利な委譲な仕組み。
一つ目。Forwardableモジュール。
class Sony extend Forwardable def_delegators :@keyaki, :discord def initialize(keyaki) @keyaki = keyaki end end class Keyaki def discord print '僕は嫌だ!' end end sony = Sony.new(Keyaki.new) sony.discord # 僕は嫌だ!
def_delegatorsメソッドの引数に、委譲先のオブジェクト、委譲するメソッドを指定。
Sonyはdiscordメソッドを持っていないにもかかわらず、実行できている。
内部では、Keyakiオブジェクトが、discordを実行している。
二つ目。method_missing。
これは、PHPのマジックメソッド__call()と似たようなもの。
class Sony def initialize(keyaki) @keyaki = keyaki end def method_missing(name, *args) @keyaki.send name, *args end end class Keyaki def discord print '僕は嫌だ!' end end sony = Sony.new(Keyaki.new) sony.discord # 僕は嫌だ!
メソッド名を指定しなくていい分、こっちの方が楽かも。
何とかここらへんをうまく使って、Proxyパターンを楽に実装していきたい。