LoginSignup
1
1

More than 5 years have passed since last update.

7つの言語 7つの世界 を読む 2

Posted at

あせらず、ちょっとずつ読んでいくつもり。
ケーススタディとかは、答え合わせをさぼったり、がんばりを後回しに保留したりとかする感じで。

Ruby

読み進めつつメモ 3日目

・メタプログラミングとは、プログラムを書くプログラムを作成すること
・メタプログラミングの例としてはRailsの中核を成すActiveRecordフレームワーク
・オープンクラスの概念により基本的なクラス定義も再定義できる(黒魔術?技術?)
・切れ味の鋭いメスには責任が伴う
・例としてmethod_missingという特殊なデバッグ用メソッドを上書きすると
 高度な振る舞いをさせることも出来るがデバッグは大変難しくなる

・モジュールもメタプログラミングスタイル
・DSLのテクニックを使うと特定のドメインで使う言語を作ることが出来る
・クラス名によってプログラム処理を変更したりできる

試してみよう

  • CSVアプリの作成
test.rb

#rowの生成のみ試作
#.class.acts_as_row(m)の呼び出しを試行

class CsvRow
    def self.acts_as_row(m)
        define_method m do
            puts 'this method is '+m
        end
    end
end
row = CsvRow.new
["3","4"].each do |i|
    row.class.acts_as_row('hoge'+i.to_s)
end
row.hoge3
row.hoge4
test.rb

#rowオブジェクトを使わず自身のメソッドを動的に定義する方向で試行
#自身にどんどん追加するのは多分よくないのかも
#使い捨てとか上書きで専門のrowオブジェクトを作るほうがいいのかな
#いや、条件式で制御するとすればどちらでもなんとかなるのかな
#わかりやすさとか、、まあいいや

module ActsAsCsv
    def self.included(base)
        base.extend ClassMethods
    end

    module ClassMethods
        def acts_as_csv
            include InstanceMethods
        end
        def acts_as_row(h)
            define_method h do
                @csv_contents[@nowline][h]
            end
        end
    end

    module InstanceMethods
        def read
            @csv_contents = []
            filename = self.class.to_s.downcase + '.txt'
            file = File.new(filename)
            @headers = file.gets.chomp.split(', ')
            @headers.each do |h|
                self.class.acts_as_row(h)
            end
            file.each do |row|
                lineobj = {}
                linecsv = row.chomp.split(', ')
                linecsv.each_with_index do |col, key|
                    lineobj[@headers[key]] = col
                end
                @csv_contents << lineobj
            end

        end

        attr_accessor :headers, :csv_contents, :nowline

        def initialize
            read
        end

        def each
            @csv_contents.each_with_index do |i,key|
                @nowline = key
                yield self
            end
        end
    end
end

class RubyCsv
    include ActsAsCsv
    acts_as_csv
end

csv = RubyCsv.new
csv.each {|row| puts row.one}
csv.each {|row| puts row.two}
  • method_missingを使って見出し列の値を返す
test.rb

#method_missingを使って実装

module ActsAsCsv
    def self.included(base)
        base.extend ClassMethods
    end

    module ClassMethods
        def acts_as_csv
            include InstanceMethods
        end
    end

    module InstanceMethods
        def method_missing name, *arg
            @csv_contents[@nowline][name.to_s]
        end
        def read
            @csv_contents = []
            filename = self.class.to_s.downcase + '.txt'
            file = File.new(filename)
            @headers = file.gets.chomp.split(', ')
            file.each do |row|
                lineobj = {}
                linecsv = row.chomp.split(', ')
                linecsv.each_with_index do |col, key|
                    lineobj[@headers[key]] = col
                end
                @csv_contents << lineobj
            end

        end

        attr_accessor :headers, :csv_contents, :nowline

        def initialize
            read
        end

        def each
            @csv_contents.each_with_index do |i,key|
                @nowline = key
                yield self
            end
        end
    end
end

class RubyCsv
    include ActsAsCsv
    acts_as_csv
end

csv = RubyCsv.new
csv.each {|row| puts row.one}
csv.each {|row| puts row.two}


どうかな、まだ動かしたレベルであって、スコープとか内部動作のイメージとかは理解が足りないだろうな。

もうちょっと確認

test.rb


#method_missingを使って実装

module ActsAsCsv
    def self.included(base)
        base.extend ClassMethods
    end

    module ClassMethods
        def acts_as_csv
            include InstanceMethods
        end
        def method_missing name, *arg
            puts "Class method_missing"
        end
        def class_hello
            puts "class_hello"
        end
    end

    module InstanceMethods
        def method_missing name, *arg
            puts 'Instance method_missing'
        end

        def selfreturn
            yield self
        end
        def instance_hello
            puts 'instance_hello'
        end
    end
