🍒 はじめに
チェリー本輪読会の第13週目のエントリーになります。
輪読会の概要については第1週目にまとめています。
- 第1週目のエントリーはこちら
- 第2週目のエントリーはこちら
- 第3週目のエントリーはこちら
- 第4週目のエントリーはこちら
- 第5週目のエントリーはこちら
- 第6週目のエントリーはこちら
- 第7週目のエントリーはこちら
- 第8週目のエントリーはこちら
- 第9週目のエントリーはこちら
- 第10週目のエントリーはこちら
- 第11週目のエントリーはこちら
- 第12週目のエントリーはこちら
🍒 輪読会 第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モジュール
このモジュールのメソッドは全て 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の機能を利用することができます。
以前のブログでも取り上げましたのでリンクを貼っておきます。
Comparableモジュールと <=>演算子(UFO演算子)
比較演算を可能にする(値の大小を識別できるようにする)モジュールです。
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
文字列の大小比較については、伊藤さんのこちらの記事が参考になります。
名前空間
大規模なプログラムや外部に公開する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のコードでセックスピストルズを取り上げたこともあり、以前に開発したメモアプリのことを思い出しました。
歴史に残る大名盤へオマージュを捧げ作成しましたw
今思えばそんなに難しくはないプログラムなのですが、当時はホントわからないことだらけで大変でした…。しかし、そう思えるのもチェリー本を学ぶことで、Ruby力が付いたことが大きいです。
参考書籍
- 伊藤淳一 著/『プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで』/技術評論社/2017年https://gihyo.jp/book/2017/978-4-7741-9397-7
- 五十嵐邦明,松岡浩平 著/『ゼロからわかる Ruby 超入門』/技術評論社/2018年https://gihyo.jp/book/2018/978-4-297-10123-7
- 高橋征義、後藤裕蔵 著/『たのしいRuby第6版』/SBクリエイティブ/2019年https://tanoshiiruby.github.io/6/index.html
- プログラミング言語 Ruby リファレンスマニュアル https://docs.ruby-lang.org/ja/
🍒 まとめ
久しぶりのブログ更新になります。
ブログとタイムラグがありますが、実は輪読会は9月17日にフィナーレを迎えました!最終日の詳細は後日ブログに書きたいと思います。
今回が13週目になりますので、こちらのブログも残り4週となります。復習も兼ね、完走まで頑張りたいと思います!
では、また来週!(次回、第14週目)