D IT Y

タマキ工務店のIT日記

チェリー本輪読会 第13週目まとめ

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

チェリー本輪読会の第13週目のエントリーになります。

輪読会の概要については第1週目にまとめています。

🍒 輪読会 第13週目まとめ

第8章8.1.1〜第8章8.10まで

期間:2021年08月16日〜2021年08月20日

モジュールの概要

クラスは、データとメソッドを持ったオブジェクトを扱う機能ですが、モジュールは「処理の部分だけ」をまとめた機能になります。

以下の特徴があります。

  • モジュールは、クラスのようにインスタンスを持つことはできません。
  • 他のモジュールやクラスを継承することはできません。

モジュールの使い方(Mix-in)

モジュールをクラスに混ぜ合わせて使えるようにすることをMix-inといいます。

以下、Mix-inの手法を説明します。

include

モジュールをクラスに取り組むことをincludeといいます。クラスに取り込むことで、モジュールで定義したメソッドがインスタンスメソッドとして呼び出せるようになります。

以下の例では、モジュールでEffectorを作成し、Guitarクラスにincludeしてみました。Guitar.newでインスタンスを生成し、メソッドを呼び出すことができます。Guitarをplayしchalkさせるのですが、引数にジミヘンを渡すことで、ジミヘンにしか出せない強烈なファズサウンドを出力することができます。

module Effector
    def chalk(guitarist)
        puts "#{guitarist}: ギュイーーン!!"
    end
end

class Guitar
    include Effector
    
    def play
        chalk('ジミヘンドリックス')
    end
end

# インスタンスメソッドとして呼び出す。
sound = Guitar.new
sound.play
ジミヘンドリックス: ギュイーーン!!  

extend

モジュールをクラスにMix-inするもうひとつの方法です。モジュール内のメソッドをそのクラスのクラスメソッドにすることができます。

今度は、GuitarクラスへEffectorモジュールをextendすることで、クラスメソッドとして呼び出すことが可能になります。

module Effector
    def chalk(guitarist)
        puts "#{guitarist}: ギュイーーン!!"
    end
end

class Guitar
    extend Effector

    def self.play
        chalk('ジミヘンドリックス')
    end
end

# クラスメソッド経由で呼び出す。
Guitar.play
ジミヘンドリックス: ギュイーーン!!

# Guitarクラスのクラスメソッドとして呼び出す。
Guitar.chalk('ジミヘン')
ジミヘン: ギュイーーン!!

Enumerableモジュール

docs.ruby-lang.org

このモジュールのメソッドは全て each を用いて定義されているので、インクルードするクラスには each が定義されていなければなりません。

上記は、るりまに記載されているの内容ですが、最初読んだとき「全てeachを用いて定義されている」の意味がわかりませんでした。輪読会内で質問をしてみたところ無事に理解できましたが、私と同じく疑問に思われている方の参考になればと思い私の解釈を書いておきます。

Enumerableモジュールに定義されたメソッドは、全て内部的にeachが動いています。Enumerableモジュールは、eachのみで書くことができる繰り返し処理を、より簡潔に書くために提供されている拡張機能のようなものです。

例えば、eachメソッドをundefすると、ブロックを渡したときに内部的に動くはずのeachが定義されていないことになるので動きません。逆に、自前のクラスにeachメソッドを定義すれば、Enumerableモジュールをincludeして50を超える便利なメソッドが手に入ることになります。

# Enumerableモジュールであるmapが使えなくなる。

class Hash
    undef each
end
=> nil

{a:1, b:2}.map
=> #<Enumerator: ...>

{a:1, b:2}.map{|k,v|[k,v*10]}
(irb):5:in `map': undefined method `each' for {:a=>1, :b=>2}:Hash (NoMethodError)

また、each以外のメソッドにもEnumerableの機能を提供するために、ラッパークラスというものがあります。Enumerator を介することで、Enumerableの機能を利用することができます。

docs.ruby-lang.org

以前のブログでも取り上げましたのでリンクを貼っておきます。

チェリー本輪読会 第5週目まとめ - D IT Y