end

#ActsAsCsv module call
#actascsv extend classmethods
#include actascsv etc. on rubycsv
#call acts_as_csv
#include instancemethods on rubycsv(classmethods?)

class RubyCsv
    include ActsAsCsv
    acts_as_csv
    class_hello
    tow
end

=begin
なるほど、モジュールからClassMethodsが呼び出されるのでそれらメソッドはbaseのクラスには追加されない。
一方、InstanceMethodsで追加する分はbaseのインスタンスに追加される。しかしインスタンス化する際に実行されるのでクラスには追加されない。たぶん。
=end

csv = RubyCsv.new
puts '============'
csv.selfreturn {|row| puts row.instance_hello}
puts '============'
csv.selfreturn {|row| puts row.class_hello}
puts '============'
csv.selfreturn {|row| puts row.class.class_hello}
puts '============'
csv.selfreturn {|row| puts row.class.name}
puts '============'
csv.selfreturn {|row| puts row.class}
puts '============'
puts csv.class
puts '============'
puts csv.object_id
puts '============'

#puts csv.class.methods.include?('class_hello')
#puts csv.class.superclass

=begin
class_hello
Class method_missing
============
instance_hello

============
Instance method_missing

============
class_hello

============
RubyCsv
============
Class method_missing
RubyCsv
============
Class method_missing
RubyCsv
============
70105229168120
============
[Finished in 0.1s]
=end

雑感

深い。。まだいろいろわからない。
言語仕様や細かいこと以外にも多分勉強になることはあると思うけど、
きりが無いのでひとまずこの辺で〜。

メモ

yieldについてもうちょっと

なるほど、block_givenでブロックの有無を判別できるのか。

  1. 「引数」と「ブロック」を受け取るメソッドの定義 引数を受け取るメソッドを定義する。ただし、ブロックも受け取る。 以下のメソッドでは、引数の値に応じて、ブロックの呼出しを変化させている。

これで実装するとちょっと複雑になりそうな気がしてしまわなくもない。

コードブロックと無名関数ってやっぱちょっと違うような

  1. 関数オブジェクトの生成と呼出し Ruby で、関数オブジェクトを生成する方法は proc lambda の2つある。

無名関数の値がある場合の表現は関数オブジェクトであることが多いと。

関数値

Rubyでは、ブロックをProcオブジェクトとして、 メモリ上に名前をつけて束縛したものが、それにあたる。
ラムダの糖衣構文
p add4(1, 2, ->(x, y){x + y})

カリー化

カリー化
curry = lambda{|x| lambda{|y| x + y}}
p curry.call(1).call(2)

procとlambdaの違いもあるらしい。

procとlambda(とブロック)

なるほど、、でも、いろいろあって私には記事を読むだけで直ぐには記憶できないな。。

違いが分かりやすい説明がある。

Procではなくblockを使うのはどんな場合でしょうか?
block: メソッドが小分けにできる場合。ユーザーに小分けにした断片を使わせたいとき
block: データベースのマイグレーションのように、複数の表現を自動的に実行したいとき。
Proc: 1つのblockを複数回使いたいとき。
Proc: メソッドが1つまたは複数のコールバックをとるとき。

ブロックは保存できない。
状態を同じくして使い回せるが、状態を引き継がず再利用するような場合にはprocってことか。

Method Object
既存のメソッドをクロージャとして他のメソッドに渡すことができます。Rubyの"method"メソッドが使えます。

なるほど、いろいろできるな。。

moduleについてもう少し

PHP5.4のトレイトを学んだらRubyのmixinがちょっと変な気がした - なんたらノート第三期ベータ
http://goo.gl/5KQqKG

セマンティック(構造的)な面では継承と同じようにincludeで構造づくられるのでis_aがつかえる。

Scalaのtraitはmixinか? - 西尾泰和のはてなダイアリー
http://goo.gl/fwuRu

まあ、要するに「3つそれぞれ別物」だね。

うーん、traitとmixin(rubyのmodule?)の定義と境界線ってなんだろう。ってのはwikipediaに書いてた。

トレイト - Wikipedia
http://goo.gl/Z7lgqC

入れ子されたトレイトは平坦なトレイトと等価である[3] トレイトはミクスインと類似しているが、ミクスインでは継承操作のみによってメソッドを合成させるが、トレイトでは、対称的な足し合せ、メソッド排除、別名化など、より多くの方法でメソッドを合成できる。また、トレイトは合成時にメソッドの型指定のみならず実装も与えるという点で、インターフェースとも異なっている。