Comparableモジュールと <=>演算子(UFO演算子

docs.ruby-lang.org

比較演算を可能にする(値の大小を識別できるようにする)モジュールです。

Comparableモジュールのメソッドを使うための条件は、このモジュールをインクルードするクラスで、基本的な比較演算子である <=> 演算子を定義している必要があります。

<=> 演算子はその形状から「UFO演算子」とも呼ばれています。

2 <=> 1
#=> 1
# a > b として、aが大きい場合正の整数となる。返り値は1となる。

2 <=> 2
#=> 0
# 等しい数値の場合は、0を返す。

1 <=> 2
#=> -1
# a < b 1が小さい場合、負の整数を返す。

2 <=> 'abc'
#=> nill
# 比較ができない場合は、nill

これは、「a <=> bは、a>bなら1を、a==bなら0を、a<bなら-1を返すようにしよう。そしたら便利なはず!sortとかで使いやすいはず!」ということが目的で使われます。

もしUFO演算子がなかったら、以下のように書かなくてはいけなくなり大変です。UFO演算子は、簡潔に書くために用意された演算子になります。

p ary.sort do |a, b|
  if a.to_i > b.to_i 
    1
  elsif a.to_i == b.to_i
    0
  elsif a.to_i < b.to_i
    -1
  else
    nil
  end
end

また、文字列の大小比較は、バイト配列の大小を元に比較しています。

''.bytes     #=> [227, 129, 130]
'あいう'.bytes #=> [227, 129, 130, 227, 129, 132, 227, 129, 134]

'あいう' <=> ''
=> 1
'' <=> 'あいう'
=> -1

文字列の大小比較については、伊藤さんのこちらの記事が参考になります。

qiita.com

名前空間

大規模なプログラムや外部に公開するgemを作ったりするときに、クラス名の重複が問題になることがあります。そのとき役に立つのが、名前空間(ネームスペース)としてのモジュールです。モジュール構文の中にクラス定義を書くと「そのモジュールに属するクラス」という意味になります。これで、同名のクラスがあったりしても外側のモジュール名さえ異なっていれば、名前の衝突は発生しなくなります。

以下、私の好きなバンドをモジュールにしました。それからクラスは、これまた大好きな二人のJohnを定義してみました。モジュール名::クラス名とすることで、同名のクラス名の衝突を防ぐことができます。

module Thebeatles
    class John
        def initialize(second_name)
            @second_name = second_name
        end
    end
end

module Sexpistols
    class John
        def initialize(second_name)
            @second_name = second_name
        end
    end
end

Thebeatles::John.new('Lennono')
=> #<Thebeatles::John:0x00007fab2a139368 @second_name="Lennono">

Sexpistols::John.new('Lydon')
=> #<Sexpistols::John:0x00007fab2f034cd8 @second_name="Lydon">

モジュールに特異メソッドを定義する

クラスへMix-inせずにモジュール自身に特異メソッドを定義すれば、モジュール名.クラス名という形でそのメソッドを呼び出すことができます。これで、クラスメソッドのような使い方ができます。

モジュールはインスタンス化できない点がクラスと異なります。インスタンスを作ってなにか操作する必要がないものであれば、モジュールにしておいたほうが他の開発者に変な勘違いをさせる心配がありません。

以下、self.chalkとすることで、特異メソッドとしてメソッドを定義しています。特異メソッドは、特定のオブジェクトに紐づくメソッドのことです。チェリー本輪読会 第12週目まとめ - D IT Y

module Guitar
    def self.play(guitarist)
        puts "#{guitarist}: ギュイーーン!!"
    end
end

Guitar.play('ジミヘン')
ジミヘン: ギュイーーン!!

class << self を使って定義することもできます。

module Guitar
    class << self    
        def play(guitarist)
            puts "#{guitarist}: ギュイーーン!!"
        end
    end
end

Guitar.play('ジミヘン')
ジミヘン: ギュイーーン!!

Sinatraメモアプリ

チェリー本とは関係ないのですが、上記のRubyのコードでセックスピストルズを取り上げたこともあり、以前に開発したメモアプリのことを思い出しました。

Image from Gyazo

github.com

歴史に残る大名盤へオマージュを捧げ作成しましたw

open.spotify.com

今思えばそんなに難しくはないプログラムなのですが、当時はホントわからないことだらけで大変でした…。しかし、そう思えるのもチェリー本を学ぶことで、Ruby力が付いたことが大きいです。

参考書籍

🍒 まとめ

久しぶりのブログ更新になります。

ブログとタイムラグがありますが、実は輪読会は9月17日にフィナーレを迎えました!最終日の詳細は後日ブログに書きたいと思います。

今回が13週目になりますので、こちらのブログも残り4週となります。復習も兼ね、完走まで頑張りたいと思います!

では、また来週!(次回、第14週目)