静的型言語のトレイトの本質は従来のミクスインのままである(平坦化されない)が、平坦なトレイトを模したりミクスインの問題点を回避する拡張を施すなどの工夫により、トレイトに近い使い勝手を実現している

なるほど、、rubyのmoduleは入れ子でincludeしたらスコープによって平坦なincludeと変わるのかどうか。。
下記を実行すると確かにモジュール内でincludeすると、モジュールに対してのモジュールのincludeになる。
traitではない。

test.rb
#method_missingを使って実装
module ActsAsCsv2
    def self.included(base)
        base.extend ClassMethods2
    end
    module ClassMethods2
        def hoge1
            puts 'hoge1'
        end
    end
    def hoge2
        puts 'hoge2'
    end
end
module ActsAsCsv
    def self.included(base)
        base.extend ClassMethods
        include ActsAsCsv2
    end

    module ClassMethods
        def hoge3
            puts 'hoge3'
        end
    end
    def hoge4
        puts 'hoge4'
    end
end

class RubyCsv
    include ActsAsCsv
end

rc = RubyCsv.new
puts rc.methods
#hoge4 existed.
puts '========================================='
puts rc.class.methods
#hoge3 existed.
puts '========================================='
puts rc.methods.include?('hoge1')
puts rc.methods.include?('hoge2')
puts rc.methods.include?('hoge3')
puts rc.methods.include?('hoge4')
#hoge4 existed. but no matched.
puts rc.class.methods.include?('hoge1')
puts rc.class.methods.include?('hoge2')
puts rc.class.methods.include?('hoge3')
#hoge3 existed. but no matched.
puts rc.class.methods.include?('hoge4')

PHP書きが久しぶりにRubyの言語仕様を詳しく思い出して抱いた感想たち
http://goo.gl/MXUOcv

PHPでいうところの、traitと名前空間を組み合わせたようなものですね
昔はPHPにはtraitも名前空間もなく、単一継承のみ可能な言語であったためRubyのモジュールがうらやましかったのですが、最近はtraitや名前空間が出てきて、PHPもいい感じですね
メソッドやクラス/モジュール定義で例外を捕捉(p88)
ついでに言うと、retryも便利すぎるんだが。えっbeginからもう一回やり直してくれるのなにそれ。

「%記法」「擬似変数たち」にも興味。

クラスメソッド・self

クラスメソッドはいろいろ定義の方法があると。
なんかjsのプロトタイプを知ったときの感じを思い出した。

method_missingの定義場所

全てのオブジェクトの継承元であるBasicObjectに定義されているため、
あらゆるオブジェクトからの呼び出しに対応できます。

なるほど。

ファーストクラス

Rubyの関数オブジェクトは、どうやら「関数」オブジェクトではないそうです。つまり、proc(lambda)で作ることのできるオブジェクトとは、(Smalltalk的な)ブロックであるらしく、|x,y|と引数を指定しても、それは仮引数ではなく、ブロック外の同名の変数(つまり、ブロック外のx,yという名の変数)を上書きしてしまうのだそうです。従って、これを使用してはファーストクラスの関数を表現できないことになります。

なるほど、、ちがうのかあ。

アンパサンド(&変数)

やはりブロックという意味なのね。
PHPの参照と似た意味があるかなあと思ったけどなさそう。
このサイトは他にもruby仕様について突っ込んだことが書いてる。

ブロックは直接にはオブジェクトではない。しかし、メソッド定義の仮引数の記述の最後に、&foo のように & を先頭に付けた引数を付けるなどすることで、簡単に Proc オブジェクトとして得ることができる。

なるほど、Procオブジェクトとして得るということなのか。

メソッド呼び出しの最後の実引数として、引数の前に & を付けることで、Proc オブジェクトをブロックの代わりに渡すこともできる。Proc オブジェクトの手続きは call というインスタンスメソッドにより呼ぶことができる。

このあたり、わかったようでわかってないんだろうな。。

これは、「メソッドにブロックが与えられていれば、そのブロックを実行しなさいよ~」ということです。

参照渡し

Rubyは『値渡し』です。しかし、実引数にが配列の場合『参照渡し』のような結果になるときがあります。実引数で渡した配列が、仮引数の値を変更すると実引数の値も変わってしまう『副作用』が生じます。実はこの時『参照の値渡し』と言う値渡しが行われています。

 でも、もっと単純に参照渡しができないものかと、
 何か方法があったらアドバイスをお願いします。
尋常な方法では不可能だと思います。

ブロックの注意

{ ... } の方が do ... end ブロックよりも強く結合します 次に例を挙げますが、このような違いが影響するコードは読み辛いので避けましょう:

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1