Roll With IT

tamakiのIT日記

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

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

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

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

🍒 輪読会 第12週目まとめ

第7章7.10.1〜第7章7.11まで

期間:2021年08月09日〜2021年08月13日

第8章にも少し入りましたが、こちらは次週へまとめます。

エイリアスメソッドの定義

alias 新しい名前 元の名前

エイリアスメソッドを定義する場合は、事前に元のメソッドを定義しておく必要があります。

class Numbergirl
    def introduce
        "福岡市博多区から参りましたナンバーガールです”
  end
alias greeting introduce
end

numbergirl = Numbergirl.new

numbergirl.introduce
=> "福岡市博多区から参りましたナンバーガールです"
numbergirl.greeting
=> "福岡市博多区から参りましたナンバーガールです"

クラス/メソッドの定義 (Ruby 3.0.0 リファレンスマニュアル)

メソッドの削除

undef 削除するメソッドの名前

メソッドの定義をあとから削除することができます。

先程の、Numbergirlクラスを継承し、その後定義しておいたintroduceメソッドを削除してみます。

class Zazenboys < Numbergirl
    undef introduce
end

zazenboys = Zazenboys.new
zazenboys.intoroduce
: undefined method `intoroduce' for #<Zazenboys:0x00007fa552839290> (NoMethodError)

クラス/メソッドの定義 (Ruby 3.0.0 リファレンスマニュアル)

ネスト(入れ子)したクラスの定義

class 外側のクラス
    class 内側のクラス
    end
end

クラスの内部に定義したクラスは、::を使って参照できます。

外側のクラス::内側のクラス 

この形を、名前空間(ネームスペース)と呼びます。(通常はクラスよりモジュールが使われることが多いです。)

class Numbergirl
    class Zazenboys
        attr_reader :menber
        
        def initialize(member)
            @member = member
        end
    end
end

leader = Numbergirl::Zazenboys.new('向井秀徳')
leader
=> #<Numbergirl::Zazenboys:0x00007fa556873d88 @member="向井秀徳">

等値を判断するメソッドや演算子4つ

equal?

英語の意味そのままなので分かりやすいです。等しければtrueを返します。

object_idを比較しているので、代入している内容が文字列だとfalseになります。object_idが違うためです。

numbergirl = 'Band'
zazenboys = 'Band'
hakata = numbergirl 

numbergirl.equal?(zazenboys)
=> false
numbergirl.equal?(hakata)
=> true

==

オブジェクトの内容が等しいか判断します。

100 == 100.000
=> true

'a' == 'a'
=> true

'a' == 'A'
=> false

'a' == :a
=> false

foo = 'hoge'
=> "hoge"

bar = 'h'+ 'o' + 'g' + 'e'
=> "hoge"

foo == bar
=> true

eql?

== とは別に、値が同じどうかを判定することができます。

== との違いは、ハッシュ値が等しいかどうかで判断する点です。

hashメソッドで、ハッシュ値を確認し同じであれば、trueになります。

numbergirl = 'Band'
zazenboys = 'Band'

numbergirl.hash
=> 1476835131188092417
zazenboys.hash
=> 1476835131188092417
numbergirl.eql?(zazenboys)
=> true

Hash#hash (Ruby 3.0 リファレンスマニュアル)

===

左辺が単独のStringやIntegerオブジェクトの場合は、==と同じように判断されます。

左辺がClassオブジェクトの場合は、右辺がそのクラスのインスタンスであるかどうかを判断します。

左辺が正規表現の場合は、=~と同じようにマッチしたかどうかを判断します。=~ を使って正規表現を比較する

100 === 100.00
=> true
'a' === 'A'
=> false
'a' === 'a'
=> true

/\w{6}-\d{3}/ === 'MONGOL-800'
=> true

また、case文のwhen節の内部で行われている処理ですが、===で判断されています。

# case文の公式
case 比較したいオブジェクト
when A
  # 処理
when B
  # 処理
else 
    # 処理
end

# 内部で行われている処理
if A === 比較したいオブジェクト
  # 処理
elsif B === 比較したいオブジェクト
  # 処理
else
  # 処理
end
case 'foo'
    when 'foo'
        puts 'true'
    when /w{3}/
        puts 'true'
    else 
        puts 'false'
 end

# true
=> nil

case 100
    when 'foo'
        puts 'true'
    when /w{3}/
        puts 'true'
    else 
        puts 'false'
 end

# false
=> nil

オープンクラス

Rubyはクラスの継承に制限がありません。String や Integer といった基本的なクラス(組み込みクラス)も自由に改変できます。定義済みのクラスへ、メソッドを追加することも上書きすることも可能です。Rubyのクラスは変更に対してオープンなので、「オープンクラス」と呼ばれています。

以下、Arrayクラスを継承させた独自のクラスになります。

class Numbergirl < Array
end

band = Numbergirl.new()
=> []
band << '向井秀徳'
=> ["向井秀徳"]
band << '田渕ひさ子'
=> ["向井秀徳", "田渕ひさ子"]
band
=> ["向井秀徳", "田渕ひさ子"]
band.class
=> Numbergirl

# 継承しないとエラーになる。
class Numbergirl
end

band = Numbergirl.new()
=> #<Numbergirl:0x00007f84d916b420>
band << '向井秀徳'
: undefined method `<<' for #<Numbergirl:0x00007f84d916b420> (NoMethodError)

モンキーパッチ

組み込みクラスやメソッドを追加したり変更したりします。このように自分が期待する挙動へ変更することを、モンキーパッチといいます。

class Numbergirl
    def initialize(name)
            @name = name
    end

    def introduce
        "#{@name}です"
    end
end

band = Numbergirl.new('ナンバーガール')
band.introduce
=> "ナンバーガールです"

# モンキーパッチを当てて、希望する挙動へ変更する。
class Numbergirl
    def introduce
        "福岡市博多区から参りました#{@name}です"
    end
end

band.introduce
=> "福岡市博多区から参りましたナンバーガールです"

原則として、オープンクラス、モンキーパッチには頼らない

オープンクラス、モンキーパッチは非常に強力です。誤って使用してしまうとプログラム全体に不具合を生じさせてしまう原因となってしまいます。

原則として、まずはオープンクラス、モンキーパッチに頼らずにプログラムが書けないか考える必要があります。

大いなる力には、大いなる責任が伴うことを忘れてはいけない。 Matz

特異メソッド (singleton メソッド)

Rubyはすべてがオブジェクトなので、StringクラスやArrayクラスもオブジェクトです。そして、それらのクラスは、Classクラスインスタンスになります。

class String
end

String.class
=> Class

class Foo
end

Foo.class
=> Class

特異メソッドとは、特定のオブジェクトに紐づくメソッドのことです。 オブジェクト固有のメソッドのことで、あるオブジェクトにはあるメソッドが存在しますが、同じクラスの別のオブジェクトには存在しません。

特異メソッドはdef オブジェクト名.メソッド名で定義できます。

class Instance
  def i_method
    puts 'インスタンスメソッドです。'
  end
end

foo = Instance.new
bar = Instance.new

# 特異メソッドを定義します。
def foo.s_method
  puts '特異メソッドです。'
end

foo.i_method
=> インスタンスメソッドです。

bar.i_method
=> インスタンスメソッドです。

# オブジェクトがfooで、特異メソッドとしてs_methodを定義しているので呼び出せる。
# s_methodは、fooのみが持つ特異メソッドとして定義されている。
foo.s_method
=> 特異メソッドです。

# オブジェクトがbarと異なるため呼び出せない。(barオブジェクトにはs_methodは定義していない)
bar.s_method 
: undefined method `s_method' for #<Instance:0x00007f8474a980d0> (NoMethodError)

るりまを参照する際には、クラスメソッドではなく、特異メソッドと書かれているので置き換えて読みます。

Ruby におけるクラスメソッドというのは Class クラスのインスタンスの特異メソッドを便宜上クラスメソッドと呼んでいて、実態は特異メソッドの一種です。

ダックタイピング

もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである

という言葉に由来するプログラミング用語です。

オブジェクトのクラスが何であろうと、そのメソッドが呼び出せればOKとするプログラミングスタイルのことです。オブジェクトを特徴づけるのは、実際のクラスや継承関係ではなく、そのオブジェクトがどのように振る舞うか?(どんなメソッドを持っているか?)です。

class Member
    def play
        puts 'メンバーは楽器を演奏する'
 end
end

class Guitar < Member
    def play
        puts 'ギターを演奏する'
    end
end

class Bass < Member
    def play
        puts 'ベースを演奏する'
    end
end

member = Guitar.new
member.play
# => ギターを演奏する

member = Bass.new
member.play
# => ベースを演奏する

同じ名前のメソッドが複数のオブジェクトに属することをオブジェクト指向の用語で、「ポリモーフィズム」といいます。playメソッドの実行結果はそれぞれ違います。(putsの中身が違う)

ポリモーフィズム

  • 共通メインルーチンを作るための仕組み。
  • 呼び出す側のロジックを一本化する仕組み。
  • 呼び出される側の引数や戻り値の形式を統一する必要がある。

botが導入されました

今週、楽しみにしていたbotが輪読会へ導入されました〜!

botとは?

e-words.jp

choosing-someone-discord-bot(Discordのボイスチャンネルに参加しているメンバーをランダムに指名するbot

Discordの音声チャンネルで開催されている輪読会ですが、毎回、司会やドライバーなどを決める作業がありました。その都度irbを開きsampleメソッドをまわして決めていたのですが、多少の手間と煩わしさがありました。そこで、この作業をbotで代替する目的で、トミーさんが開発されていたのが今回紹介するbotになります。今までの手間がなくなり、かつエンタメ性もプラスされ、楽しく、便利になりました!

botの詳細は、トミーさん(id:eatplaynap329)のブログ、GitHubページに詳しく書かれています。

eatplaynap329.hatenablog.jp

github.com

mitaka.rbへ初参加!

輪読会メンバーの紹介で初めてmitaka.rbへ参加しました。

イベント管理アプリをモブプロで作る会でした。

Webアプリケーションの制作過程を、モブプロ形式で見ることができました。テスト駆動開発(TDD)の流れをベースに、どのように思考しコードを書いてくのか?熟練者のプログラマの手元をLIVEで見ることができ勉強になりました。Vimを使っている画面も初めて見ることができました。他にも、esaを初めて使ってみたり、いろいろ経験できよかったです。

そしてなにより、主催の@tatsuoSakurai さんや、@okuramasafumi さんがとても優しく親切で、とても雰囲気の良いコミュニティでした。ぜひまた参加したいと思います!

mitakarb.doorkeeper.jp

参考書籍

🍒 まとめ

チェリー本、最初の難関であるクラスの章が完了しました!

いや〜、大変でした。しかしその分、同時に参加した『オブジェクト指向でなぜつくるのか』輪読会ともあわせて、クラス、オブジェクト指向の基礎について学ぶことができた章でした。 今までは、「クラス怖い」「継承?クラスメソッド?」と、自信の持てなかった内容ですが、輪読会を通して理解することができ大きな自信となりました。次回は、モジュールに入ります!

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

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

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

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

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

🍒 輪読会 第11週目まとめ

第7章7.6.1〜第7章7.9.3まで

期間:2021年08月02日〜2021年08月06日

クラスの継承

オブジェクト指向プログラミングの三大要素のひとつでもある「継承」を学びました。

以下、継承を図で表してみましたが、ひと言で説明すると「継承はクラスの特徴を共有すること」です。動物やゲームのキャラクターなど、いろいろな例があるとは思いますが、生き物ではなくモノでも当てはまるか考えてみました。

is-aの関係

クラスの基本として、スーパークラス、サブクラスという関係があります。

親と子の関係でも表せます。親クラス、子クラス。

以下では、「弦楽器」というクラスの中に「ギター」というクラスが存在します。これを「is-aの関係」と呼び、「サブクラスはスーパークラスの一種である」(サブクラス is a スーパークラス)と当てはめた場合、違和感がないか?継承関係が正しいか確かめることができます。

# 文脈は正しい

ギター is a 弦楽器
ギターは弦楽器の一種である。

サブクラスのギターは、ベースでも、ウクレレでも継承関係は正しいので問題ないですが、弦楽器のスーパークラスに値する「楽器」となると、関係がおかしくなります。

# 文脈的におかしい

楽器 is a 弦楽器
楽器は弦楽器の一種である

クラスの図はサブクラスからスーパークラスへ向かって矢印↑を伸ばすことで、クラスの関係を表します。

f:id:shirotamaki:20210825182458p:plain

上記の話しをベースに、楽器クラスを題材にクラスの継承関係を考えてみました。

  • まず楽器は、弦楽器、管楽器、打楽器などありますが、弦楽器を選択してみました。
    • 弦楽器 is a 楽器
  • それから、弦楽器の中からギターを選び、エレキギターを選択していきます。
  • さらに、メーカーはギブソンを選択します(もちろん、フェンダーもグレッチも同じ関係を持つことができます)
  • 最後に、ギブソンのシリーズ名を選択します(ここでも、フライングV、SGなども同じ関係を持つことができます)

f:id:shirotamaki:20210825182341p:plain

スーパークラス(楽器)に向かうほど、汎化していき、サブクラス(ギブソン)に向かうほど特化していきます。スーパークラスからサブクラスへ、上から下(左から右)へ性質を受け継いでいきます。これを「継承」と呼びます。

サブクラスである「ギブソン」は、スーパクラスの「エレキギター」の特徴を全て継承しています。(鉄製の弦を装備している。ピックアップを内蔵している。弦の振動を電気信号に変換している。など)しかし、スーパークラスの関係性にない「フェンダー」の特徴は継承していません。(製作している工場が違う。ピックアップの種類が違う。音色が違う。など)

オブジェクトのクラスの確認方法

classメソッドを使います。

class Gibson
    puts 'I play guitar'
end

gibson = Gibson.new
=> #<Gibson:0x00007fdcdd045f08>
gibson.class
=> Gibson

instance_of?メソッドを使うと、引数にクラスを渡すことで、true、falseで確認できます。

gibson = Gibson.new
=> #<Gibson:0x00007fdcdd0d7688>
gibson.instance_of?(Gibson)
=> true
gibson.instance_of?(String)
=> false

is_a?メソッドを使うと、継承関係を確認できます。GibsonクラスのスーパークラスであるObjectクラスは、trueになります。

gibson = Gibson.new
=> #<Gibson:0x00007fdcd90d2df8>
gibson.is_a?(Gibson)
=> true
gibson.is_a?(Object)
=> true
gibson.is_a?(String)
=> false

ほかのクラスを継承したクラスを作る

class サブクラス < スーパークラス
end

Guitarクラス(スーパークラス)を作り、その後にElectric_guitarクラス(サブクラス)を作り継承させてみます。

class Guitar 
    attr_reader :brand, :price

    def initialize(brand, price)
        @brand = brand
        @price = price
    end
end

class Electric_guitar < Guitar
    attr_reader :model

    def initialize(brand, price, model)

# @brand、@priceの箇所をsuperを使って呼び出すことができる。
# superを使うと、スーパークラスの同名メソッドを呼び出すことができる。

        super(brand, price)       
        @model = model
    end
end

electric_guitar = Electric_guitar.new('Gibson', 1000000, 'LesPau
l')
=>
#<Electric_guitar:0x00007fc73d1613c0
...
electric_guitar.brand
=> "Gibson"
electric_guitar.price
=> 1000000
electric_guitar.model
=> "LesPaul"

しかし、そもそもスーパークラスとサブクラスで実行する処理が変わらなければ、サブクラスで同名メソッドを定義したり、superで呼んだりする必要はありません。

class Guitar 
    attr_reader :brand, :price

    def initialize(brand, price)
        @brand = brand
        @price = price
    end
end

class Electric_guitar < Guitar
    # スーパークラスに処理を任せる。
end

electric_guitar = Electric_guitar.new('Gibson', 1000000)
=> #<Electric_guitar:0x00007f914618c318 @brand="Gibson", @price=1000000>
electric_guitar.brand
=> "Gibson"
electric_guitar.price
=> 1000000

メソッドの公開レベル

publicメソッド

クラスの外部からでも自由に呼び出せるメソッドのことです。

インスタンスメソッドとして返るように公開することを指します。initializeメソッド以外のインスタンスメソッドは、デフォルトでpublicメソッドが指定されています。

class Guitar 
    def buy
        '私はギターを買いました' 
    end
end

guitar = Guitar.new
=> #<Guitar:0x00007fc82f1873c0>

# buyメソッドを外から呼び出している。
guitar.buy
=> "私はギターを買いました"

privateメソッド

クラスの外からは呼び出せず、クラスの内部でのみ使えるメソッドです。

クラス内でprivateキーワードを書くと、そこから下で定義されたメソッドはprivateになります。

class Guitar 
    private

    def buy
        '私はギターを買いました'
    end
end

guitar = Guitar.new
=> #<Guitar:0x00007fe8a81accf8>
guitar.buy
#=> : private method `buy' called for #<Guitar:0x00007fe8a81accf8> (NoMethodError)

レシーバを指定して呼び出すことができないメソッドになるので、以下のようにself.brandとした場合は、NoMethodErrorとなり呼び出せません。brandメソッドはprivateメソッドになるためです。

class Guitar
  def buy
    "私は、#{self.brand}ギターを買いました"
  end

  private

  def brand
    'Gibson'
  end
end

guitar = Guitar.new
guitar.buy
#=> : private method `brand' called for #<Guitar:0x00007fb3221126f0> (NoMethodError)

protectedメソッド

メソッドを定義したクラス自身とそのサブクラスからは、インスタンスメソッドをレシーバ付きで呼び出すことができます。それ以外の場所からは呼び出せません。

class Guitar
# 一旦publicメソッドとして定義する。
    attr_reader :brand, :price

# priceのみ、protectedメソッドへ変更する。
    protected :price 

    def initialize(brand, price)
        @brand = brand
        @price = price
    end

    def which_is_expensive?(other_brand)
        other_brand.price < @price
    end
end

gibson = Guitar.new('Gibson', '100万円')
=> #<Guitar:0x00007f8fedb68b20 @brand="Gibson", @price="100万円">
fender = Guitar.new('Fender', '80万円')
=> #<Guitar:0x00007f8fedaa8758 @brand="Fender", @price="80万円">

gibson.which_is_expensive?(fender)
=> false
fender.which_is_expensive?(gibson)
=> true

gibson.brand
=> "Gibson"

# クラス外からは、priceは呼び出せない。protectedメソッドのため。
gibson.price
`<main>': protected method `price' called for #<Guitar:0x00007f8fedb68b20 @brand="Gibson", @price="100万円"> (NoMethodError)

定数の再代入を防ぐ

Rubyの定数は再代入が可能です。warningと警告は出ますが、再代入ができてしまいます。

FOO = 'bar'
=> "bar"
FOO = 'hoge'
warning: already initialized constant FOO
warning: previous definition of FOO was here
=> "hoge"

誤って変更されないためには、freezeが必要になります。

しかし、単に定数へfreezeしただけでは、値の変更は防ぐことはできますが、再代入は可能なままです。

# 値の変更は防ぐことはできる。
FOO = 'bar'
=> "bar"
FOO.freeze
=> "bar"
FOO.upcase!
(irb):10:in `upcase!': can't modify frozen String: "bar" (FrozenError)

# しかし、再代入は防ぐことができない。
FOO = 'hoge'
(irb):11: warning: already initialized constant FOO
(irb):1: warning: previous definition of FOO was here
=> "hoge"

再代入を防ぐには、クラスを準備する必要があります。

クラス外部から定数を参照するには、クラス名::定数名 とします。

# クラスを凍結して再代入を阻止する。
class Hoge
    FOO = 'bar'
end

Hoge.freeze
Hoge::FOO = 'piyo'
`<main>': can't modify frozen #<Class:Hoge>: Hoge (FrozenError)

# クラス内でfreezeを呼び再代入を阻止する。
class Hoge
   FOO = 'bar'
  
  freeze
   FOO = 'piyo'
end

`<class:Hoge>': can't modify frozen #<Class:Hoge>: Hoge (FrozenError)

freezeを適用すれば、上記の例のように再代入を防止できますが、通常はこのようにfreezeを呼ぶことはないようです。

マジックコメント # frozen_string_literal: true

# frozen_string_literal: trueというマジックコメントをファイルの先頭に書いておくと、文字列が最初からfreezeされます。freezeされるのは文字列リテラル( ' , " で作成する String オブジェクト) のみであることに注意が必要です。

以下、定数での例ですが、ここでは定数・変数で挙動に違いはありません。

# frozen_string_literal: true

FOO = 'hoge'
FOO.upcase!

`upcase!': can't modify frozen String: "hoge" (FrozenError)

また、文字列リテラルをデフォルトで イミュータブル にする変更は、Ruby 3.0 で導入予定だったようですが、今のところ取りやめになっているようです。

I officially abandon making frozen-string-literals default (for Ruby3).

和訳:Ruby3.0から、frozen-string-literalsをデフォルトにすることは公式に取りやめます。

Matz.

Feature #11473: Immutable String literal in Ruby 3 - Ruby master - Ruby Issue Tracking System

他、こちらの伊藤さんの記事も参考にさせていただきました。

文字列をfreezeさせるいくつかの方法 - Qiita

輪読会「オブジェクト指向でなぜつくるのか第3版」へ参加

www.nikkeibp.co.jp

第2回目の輪読会へ参加しました。オブジェクト指向の三大要素(クラス、ポリモーフィズム、継承)を中心に学びました。

そこで、オブジェクト指向設計においての大事な原則を教えてもらいましたので紹介したいと思います。

Tell, Don't Ask(求めるな、命じよ)

llcc.hatenablog.com

クラスの役割についての原則です。

理想的なオブジェクト指向設計において、クラスを呼び出す側はAsk(求めること)はせずに、Tell(命令)だけするべきという原則です。オブジェクトに対してあれこれと聞いて手元でロジックを組み立てること(Ask)は良い設計とされていません。オブジェクトに対してロジックの結果を聞け(Tell)ということです。

悪い例 Ask 版

ここでは呼び出す側は求めています。

もし、macのosが:mojaveだったら、osを:catalineにして変更してね。と求めています。

class Computer
  attr_accessor :type, :cpu, :memory, :hard_disk, :os

  def initialize(type, cpu, memory, hard_disk, os)
    @type = type
    @cpu = cpu
    @memory = memory
    @hard_disk = hard_disk
    @os = os
  end
end

mac = Computer.new(:mac, :intel, 32, 124, :mojave)
mac.os
=> :mojave

# もし、macのosが:mojave だったら、osを:catalineにしてね。と求めている。
mac.os = :catalina if mac.os == :mojave
mac.os
=> :catalina

良い例 Tell版

ここでは呼び出す側は命令しています。

mojave?メソッドを呼びます。呼ばれたmojave?メソッドは、@os == :mojaveを調べます。その後、mojaveだったら、trueなので、次は、uppgrade!メソッドが呼ばれます。:catalineが代入されます。

class Computer
  attr_accessor :type, :cpu, :memory, :hard_disk, :os

  def initialize(type, cpu, memory, hard_disk, os)
    @type = type
    @cpu = cpu
    @memory = memory
    @hard_disk = hard_disk
    @os = os
  end

  def upgrade!
    @os = :catalina
  end

  def mojave?
    @os == :mojave
  end
end

mac = Computer.new(:mac, :intel, 32, 124, :mojave)
mac.upgrade! if mac.mojave?
=> :catalina

参考書籍

🍒 まとめ

クラスの後半戦に入り、徐々に内容が難しくなってきました。

しかし、同時に参加している『オブジェクト指向でなぜつくるのか』輪読会で学んだことも理解の後押しとなり、オブジェクト指向の需要な要素である「クラス」について、理解ができるようになってきました。

クラスは「まとめて、隠して、たくさん作る」仕組みです。プログラムの無駄を省いて整理整頓するために、このクラスの仕組みが必要不可欠となります。まだまだとっちらかっている自分で書いたプログラム。今後はクラスを用いて整理整頓できるように、試行錯誤していきたいと思います。

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

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

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

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

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

🍒 輪読会 第10週目まとめ

第7章7.1.1〜第7章7.5.3まで

期間:2021年07月26日〜2021年07月30日

クラスを使う場合と使わない場合の比較

オブジェクト指向でなぜつくるのかに書かれていたことですが、クラスを用いる理由にも該当するため紹介したいと思います。

オブジェクト指向はソフトウエアの保守や再利用をしやすくすることを重視する技術です。個々の部品により強く着目し、部品の独立性を高め、それらを組み上げてシステム全体の機能を実現することを基本にします。部品の独立性を高めることで、修正が起きた場合の影響範囲を最小限にし、他のシステムで容易に再利用できるようにします。 出典:オブジェクト指向でなぜつくるのか 第3版 22頁

上記のオブジェクト指向を元に、まずはクラスを用いることで堅牢なプログラムの作成の基本を学びます。プログラムが大規模になるほど、データとメソッドを一緒に持ち運べるクラスのメリットは大きくなります。

クラスを使わないプログラムの例

  1. 配列を準備し、欲しいコンピュータのハッシュデータを入れます。
  2. その後、配列にしたcomputers変数のデータをeachで取り出します。
computers = []
computers << {type: 'mac', cpu: 'M1', memory: 32, storage: 'SSD256' }
computers << {type: 'win', cpu: 'intel', memory: 16, storage: 'HDD50' }

computers.each do |c|
    puts "タイプ: #{c[:type]}、CPU: #{c[:cpu]}、メモリ: #{c[:memory]}、ストレージ: #{c[:storage]}"
end

=> タイプ: mac、CPU: M1、メモリ: 32、ストレージ: SSD256
=> タイプ: win、CPU: intel、メモリ: 16、ストレージ: HDD50

クラスを使ったプログラムの例

  1. Computerクラスを準備します。
  2. コンピュータのスペック表示用でメソッドを定義します。

attr_reader initialize は後ほど取り上げます。

class Computer
    attr_reader :type, :cpu, :memory, :storage

    def initialize(type, cpu, memory, storage)
       @type = type
       @cpu = cpu
       @memory = memory
       @storage = storage
    end

    def display_pc_spec
        puts "タイプ: #{@type}、CPU: #{@cpu}、メモリ: #{@memory}、ストレージ: #{@storage}"
  end
end
  1. Computer.newで、インスタンスを生成します。その際、引数に欲しいコンピュータのデータを渡しておきます。
  2. 定義したメソッドを呼び出します。
mac = Computer.new(:mac, :M1, 32, :SSD256)
mac.display_pc_spec
#=> タイプ: mac、 CPU: M1、メモリ: 32、ストレージ: SSD256

win = Computer.new(:win, :intel, 16, :HDD500)
win.display_pc_spec
=> タイプ: win、 CPU: intel、メモリ: 16、ストレージ: HDD500

クラスを使わない場合、使った場合ともに、結果は同じになります。

オブジェクト指向を完全に表現できているわけではないと思いますが、2つの例から、後者がよりオブジェクト指向寄りのプログラムになります。

クラスを定義することで、コンピュータのスペック内容を(属性)を保持することのできる、設計図が出来上がります。class Computer

それから、動作や振る舞いを表すメソッドを定義します。def display_pc_spec 今回は、コンピュータのスペック情報を表示できる機能を持たせました。

ここまで出来たら、後はクラスに命を吹き込みます。インスタンスを作ります。Computer.new引数に欲しいコンピュータの情報を渡してあげることで、希望するインスタンスを生成することができます。

最後に、メソッドを呼び出しますmac.display_pc_spec。レシーバ(先ほど生成したインスタンス)に対して、メソッドを実行することができます。mac変数(レシーバ)に対して、「スペックを表示して」という指示を出しています。

オブジェクト指向プログラミング関連の用語

クラス、インスタンス

上記で取り上げたクラスのことですが、クラスは「オブジェクトの設計図」と表現されることが多いです。

クラスはインスタンス(オブジェクト)と対になる要素です。上記の例では、コンピュータ(クラス)、Macインスタンス)という例で書いてみましたが、コンピュータクラスを使って、いろいろなインスタンスを生成することができます。Winのコンピュータもあれば、Macの超ハイスペックモデルもあるかもしれません。予め容易した属性に、好きなデータを入れ込んで作ることができます。

Compute.new(:好きなOS, :好きなCPU, 希望するメモリ, :希望するストレージ)

クラスは、英語でclass「分類、種類」「同種のものの集まり」を意味します。インスタンスは、英語でinstance「具体的なモノ、実例」を意味します。クラスという種類を元に、そこから具体的なモノを生成するしくみは、プログラムをより堅牢で再利用しやすくすることに繋がります。

オブジェクト、インスタンス、レシーバ

3つとも同じ意味です。

文脈によって使い分けられているようです。

インスタンスという言葉はオブジェクトとほとんど同じ意味で使われています。一方、あるオブジェクトが、あるクラスに属していることを強調する場合には、「インスタンス」のほうがよく使われます。 出典:たのしいRuby 79頁


レシーバは英語で書くと"receiver"で、「受け取る人」や「受信者」という意味です。なので、「レシーバ」は「メソッドを呼び出された側」というニュアンスを出したいときによく使われます。 出典:チェリー本 210頁

メソッド、メッセージ

オブジェクトが持つ「動作、振る舞い」をメソッドと表現します。

ここでは、メソッド名をdisplay_pc_specと定義し、Computerクラスから、インスタンスを生成するときに、コンピュータのスペックを表示する振る舞いを持つメソッドを書きました。

def display_pc_spec
    puts "タイプ: #{@type}、CPU: #{@cpu}、メモリ: #{@memory}、ストレージ: #{@storage}"
end

メッセージは、レシーバと組み合わせて使われます。

変数のmacをレシーバとし、display_pc_specをメッセージとして呼んでいます。「コンピュータのスペックを表示しろ」と、レシーバに対してメッセージを送っているイメージです。

mac = Computer.new(:mac, :M1, 32, :SSD256)
mac.display_pc_spec

状態(ステート)

オブジェクトごとに保持される状態のことを指します。ここでは、引数で渡されたデータによってインスタンスが生成されます。生成されたインスタンスは、「macタイプで、M1のCPUを搭載した、メモリ32でストレージSSD256のコンピュータ」になります。このコンピュータの持つデータが、オブジェクト指向の考え方で言う状態(ステート)です。

mac = Computer.new(:mac, :M1, 32, :SSD256)
mac.display_pc_spec
=> タイプ: mac、 CPU: M1、メモリ: 32、ストレージ: SSD256

属性(アトリビュート、プロパティ)

attr_reader :type, :cpu, :memory, :storage として、オブジェクトに設定することができる値を属性と呼びます。

mac = Computer.new(:mac, :M1, 32, :SSD256) クラス.newをする際に引数を渡していますが、これにより属性:type, :cpu, :memory, :storageへ希望する値を指定してオブジェクトを生成することができます。

属性とは、オブジェクトに属していて、取得、設定が可能な値のことです。

class Computer
    attr_reader :type, :cpu, :memory, :storage

# 省略

mac = Computer.new(:mac, :M1, 32, :SSD256)
mac.display_pc_spec
=> タイプ: mac、 CPU: M1、メモリ: 32、ストレージ: SSD256

initializeメソッド

インスタンスを初期化するために使われます。クラス名.newを実行することにより、オブジェクトが生成されますが、その際、真っ先に実行されるメソッドです。

class Computer
    def initialize(type, cpu, memory, storage)
        puts "タイプ: #{type}、CPU: #{cpu}、メモリ: #{memory}、ストレージ: #{storage}"
    end
end

Computer.new(:mac, :M1, 32, :SSD256)
=> タイプ: mac、CPU: M1、メモリ: 32、ストレージ: SSD256

引数の数が合わないとき、エラーになります。

実引数へ4つ与えられるべきですが、3つですよ。足りないですよ!と、怒られています。

class Computer
    def initialize(type, cpu, memory, storage) # 仮引数
        puts "タイプ: #{type}、CPU: #{cpu}、メモリ: #{memory}、ストレージ: #{storage}"
    end
end

Computer.new(:mac, :M1, 32)  # 実引数
(irb):30:in `initialize': wrong number of arguments (given 3, expected 4) (ArgumentError)

アクセサメソッド

インスタンス変数の値を読み書きするメソッドのことです。

  • attr_reader 読み取り専用(ゲッターメソッド)
  • attr_writer 書き込み専用(セッターメソッド)
  • attr_accessor 読み書き両方に対応

Module#attr_accessor (Ruby 3.0.0 リファレンスマニュアル)

ゲッターメソッド(読み取り専用部分)

attr_readerを使わない場合のプログラム

def computer
    @type
end

セッターメソッド(書き込み専用部分)

attr_writerを使わない場合のプログラム

computer=メソッドは、computerとしていますが、fooでもbarでも何でも使えます。このメソッドは、@typeを外部から変更するためのメソッドです。computer=(value) の仮引数としてvalueとしていますが、これもcomputerと同じく何でも大丈夫です。慣例的にvalueにすることが多いそうです。

Rubyは、=で終わるメソッドを定義すると、変数に代入するような形式でそのメソッドを呼び出すことができます。個人的にはこの項目はかなり躓きました。こちらは、『ゼロからわかるRuby超入門』の198頁の説明が分かりやすくてオススメなのでぜひご一読ください。

def computer=(value)
    @type = value
end

実際にプログラムに書き込んでみます。

読み取り、書き込み共に成功します。

class Computer
    def initialize(type)
        @type = type
    end

    def computer
        @type
    end

    def computer=(value)
        @type = value
    end
end

# インスタンスを生成する
mac = Computer.new(:mac)
#=> #<Computer:0x00007ffb288ff400 @type=:mac>

# 読み取り
mac.computer
=> :mac

# 書き込み
# 変数に代入しているように見えるが、メソッドのcomputer=(value)を呼び出している。
mac.computer = :マックブック
=> :マックブック

ゲッターメソッドだけ削除してみます。

読み取りはエラーが出ます。書き込みは成功します。

class Computer
    def initialize(type)
        @type = type
    end

    def computer=(value)
        @type = value
    end
end

win = Computer.new(:win)
=> #<Computer:0x00007fd9520ade48 @type=:win>

# 読み取りはエラーが出る。
win.computer
(irb):18:in `<main>': undefined method `computer' for #<Computer:0x00007fd9520ade48 @type=:ウィンドウズ> (NoMethodError)

# 書き込みは成功。
win.computer = :ウィンドウズ
=> :ウィンドウズ

attr_accessorメソッド(読み取り、書き込み)

attr_accessorメソッドを使ってプログラムを呼び出してみます。

このメソッドは、読み取り、書き込みの両方に対応しています。

class Computer
    attr_accessor :type

    def initialize(type)
        @type = type
    end
end

# インスタンスを生成する。
linux = Computer.new(:linux)
=> #<Computer:0x00007fd955a23ce0 @type=:linux>

# 読み取り
linux.type
=> :linux

# 書き込み
linux.type = :リナックス
=> :リナックス

attr_reader メソッド 読み取り専用(別名:ゲッターメソッド)

読み取り専用にしたい時は、こちらのメソッドでも対応できます。

class Computer
    attr_reader :type

    def initialize(type)
        @type = type
    end
end

# インスタンスを生成する。
linux = Computer.new(:linux)
=> #<Computer:0x00007fd955a23ce0 @type=:linux>

# 読み取り
linux.type
=> :linux

# 書き込みエラー
linux.type = :リナックス
(irb):10:in `<main>': undefined method `type=' for #<Computer:0x00007f8a9610b008 @type=:linux> (NoMethodError)

attr_writer メソッド 書き込み専用(別名:セッターメソッド)

書き込み専用にしたい時は、こちらのメソッドでも対応できます。

class Computer
    attr_writer :type

    def initialize(type)
        @type = type
    end
end

# インスタンスを生成する。
linux = Computer.new(:linux)
=> #<Computer:0x00007f8a9b2133b0 @type=:linux>

# 読み取りエラー
linux.type
(irb):9:in `<main>': undefined method `type' for #<Computer:0x00007fe0640e1108 @type=:linux> (NoMethodError)

# 書き込み
linux.type = :リナックス
=> :リナックス

クラスメソッドの定義

クラスメソッドとは、オブジェクトを作らずに呼び出せるメソッドのことです。レシーバがクラスになるので、クラスに対して呼び出せます。ひとつひとつの「インスタンスに含まれるデータは使わない」メソッドを定義したい場合もあるので、そのような場合はクラスメソッドを定義した方が良いです。

クラスメソッドを定義する方法は2つありますが、ここでは、よく使われるメソッドの前にselfを付ける手法を例に取り上げます。

class クラス名
    def self.クラスメソッド
         # クラスメソッドの処理
    end
end

プログラムの例が無理やり感ありますが、インスタンスを生成しなくてもメソッドを呼び出すことができています。

class Computer
    def initialize(type)
         @type = type
    end

    def self.upgrade(type)
        type.upcase
    end
end

Computer.upgrade(:big_sur)
=> :BIG_SUR

メソッド名の表記法について

メソッドの表記についてです。るりまや、他ドキュメント等では下記のように記載されています。

  • クラス名#メソッド名

#インスタンスメソッドであることを表しています。

  • クラス名.メソッド名 または、クラス名::メソッド名

. ::はクラスメソッドであることを表しています。

このマニュアルのヘルプ (Ruby 3.0.0 リファレンスマニュアル)

定数

一般に定数という言葉は、書き換えが不可能なことを表します。

定数 (プログラミング) - Wikipedia)

しかし、Rubyにおける定数は、書き換えが可能です。

Rubyの定数は「みんなわざわざ変更するなよ」と念押しした変数のようなものです。定数という言葉に惑わされないようにしてください。ミュータブル(変更可能)なオブジェクトの場合、定数の中身を変更できます。(String, Array, Hashなど)

定数を使用する理由は、 マジックナンバー をなくすためというのもあります。

ハードコーディングされた値のことをマジックナンバーと呼びます(本来、別の場所に保存しておくべき値をソースコードの中に直接記述してしまうこと)

また、書き方についてですが、定数は大文字で始める必要があります。最初の一文字が大文字であればOKですが、慣習的には全部大文字で書くことが多いです。

COMPUTER = 'MacBookPro'
=> "MacBookPro"

COMPUTER = 'WindowsMachine'
warning: already initialized constant COMPUTER
warning: previous definition of COMPUTER was here
=> "WindowsMachine"

selfキーワード

Rubyでは、インスタンスメソッドの中で、メソッドのレシーバ自身を参照するために、selfという特別な変数を使います。selfを付けても付けなくても挙動は変わりませんが、ここでのポイントは、selfがレシーバであることを理解することです。ちなみに、selfは省略して書かれることが多いとの事です。

以下、メソッドを呼び出す3パターンを用意しました。

  1. selfなしで、greet_kiyoshiroメソッドを呼び出す
  2. self付きで、greet_hirotoメソッドを呼び出す
  3. インスタンス変数@greetingを直接参照して、greet_yusukeメソッドを呼び出す
class RocknrollHero
    attr_accessor :greeting

    def initialize(greeting)
        @greeting = greeting
    end

    def greet_kiyoshiro
        "#{greeting}!!、愛しあってるかい!?"
    end

    def greet_hiroto
        "#{self.greeting}!!、我々はクロマニヨンズだ!!"
    end

    def greet_yusuke
        "#{@greeting}!!、俺たちが日本のザ・ミッシェルガンエレファントだ!!"
    end
end

hero = RocknrollHero.new('ロックンロール')
=> #<RocknrollHero:0x00007f9cdd97df70 @greeting="ロックンロール">

hero.greet_kiyoshiro
=> "ロックンロール!!、愛しあってるかい!?"

hero.greet_hiroto
=> "ロックンロール!!、我々はクロマニヨンズだ!!"

hero.greet_yusuke
=> "ロックンロール!!、俺たちが日本のザ・ミッシェルガンエレファントだ!!

selfが省略できない場合

name= メソッドのように、=で終わるメソッドを呼び出す場合は、selfの省略ができません。メソッド内で、セッターメソッド(書き込み専用)を呼び出す際には注意が必要です。

class RocknrollHero
    attr_accessor :greeting

    def initialize(greeting)
        @greeting = greeting
    end

    def greet_other_kiyoshiro
        greeting = 'ハローベイベー'
    end

    def greet_other_yusuke
    self.greeting = 'ハローベイベー'
    end
end

hero = RocknrollHero.new('ロックンロール')
=> #<RocknrollHero:0x00007f9cda3e71e0 @greeting="ロックンロール">

hero.greet_other_kiyoshiro
hero.greeting
=> "ロックンロール"    # 書き込み(変更)できない

hero.greet_other_yusuke
hero.greeting
=> "ハローベイベー"

輪読会「オブジェクト指向でなぜつくるのか」へ参加

クラスの章に入るタイミングで、下記の輪読会が開催されると聞き「これは良きタイミングだ!」と思い参加してみました。(7/26に開催)

www.nikkeibp.co.jp

ここ最近よく耳にする「オブジェクト指向

わかるようでわからない。そんな、奥の深いオブジェクト指向を学べる良書ということで、ワクワクした気持ちで本を読みました。週一ペースの開催になるため、まだ第2章までしか読み進めていませんが、こちらで学んだことも一部備忘録として残しておきたいと思います。

オブジェクト指向はソフトウエア開発の総合技術

オブジェクト指向でなぜソフトウエアを作るのですか?」誰かにこんな質問をされたなら、著者はこう答えます。「その理由はソフトウエアを楽に作りたいからです。」 出典:オブジェクト指向でなぜつくるのか 第3版 21頁

冒頭の一文です。

当初、私はオブジェクト指向とは、よく見かけるたい焼きや動物などで表現されている、単にクラスやインスタンスの話しだけだと思っていました。しかし、そんな単純なことではなく、今やオブジェクト指向をカバーする範囲は広く、ソフトウエア開発の総合技術として、プログラマが「ソフトウエアを楽につくるため」に取られる開発手法全般を指していることがわかりました。

オブジェクト指向が難しい理由3つ

  1. プログラミング言語の仕組みが複雑
  2. 比喩を使った説明による混乱
  3. オブジェクト指向というコンセプトが抽象的

また、難しいと思われている理由が3つ述べられており、特に3つ目の理由にも当てはまるであろう「全てがオブジェクト」という概念。このあたりの、難しいと思っている理由を少しでも紐解き、今後の章を読み進めるなかで理解を進めていきたと思います。

参考書籍

🍒 まとめ

今週からついにクラスの章に入りました!

輪読会で一番読みたかった章です。一度、通読してはいますが、クラスの章は「手強い...」という印象で、前回は完敗とまではいきませんが、負けを認めざるえない散々な結果でした...。輪読会メンバーの多くもこの章で苦労している方が多かったです。今回はそんな強敵クラスへリベンジすべく!疑問点は輪読会内で積極的に質問したり、復習にも時間をかけたりと気合を入れて取り組みました。

また、『オブジェクト指向でなぜつくるのか第3版』の輪読会も始まり、クラスの章を学ぶ上で、オブジェクト指向の考えが助けになっています。

学習を進めれば進めるほど、色々な壁が立ちふさがりますが、ひとつひとつ積み重ねて乗り越えていきたいと思います。

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

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

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

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

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

🍒 輪読会 第9週目まとめ

第6章6.3〜第6章6.4まで

期間:2021年07月19日〜2021年07月23日

第8週目で紹介したQiita記事の続きから進めました。

今週は、週の最後で第7章(クラス)にも入りましたが、こちらは第10週目の記事で取り上げたいと思います。

正規表現Regexpクラスのオブジェクト

Rubyでは扱える全ての値はオブジェクトです。

正規表現は、Regexpクラスになります。

class Regexp (Ruby 3.0.0 リファレンスマニュアル)

正規表現は、/スラッシュで囲んだ形式で記述します。

\w は、[a-zA-Z0-9_](半角英数字とアンダースコア1文字) と、同等の意味になります。

wは小文字です。大文字のWになると、意味が変わってきますので注意です。[^a-zA-Z_0-9](半角英数字とアンダースコア以外の1文字)になります。

regexp = /\w{6}-\d{3}/
=> /\w{6}-\d{3}/
regexp.class
=> Regexp

=~ を使って正規表現を比較する

=~を使い、マッチした場合はマッチした最初の文字列の位置が返り値として戻ってきます。

'MONGOL-800' =~ /\w{6}-\d{3}/
=> 0

マッチしなかった場合は、nilが返ってきます。

'GO!GO!-7188' =~ /\w{6}-\d{3}/
=> nil

マッチした場合は真、マッチしなかった場合はnil(偽)を返す性質を利用し、if文などの条件分岐でよく使われます。

 if 'MONGOL-800' =~ /\w{6}-\d{3}/
   puts 'マッチしたさ〜'
 else
   puts 'マッチしないさ〜'
 end
マッチしたさ〜

!~ を使って正規表現を比較する

!~ は、=~の逆の結果が返ります。マッチしなかったときに真を、マッチしたときに偽を返せます。

 if 'MONGOL-800' !~ /\w{6}-\d{3}/
   puts 'マッチしたさ〜'
 else
   puts 'マッチしないさ〜'
 end
マッチしないさ〜

キャプチャ機能

英語のcaptureは「捕まえる」「取る」の意味になります。そこから、コンピュータにデータを取り込むことを指す用語として使われています。正規表現のキャプチャ機能は、マッチした部分の一部を取り出すものです。()を使い、抜き出したい部分を指定します。

キャプチャ機能でマッチした部分は、$数字の形式で取り出すことができます。

 /(\w{6})(-)(\d{3})/ =~ 'MONGOL-800'
=> 0
 $1
=> "MONGOL"
 $2
=> "-"
 $3
=> "800"

MatchDataオブジェクト

class MatchData (Ruby 3.0.0 リファレンスマニュアル)

matchメソッドを使い、MatchDataオブジェクトとして扱うことで、マッチした箇所を指定して取り出すこともできます。0は、マッチ全体を返します。

 name = 'MONGOL-800'
 m = /(\w{6})(-)(\d{3})/.match(name)
=> #<MatchData "MONGOL-800" 1:"MONGOL" 2:"-" 3:"800">
 m[1]
=> "MONGOL"
 m[2]
=> "-"
 m[3]
=> "800"
 m[4]
=> nil
 m[0]
=> "MONGOL-800"

キャプチャの結果に名前を付ける

上記の方法でキャプチャした結果に名前を付けることができます。(?<foo>)というメタ文字を使います。キャプチャの結果に名前を付けることで、取り出すときに指定した名前で呼び出せます。名前があることで、何の値を取得しているのかが分かりやすくなります。

 name = 'MONGOL800のメンバーはkiyosaku、takashi、satoshiです'
 m =  /(?<band_name>\w{9})のメンバーは(?<bass>\w+)(?<guitar>\w+)(?<drums>\w+)です/.match(name)
=> #<MatchData...
 m[:band_name]
=> "MONGOL800"
 m[:bass]
=> "kiyosaku"
 m[:guitar]
=> "takashi"
 m[:drums]
=> "satoshi"

正規表現と組み合わせると便利なメソッド

scanメソッド

String#scan (Ruby 3.0.0 リファレンスマニュアル)

matchメソッドと似ていますが、scanメソッドは引数で渡した正規表現にマッチする部分を「配列」に入れて返します。(matchメソッドは、マッチした場合には結果を MatchData オブジェクトで返します。マッチしなかった場合 nil を返します。)

以下は、マッチした数字\dを配列で返しています。

'MONGOL800 GO!GO!7188 10-FEET'.scan(/\d+/)
=> ["800", "7188", "10"]

() を使い、キャプチャした部分を配列の配列で返すこともできます。

'MONGOL800 GO!GO!7188 10-FEET'.scan(/(\d+)/)
=> [["800"], ["7188"], ["10"]]

[ ] メソッド

[] は、スクエアブラケット、角カッコと読みます。

エイリアスメソッド(slice)

[]メソッドを使うと、文字列から正規表現にマッチした部分を抜き出すことができます。

band_name = 'MONGOL800 GO!GO!7188 10-FEET'
band_name[/\w{6}\d{3}/]
=> "MONGOL800"

複数ある場合は、最初にマッチした文字列が返ります。

band_name = 'MONGOL800 LONDON700'
band_name[/\w{6}\d{3}/]
=> "MONGOL800"

slice! メソッド

マッチした部分が文字列から破壊的に取り除かれます。

band_name = 'MONGOL800 GO!GO!7188 10-FEET'
=> "MONGOL800 GO!GO!7188 10-FEET"
band_name.slice!(/\w{6}\d{3}/)
=> "MONGOL800"
band_name
=> " GO!GO!7188 10-FEET"

gsubメソッド、gsub!メソッド

String#gsub (Ruby 3.0.0 リファレンスマニュアル)

第1引数の正規表現にマッチした文字列を、第2引数の文字列へ置き換えるメソッドです。

band_name = 'MONGOL800'
band_name.gsub(/\d{3}/, '八百')
=> "MONGOL八百"

置き換え後に、変数band_nameを確認すると元に戻っています。

band_name
=> "MONGOL800"

破壊的に変更するには、gsub! メソッドを使います。

band_name = 'MONGOL800'
band_name.gsub!(/\d{3}/, '八百')
=> "MONGOL八百"
band_name
=> "MONGOL八百"

正規表現オブジェクトを作成する

Regexp.new

エイリアスメソッド(Regexp.compile)

Regexp.compile (Ruby 3.0.0 リファレンスマニュアル)

Regexp.newの引数へ正規表現の文字列を渡すと、正規表現オブジェクトが作成できます。

正規表現オブジェクトを変数に代入して使いたいときは、以下のようにRegexp.newが必要です。

regexp = Regexp.new('\d+')
=> /\d+/
'MONGOL800 GO!GO!7188 10-FEET'.scan(regexp)
=> ["800", "7188", "10"]

%r (%記法)

//の代わりに%r記法を使うこともできます。

以下、4つとも同じ意味です。

 /foo/
=> /foo/
 %r{foo}
=> /foo/
 %r!foo!
=> /foo/
 %r?foo?
=> /foo/

//を使う場合は、スラッシュをエスケープする必要があります。

/http:\/\/www\.google\.com/
=> /http:\/\/www\.google\.com/

%rを使うときは、スラッシュのエスケープ処理は不要です。ただし、.についてはメタ文字のため、エスケープ処理が必要になります。

%r{http://www\.google\.com}
=> /http:\/\/www\.google\.com/

case文で正規表現を使う

case文は、whenで指定した値に一致するかどうかを===演算子を使って判定しています。==ではありません。(===演算子 についての詳細は、チェリー本269頁)

=== 演算子を使うことで、オブジェクトのデータ型を識別することができます。

配列の場合

array = [1,2,3]

case array
when String
    puts '文字列'
when Array
    puts '配列'
when Regexp
    puts '正規表現'
end
配列

正規表現の場合

regexp = Regexp.new('\d+')

case regexp
when String
    puts '文字列'
when Array
    puts '配列'
when Regexp
    puts '正規表現'
end
正規表現

when節に正規表現を指定して、以下のようにマッチする文字列を判定するときに使うこともできます。

band_name = 'MONGOL800'

case band_name
when /\w{6}\d{3}/
    puts 'モンパチ'
when /\d{3}/
    puts '八百'
end
モンパチ

輪読会メンバーがLT会へ登壇!

今回のブログを書き始めた8月7日のこの日、フィヨルドブートキャンプ内で開催されるLT会へ参加しました。輪読会メンバーのぺろさん(id:mosshu0703)と、いっしーさん(id:isshi-hasegawa)が登壇されたのですが、とても良かったので当ブログでも感想を書きたいと思います。

隣の芝生はいつも青い

フィヨルドブートキャンプは、メンターさん、アドバイザーさんをはじめ、同じ受講生でも凄い方が多く、隣どころか360度青くしか見えない私にとって、ぺろさんの発表はとても勇気がもらえました。

自分の庭に集中しよう

ほんとコレですね。

分からないことやできないこと、詰まって思い悩むことは今でも頻繁にありますが、そんなときこそ、ぺろさんのLTを思い出し、目の前のことに集中し人と比べず取り組んでいきたいと思います。

エモくて元気になれる素敵なLTでした!サンクス!

eminoprogramming.hatenablog.com

2021年フィヨルドブートキャンプの旅

映画が好きないっしーさん。物語のような発表構成で、スライドの冒頭からその世界観に引き込まれました。普段から輪読会やもくもく会などで積極的にコミュニケーションを取られている姿や、何でも器用にこなしている姿しか知らなかったので、今までの独学期間で思い悩まれていた話しなどは意外でした。当時の苦労があったからこその今に繋がっているんですね。それにしてもまだ3ヶ月ってのは本当に凄い!

技術や言語が好きになる = マジ最強💪

もちろん、ほとんどの受講生が転職やスキルアップなど、何らかしらの現実的な目標を持ち学習に取り組んでいるとは思いますが、シンプルに「好き」だからやる。これが一番最強なんだと思えました。あれやこれやと考え、上手くいかないことも多かったりするのですが、もっとシンプルに考えていきたと思いました。私もまだまだ旅の途中ですが、楽しんで経験値を積み、まずはフィヨルドブートキャンプというゲームのクリアを目指し、頑張っていきたいと思います!

isshi-hasegawa.hatenablog.com

「好き」に繋がりますが、我らが輪読会のオーガナイザー、トミーさん(id:eatplaynap329)のアツいツイートもご紹介🔥

参考書籍

🍒 まとめ

正規表現の章を終え、基礎を学んだことで、「意味不明の恐ろしい呪文」がそうではなくなりました。他の場所で見かける正規表現に対しても、「ひとつひとつ調べればわかる」と、うろたえることなく扱えるようになったことは、またひとつ自信になりました。

いよいよ次は、最初の関門「クラス」の章に入ります。

オブジェクト指向の知識も必要となってくる章となり、難易度がグッと上がります。

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

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

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

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

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

🍒 輪読会 第8週目まとめ

第6章6.1.1〜第6章6.2.2章まで

期間:2021年07月12日〜2021年07月16日

正規表現とは?

「パターンを指定して、文字列を効率よく検索・置換するためのミニ言語」

チェリー本ではこのように表現されています。

正規表現Rubyだけのものではなく、他プログラミング言語でも使える汎用的な表現方法です。

いくつかの文字列を一つの形式で表現するための表現方法とも言えます。

英語で書くと、Regular Expression となり、Regex, Regexpと略されます。

正規表現をゼロから学習するための参考資料(Qiita記事)

チェリー本でも紹介されていますが、まずはこちらの伊藤さんのQiita記事を元に学習を進めていきました。

輪読会では、その1からその3までを取り上げました。その4については、Qiita記事にもあるように「中級者向け」の内容となり、一気に難易度が上がっています。今回はチェリー本の本筋から外れてしまうため、一旦こちらは各自で目を通すだけにし、深追いするのはやめました。

メタ文字

正規表現では、さまざまなメタ文字が使われています。

メタ文字とは「コンピュータープログラムに与える文字列データの中で、特別な意味や役割を持つ文字のこと」です。\[ ]- などのことを指します。

例えば、\dは半角数字1文字を表します。[AB] は、AまたはBが1文字であることを表しています。

正規表現とは、このようなメタ文字の組み合わせで表現する、検索・置換するためのミニ言語です。

RubyMineで正規表現の置換モードを使う

ショートカットコマンド command + R にて、入力できる項目が表示されます。

その後、矢印の箇所をクリックにしONにすることで、正規表現の検索・置換モードへ切り替えられます。

f:id:shirotamaki:20210804172838p:plain

このような形で、正規表現がマッチする箇所がハイライトされます。

f:id:shirotamaki:20210804172913p:plain

タブ文字は何文字分か?

タブ文字は \t で表現できます。

タブ文字の見た目が「スペース何文字分か」は、出力するエディタなりコンソールなりの設定によって異なるようです。Rubyではタブ文字は半角スペース2文字分が推奨されていることが多いため、私もRubyMineでは、文字数「2」へ設定しています。

Rubyでは、内部に含まれるものを記述する時、常に2文字のインデントが推奨されています。 参照:独習Ruby on Rails(40頁)

環境設定command + ,から、エディター > コードスタイル > Ruby で変更できます。

f:id:shirotamaki:20210804172941p:plain

\t の代わりに\sを使うこともできる

\s は、空白文字を表します。\s には、タブ文字\t改行文字\nや復帰文字\rなども含まれています。ただし、\s にマッチする文字は、言語や環境によって違うので注意が必要です。

Rubyの場合は、こうなります。\s = [ \t\r\n\f]

正規表現との出会い

余談ですが、初めて正規表現と出会った時のことを思い出したため書きたいと思います。

数ヶ月も前の話しですが、Railsチュートリアルへ取り組んでいるとき、講師の@yasulab さんが、解説動画内でこのようなことを仰っていました。

  • 正規表現は奥が深い。
  • 正規表現にテーマを絞ったカンファレンスも開催されており、好きな人にはたまらない。
  • 正規表現をツマミに酒が飲めるww
  • 正規表現は実際に試しながら学んだ。

そこでは、正規表現の教科書とも言える、O'Reillyから出版されている本も紹介されていたのですが、こちらの本はなんと正規表現だけで528頁もあるそうです。(チェリー本の正規表現の章は25頁)

www.oreilly.co.jp

@yasulab さんも、こちらの本を全部は読んでいないそうで、正規表現は実際使っていくうちに覚えていったそうです。

やはり「手を動かす」ことが大事なのですね。

今回改めてチェリー本で正規表現を学び直すなかで、正規表現をツマミにお酒が飲める気はまったくしませんがww、コーヒーくらいなら楽しめるようになってきたと思います。

いつかは、フクロウ本にもチャレンジしてみたいです!

るりま勉強会(Kyoto.rbへ潜入!)

輪読会でお世話になっている(id:sanfrecce-osaka) さんの紹介で、この日(2021年7月11日)るりま勉強会が開催されることを知りました。Kyoto.rbで開催されるとのことで、初めて参加してみました。

以前、ブログや、LTの題材としても取り上げたり、日々慣れ親しんではいるものの、まだまだ知らないことも多く苦手意識がある「るりま」。今回、少しでも学び、気づきが得れればと思い参加しましたが、結果、とても良かったです。特にRuby熟練者からのるりまに関する話しは貴重で、とても学びの多い勉強会でした。

  • 10年近くRubyを使っているが、それでも知らないメソッドはたくさんある。など、現役エンジニアからの声。
  • るりまの読み方
  • permalink, rdoc とは?
  • ターミナルでsayとすると、音声で読み上げてもらえる!(小ネタ的なことも!)

などなど。

同じく参加された(id:fuga__ch) さんのブログに詳しい勉強会の内容がまとまっています。 勉強会の詳細が知りたい方はぜひ!

fuga-ch85.hatenablog.com

また、今回の勉強会には多くのフィヨルド生や卒業生が参加されていました。普段より多くの参加者に対し、主催者の方も驚かれていましたww 有益な勉強会を開催してくださったKyoto.rbの皆さま、ありがとうございました!ぜひまた第二弾!楽しみにしております!

参考書籍

🍒 まとめ

今週から入った正規表現。Qiitaの記事を中心に、その1からその3まで進めました。

こちらの記事は「正規表現初心者」を対象としている内容なので、とてもわかり易かったです。

謎の呪文が\d{2,5}[-(]\d{1,4}[-)]\d{4} 謎ではなくなり、基本的な正規表現についてはポイントを抑えることができました。

「手と目で覚える正規表現

伊藤さんも仰っているように、手を動かして使い慣れていくことが、正規表現を身につける一番の近道だと思いました。今回学んだRubyMineの検索・置換モードや、Rubular を活用し身につけていきたいと思います。

次週も引き続き正規表現へ取り組みます。

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

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

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

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

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

🍒 輪読会 第7週目まとめ

第5章5.4.1〜第5章5.8まで

期間:2021年07月5日〜2021年07月9日

ハッシュのキーにはシンボルを使う

チェリー本輪読会 第6週目まとめ - D IT Yのハッシュの書き方でも言及しましたが、ハッシュのキーは文字列とシンボルを使うことができますが、シンボルを使うことが推奨されています。理由としては、文字列より高速に処理でき、イミュータブルなので勝手に値が変えられる心配がないためです。

また、ハッシュのキーを指定する際、文字列とシンボルを混在して使用することも可能ですが、混乱を招くため統一したほうが良いです。

下記はキーを混在して書いた例です。エラーは起きませんが、統一感がなく混乱を招きます。

*irb(main):001:0> records = { 'first_single' => 'LAMP', :second_sigle => 'ダイヤモンド', third_single: '天体観測'
 }*
=> {"first_single"=>"LAMP", :second_sigle=>"ダイヤモンド", :third_single=>"天体観測"}

メソッドのキーワード引数

可読性を上げるためにキーワード引数を使用します。

def メソッド名(キーワード引数1: 引数1の値,  キーワード引数2: 引数2の値)
    #  メソッドの実装
end

キワード引数名: 値の形式は、引数名だけではなく、デフォルト値の指定も同時に行っています。

irb(main):022:1* def count(math: 0, english: 0)
irb(main):023:1*   puts total_score = math + english
irb(main):024:0> end
=> :count
irb(main):025:0> count(math: 90, english: 80)
170
=> nil
irb(main):026:0> count(math: 50)
50
=> nil

キーワード引数がシンボルではない話

輪読会で話題に上がった内容ですが、下記の記事が参考になりました。

Rubyのキーワード引数はシンボルっぽく定義するけど、シンボルそのものではない、という話 - Qiita

ハッシュで使用頻度の高いメソッド

keysメソッド

全キーを配列で返します。指定したキーで返ってきます。(文字列なら文字列。シンボルならシンボルで返ってきます)

irb(main):001:0> records = { 'first_single' => 'LAMP', second_sigle: 'ダイヤモン
' }
=> {"first_single"=>"LAMP", :second_sigle=>"ダイヤモンド"}
irb(main):002:0> records.keys
=> ["first_single", :second_sigle]

Hash#keys (Ruby 3.0.0 リファレンスマニュアル)

valuesメソッド

ハッシュの値を配列として返します。

irb(main):006:0> records = { 'hit_song' => 'およげ!たいやきくん', number_of_sal
es: 4570000 }
=> {"hit_song"=>"およげ!たいやきくん", :number_of_sales=>4570000}
irb(main):007:0> records.values
=> ["およげ!たいやきくん", 4570000]

class Hash (Ruby 3.0.0 リファレンスマニュアル)

has_key? メソッド

エイリアスメソッド(key? /include?/member?)

ハッシュの中に指定されたキーが存在するかどうか確認するメソッドです。

エイリアスメソッドであるinclude?が、英単語的に分かりやいのでこちらを採用します。

irb(main):010:0> hit_records = { mj: 'スリラー', eagles:'イーグルスベスト版', ac
dc: 'バックインブラック' }
=> {:mj=>"スリラー", :eagles=>"イーグルスベスト版", :acdc=>"バックインブラ...
irb(main):011:0> hit_records.include?(:mj)
=> true
irb(main):012:0> hit_records.include?(:beatles)
=> false

Hash#has_key? (Ruby 3.0.0 リファレンスマニュアル)

** でハッシュを展開させる

作成したハッシュを別のハッシュの中で展開することができます。

変数の前に**ダブルスプラットを書きます。**hit_records

mergeメソッドも同じ結果が得られます。{ acdc: 'バックインブラック'}.merge(hit_records) 

irb(main):013:0> hit_records = { mj: 'スリラー', eagles:'イーグルスベスト版'}
=> {:mj=>"スリラー", :eagles=>"イーグルスベスト版"}
irb(main):014:0> { acdc: 'バックインブラック', **hit_records}
=> {:acdc=>"バックインブラック", :mj=>"スリラー", :eagles=>"イーグルスベスト版"}

ちなみに、*を使って展開する方法は、splat展開と言います。

第4週目で取り上げた内容です。

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

メソッドの定義に存在しないキワード引数を受けとる

**変数名として引数へ用意します。

キーワード引数を使うメソッドに、存在しないキーワードを渡すとエラーが発生します。エラーを回避するために、**を付けた引数を用意することで、指定されていないキーワードを受け取ることができます。

irb(main):015:1* def hit_records(mj: 'スリラー', eagles:'イーグルスベスト版', **others)
irb(main):016:1*   puts others
irb(main):017:0> end
=> :hit_records
irb(main):020:0> hit_records(mj: 'スリラー', eagles: 'イーグルスベスト版', acdc: 'バックインブラック')
{:acdc=>"バックインブラック"}
=> nil

( )の省略

Rubyではメソッドの呼び出しの( )を省略することができます。

# 同じ意味
puts ('hit_records')
puts 'hit_records'

しかし、ハッシュキーワード引数では、( )を省略できない場合があります。

以下は、メソッドの呼び出し時に( )を省略した際、エラーとなります。

irb(main):021:1* def hit_records(artists = {})
irb(main):022:1*   puts artists
irb(main):023:0> end
=> :hit_records
irb(main):024:0> hit_records {mj: 'スリラー', eagles:'イーグルスベスト版'}
/Users/shiro/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.6/lib/irb/workspace.rb:116:in `eval': (irb):24: syntax error, unexpected ':', expecting '}' (SyntaxError)
hit_records {mj: 'スリラー', eagles:'イ...

( ) を付け加えると実行できます。

irb(main):025:1* def hit_records(artists = {})
irb(main):026:1*   puts artists
irb(main):027:0> end
=> :hit_records
irb(main):028:0> hit_records ({mj: 'スリラー', eagles:'イーグルスベスト版'})
{:mj=>"スリラー", :eagles=>"イーグルスベスト版"}
=> nil

第2引数にハッシュが来る場合は、省略してもエラーになりません。

irb(main):033:1* def hit_records(genre, artists = {})
irb(main):034:1*   puts artists
irb(main):035:0> end
=> :hit_records
irb(main):036:0> hit_records 'pop', {mj: 'スリラー', eagles:'イーグルスベスト版'}
{:mj=>"スリラー", :eagles=>"イーグルスベスト版"}
=> nil

Railsでは省略している場合が多い

Railsで用意しているメソッドは、基本的に( ) は省略していることが多いようです。

link_torender など。

# Railsのコード抜粋

<%= render 'form', book: @book %>

<%= link_to 'Back', books_path %>

RubyDSL(←チェリー本12章参照)っぽく見えるメソッドを省略する傾向があるようです。

DSL(ドメイン固有言語)とは - IT用語辞典 e-Words

これからRailsを学ぶ上で覚えておきたいと思いました。

ハッシュへ初期値を設定する

ハッシュへ存在していないキーを指定するとnilが返ってきます。

irb(main):006:0> hit_records = {}
irb(main):007:0> hit_records[:mj]
=> nil

Hash.newを使い、引数へ初期値となる値を入力することで、指定できます。

バックインブラック を初期値として指定しています。

irb(main):037:0> hit_records = Hash.new('バックインブラック')
irb(main):038:0> hit_records[:acdc]
=> "バックインブラック"

同じく値に初期値を設定し、変数a、変数bへそれぞれ別のキーを指定して代入してみます。

変数a、変数bともに代入できました。aとbは同じオブジェクトIDを保持しています。

irb(main):044:0> hit_records = Hash.new('バックインブラック')
=> {}
irb(main):045:0> a = hit_records[:acdc]
=> "バックインブラック"
irb(main):046:0> b = hit_records[:mj]
=> "バックインブラック"

insertメソッドを使い、変数aの文字列を変更してみます。

String#insert (Ruby 3.0.0 リファレンスマニュアル)

変数aに変更を加えた場合、変数bも同じオブジェクトIDなので、値も一緒に変わってしまいます。

irb(main):058:0> a.insert(0, "ヒットアルバム_")
=> "ヒットアルバム_バックインブラック"
irb(main):059:0> b
=> "ヒットアルバム_バックインブラック"

上記の問題を回避するためには、{ }を使う必要があります。

変数aと変数bは異なるオブジェクトIDです。変数aを変更しても変数bは変わりません。

irb(main):066:0> hit_records = Hash.new{'バックインブラック'}
=> {}
irb(main):067:0> a = hit_records[:acdc]
=> "バックインブラック"
irb(main):068:0> b = hit_records[:mj]
=> "バックインブラック"
irb(main):069:0> a.insert(0, "ヒットアルバム_")
=> "ヒットアルバム_バックインブラック"
irb(main):070:0> b
=> "バックインブラック"

to_aメソッド, to_hメソッド, to_symメソッド

ハッシュから配列へ変換する場合はto_aメソッドを使用します。

配列の配列(2次元配列)が返ります。

irb(main):003:0> hash = { mj: 'king_of_pop', elvis:'king_of_rock', jb: 'king_of_
soul' }
=> {:mj=>"king_of_pop", :elvis=>"king_of_rock", :jb=>"king_of_soul"}
irb(main):004:0> hash.to_a
=> [[:mj, "king_of_pop"], [:elvis, "king_of_rock"], [:jb, "king_of_soul"]]

反対に、to_hメソッドと使用することでハッシュへ戻すことができます。

irb(main):005:0> arry =  [[:mj, "king_of_pop"], [:elvis, "king_of_rock"], [:jb,
"king_of_soul"]]
=> [[:mj, "king_of_pop"], [:elvis, "king_of_rock"], [:jb, "king_of_soul"]]
irb(main):006:0> arry.to_h
=> {:mj=>"king_of_pop", :elvis=>"king_of_rock", :jb=>"king_of_soul"}

to_symメソッドを使用すると、文字列をシンボルへ変換することができます。

irb(main):013:0> moji = 'キング'
=> "キング"
irb(main):014:0> moji.to_sym
=> :キング

反対にシンボルを文字列に戻す場合は、to_sメソッドになります。

irb(main):017:0> symbol = :キング
=> :キング
irb(main):018:0> symbol.to_s
=> "キング"

イディオム

イディオムとは、慣用句、成句、熟語などと訳されます。習慣として長い間広く使われてきた定番の書き方をイディオムと言います。

イディオム - Wikipedia

ここでは、Rubyでよく使われるイディオムを紹介します。

ぼっち演算子 lonely operator

&. 演算子を使ってメソッドを呼び出すと、メソッドを呼び出されたオブジェクトがnilでない場合はその結果を、nilだった場合はnilを返します。

基本的な使い方は、オブジェクト&.メソッドになります。

irb(main):039:0> content  = 'コンテンツ'
=> "コンテンツ"
irb(main):040:0> content&.to_sym
=> :コンテンツ
irb(main):041:0> content = nil
=> nil
irb(main):042:0> content&.to_sym
=> nil

&. 演算子が下記のような姿に見えるため、通称「ぼっち演算子」と呼ばれています。

www.instagram.com

nilガード

通称「nilガード」と呼ばれている||=を使った自己代入の書き方になります。

下記、変数contentがnilまたはfalseであれば、||=を使い代入を試みた場合、コンテンツが代入されます。

irb(main):043:0> content = nil
=> nil
irb(main):044:0> content ||= 'コンテンツ'
=> "コンテンツ"

しかし、すでに変数contentの中身が存在する場合は代入ができません。

irb(main):048:0> content = 'コンテンツ'
=> "コンテンツ"
irb(main):049:0> content ||= '別のコンテンツ'
=> "コンテンツ"

メモ化などでも使われ、以下はSinatraを使ってアプリを作成していた際に使用したnilガードです。

PG.connect(dbname: 'memos') というデータベースへ繋ぐ処理が重いため、毎回接続処理を行なわないようにしました。nilガードを使い@connectionへキャッシュとして保持する目的で使用しました。

メモ化 - Wikipedia

class Memo
  def conn
    @connection ||= PG.connect(dbname: 'memos')
  end
en

!! ダブルバンを使った真偽値の型変換

! バンひとつだと、否定の演算子になります。

irb(main):023:0> a = true
=> true
irb(main):024:0> b = !a
=> false
irb(main):025:0> a = false
=> false
irb(main):026:0> b = !a
=> true
irb(main):027:0> a = nil
=> nil
irb(main):028:0> b = !a
=> true

!! バンバンを使うことで、否定の否定となり、trueまたは falseのどちからに変換することができます。ここで、注目したいのは、nilです。nil!は、trueが返ります。その後もう一度、!することで、true ⇒ false へ変換することができます。

irb(main):029:0> a = true
=> true
irb(main):030:0> b = !!a
=> true
irb(main):031:0> a = false
=> false
irb(main):032:0> b = !!a
=> false
irb(main):033:0> a = nil
=> nil
irb(main):034:0> b = !!a
=> false

参加人数

7月7日の七夕。

この日も、普段と変わらず輪読会が始まりました。

最初は「今日はいつもより多いな〜」ぐらいにしか思っていなかったのですが、その後天の川の星のようにどんどんを増え、最終的に21名の方が集まっていました(凄)

もちろん、過去最高の参加人数でした。

「もはやRubyコミュニティの1イベント並みの規模ですね…! rindokukai.rbとかやったらおもしろそうw」

メンターさんも参加人数の多さに驚いていました。

現在は、フィヨルドブートキャンプ内での開催ですが、将来は外部で参加者を広く募り開催できたら面白そうだなと思いました。

神降臨

七夕の翌日、またもうひとつ奇跡が起きました。

何と、まさかの伊藤さん降臨...!!!

f:id:shirotamaki:20210731103517p:plain

あまりの突然の出来事に、

全員驚き、それまでワイワイガヤガヤしていた輪読会が一気に静まり返りました…

人間は驚きと緊張が重なると静かになるようです。笑

輪読会の開催当初から気にかけていただき、質問等にも快く解答をいただけたりと、普段からお世話にはなっていたのですが、まさかリアルタイムで、直接本を書いた著者からお話しを聞けるとは思ってもいませんでした。驚きと同時にとても嬉しかったです。

伊藤さん、ありがとうございました!

KPTふりかえり会

早いもので、輪読会をスタートしてから7週間が経過しました。

会を進めていくうちに、進行や各自の負担など見直すべきこと、取り入れたいことなど意見が出ていました。しかし、輪読会後の雑談等でたまに話し合う機会もありましたが、雑談の話題のひとつで毎回終わってしまっていました。

そんな中、メンターの@rllllhoさんから、「一度KPTしてみては?」とアドバイスをいただき、KPTふりかえり会をしてみることにしました。

KPTとは?

  • K:keep = 良かったこと(今後も続けること)
  • P:problem= 悪かったこと(今後はやめること)
  • T:try = 次に試すこと

【徹底解説】正しい「KPT」が仕事の成果を生み出す!進め方のコツ、現場の事例を紹介 | SELECK [セレック]

ふりかえりメソッド「KPT」の基本とはじめ方 | Social Change!

まず、KPTを行う目的を決めました。

  • 輪読会を、楽しく・有意義(学び、成長)に運営するため

この目的に向かい、K P T の順番で、メンバー同士で率直な意見を交わしていきました。

改めてみんなでふりかってみる事で色々な気づきがありました。Tryする良き案も多く出てとても有益なKPTとなりました。今後も定期的(月1回程度)に開催をしていく予定です。

参考書籍

🍒 まとめ

今週はハッシュとシンボルを中心に学習しました。

ハッシュ、シンボルともによく使われるものですが、文字列との違いなど理解に自信がありませんでした。しかし、今回輪読会を通し学ぶことで理解でき、現在取り組んでいるSinatraを使ったメモアプリ作成の課題クリアの助けにもなりました。

また、輪読会としてもますます盛り上がりをみせており、今週は参加人数が21名まで達しました。そして何より、チェリー本の生みの親、伊藤さんのサプライズでの登場には驚きました!また来ていただけるとのことで、今後の楽しみのひとつになりました。

いよいよ次は、正規表現に入ります!

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

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

f:id:shirotamaki:20210619091936p:plain

🍒 はじめに

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

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

🍒 輪読会 第6週目まとめ

第4章4.9.1〜第5章5.3.2まで

期間:2021年06月28日〜2021年07月02日

さまざまな繰り返し処理

Rubyには、同じことを繰り返す方法がいくつかあります。

timesメソッド

繰り返しの回数が決まっているときはtimesメソッドがおすすめです。

timesメソッドは、イテレータとも呼ばれます。イテレータ「繰り返す(iterate) + もの(-or)」という意味です。Ruby 用語としては、もっぱら繰り返し処理のためのブロック付きメソッドのことを指します。

Integer#times (Ruby 3.0.0 リファレンスマニュアル)

Ruby 用語としては、もっぱら繰り返し処理のためのブロック付きメソッドのことを指す。

uptoメソッド、downtoメソッド

Integer#upto (Ruby 3.0.0 リファレンスマニュアル)

Integer#downto (Ruby 3.0.0 リファレンスマニュアル)

stepメソッド

開始値.step(上限値, 1度に増減する大きさ)

Numericとは「数、数的な、数に関するの意」

Numeric#step (Ruby 3.0.0 リファレンスマニュアル)

while文

while文は指定した条件が「真」である間、処理を繰り返します。

while 条件式 (真であれば実行)
    繰り返したい処理
end

until文

while文の反対で、条件が「偽」である間、処理を繰り返します。

until 条件式 (偽であれば実行)
    繰り返したい処理
end

for文

eachメソッド、mapメソッドで代用できるためほとんど出番のない構文です。

普段、輪読会メンバーもfor文を使っている人はいませんでした。

for文はメソッドではありません。Rubyの書き方のひとつです。

for 変数 in  配列やハッシュ
    繰り返し処理
end

loopメソッド

無限ループを作りたいときは、loopメソッドが使えます。

loop do
    無限ループ用の処理
end

例えば、puts foo と記述すると、無限に'foo'がputsされ続け大変なことになります。

そんな時、繰り返し処理を途中で抜ける方法があります。

  • break  繰り返し処理を中断し、繰り返しの中から抜けることができる
  • next  繰り返し処理を中断し、次の回の繰り返し処理へ移動することできる
  • return  繰り返し処理を中断し、メソッドから抜け出すことができる

逆に、繰り返し処理をやり直したい場合は以下を使います。

  • redo  同じ条件で繰り返し処理をやり直す

繰り返し処理用の制御構造

繰り返し処理の途中で、処理を中断したり、次の回に飛ばしたり処理を制御したいときがあります。そのような処理を制御するための構造がRubyでは用意されています。以下のキーワードで処理を制御することが可能です。

break

  • breakを使ってloop処理を抜けます。
    • 下記は、 変数mfujiwaraと同一になった場合、breakさせます。(繰り返し処理を脱出します)
    • 下記の例では、loopメソッドを使っていますが、for文やeachメソッドなど他の繰り返し記法でも利用できます。
irb(main):073:1* def choose_members
irb(main):074:1* members = ['fujiwara', 'masukawa', 'naoi', 'masu']
irb(main):075:2*   loop do
irb(main):076:2*   m = members.sample
irb(main):077:2*   puts m
irb(main):078:2*   break if m == 'fujiwara'
irb(main):079:1*   end
irb(main):080:1*   puts 'I am a composer!!'
irb(main):081:0> end
=> :choose_members
irb(main):082:0> choose_members
masukawa
naoi
masukawa
fujiwara
I am a composer!!
=> nil

next

  • nextを使って次の処理へ移動してみます。
    • next if m == 'fujiwara' がtrueとなった場合、次の繰り返し処理へ移動します。
irb(main):031:1* def check_main_songwriter
irb(main):032:2*   ['fujiwara', 'masukawa', 'naoi', 'masu'].each do |m|
irb(main):033:2*     next if m == 'fujiwara'
irb(main):034:2*     puts "#{m}さんは、違います"
irb(main):035:1*   end
irb(main):036:2*   ['作詞', '作曲'].each do |n|
irb(main):037:2*     puts "fujiwaraさんは、#{n}します"
irb(main):038:1*   end
irb(main):039:0> end
=> :check_main_songwriter
irb(main):040:0> check_main_songwriter
masukawaさんは、違います
naoiさんは、違います
masuさんは、違います
fujiwaraさんは、作詞します
fujiwaraさんは、作曲します
=> ["作詞", "作曲"]

breakとreturnの違い

breakは、上記の例で取り上げたように「繰り返し処理からの脱出」を行うために利用する記法です。

returnは、「メソッドからの脱出」を行うために利用されます。メソッドの実行を終了するとも言い換えられます。

  • returnを使ってメソッドから脱出させます。
    • 下記は、 変数mfujiwaraと同一になった場合、returnさせます。(メソッドを脱出します)
    • 下記の例では、loopメソッドを使っていますが、for文やeachメソッドなど他の繰り返し記法でも利用できます。
    • return if m == 'fujiwara' がtrueになった場合、メソッドから脱出します。メソッド内で記述しているputs 'I am a composer!!' は、実行されません。
irb(main):083:1* def choose_members
irb(main):084:1* members = ['fujiwara', 'masukawa', 'naoi', 'masu']
irb(main):085:2*   loop do
irb(main):086:2*   m = members.sample
irb(main):087:2*   puts m
irb(main):088:2*   return if m == 'fujiwara'
irb(main):089:1*   end
irb(main):090:1*   puts 'I am a composer!!'
irb(main):091:0> end
=> :choose_members
irb(main):092:0> choose_members
masukawa
masukawa
naoi
masu
fujiwara
=> nil

throw と catch を使った大域脱出

breakでは、繰り返し処理を中断し、一番内側の処理しか脱出できませんが、throwcatchを使えば、一気に外側のループまで脱出できます。(returnのようにメソッドの脱出ではありません。あくまでもループ全体の脱出になります)

:bumpがタグ文字といってcatchとthrowの後に記述します。同じタグ文字にする必要があります。

irb(main):095:0> records = [ 'LAMP', 'ダイヤモンド', '天体観測' ]
=> ["LAMP", "ダイヤモンド", "天体観測"]
irb(main):096:0> release = [1, 2, 3]
=> [1, 2, 3]
irb(main):097:1* catch :bump do
irb(main):098:2* records.shuffle.each do |record|
irb(main):099:3* release.shuffle.each do |n|
irb(main):100:3* puts "#{record}は、#{n}枚目のシングルです}"
irb(main):101:4* if record == '天体観測' && n == 3
irb(main):102:4* throw :bump
irb(main):103:3* end
irb(main):104:2* end
irb(main):105:1* end
irb(main):106:0> end
ダイヤモンドは、1枚目のシングルです}
ダイヤモンドは、2枚目のシングルです}
ダイヤモンドは、3枚目のシングルです}
LAMPは、3枚目のシングルです}
LAMPは、2枚目のシングルです}
LAMPは、1枚目のシングルです}
天体観測は、2枚目のシングルです}
天体観測は、1枚目のシングルです}
天体観測は、3枚目のシングルです}
*=> nil*

ハッシュ

ハッシュは、「キー」と「値」のセットでデータを管理するオブジェクトのことです。

他の言語では「連想配列」「辞書」「マップ」と呼ばれたりします。

英語のHashは、コマ切れ肉と野菜を炒めた料理の事を指し、ハッシュドビーフの語源でもあります。

ハッシュの3つの書き方

ハッシュロケットを使った書き方。キーは文字列です。

{ 'first_single' => 'LAMP', 'second_sigle' => 'ダイヤモンド', 'third_single' => '天体観測' }

ハッシュロケットを使った書き方。キーはシンボルです。

{ :first_single => 'LAMP', :second_sigle => 'ダイヤモンド', :third_single => '天体観測' }

ハッシュロケットを省略した書き方。キーはシンボルですが、:コロンの位置が右側に変わります。

{ first_single: 'LAMP', second_sigle: 'ダイヤモンド', third_single: '天体観測' }

ちなみに、改行して書くこともできます。

{ 
'first_single' => 'LAMP',
'second_sigle' => 'ダイヤモンド',
'third_single' => '天体観測' 
}

シンボル

シンボルは任意の文字列と一対一に対応するオブジェクトです。Rubyがメソッドなどの名前を識別するためのラベルをオブジェクトにしたものです。先頭に: をつけて表現します。

文字列との違いは、主に4つあります。

  • クラスが違います。文字列はStringクラスです。シンボルはSymbolクラスです。
# 文字列
irb#1(main):014:0> 'BumpOfChicken'.class
=> String

# シンボル
irb#1(main):015:0> :BumpObChicken.class
=> Symbol
  • 同じシンボルは同じオブジェクトIDです。文字列は毎度オブジェクトIDが変わります。
# 文字列(毎回、違う)
irb#1(main):016:0> 'BumpOfChicken'.object_id
=> 260
irb#1(main):017:0> 'BumpOfChicken'.object_id
=> 280
irb#1(main):018:0> 'BumpOfChicken'.object_id
=> 300

# シンボル(毎回、同一になる)
irb#1(main):019:0> :BumpOfChicken.object_id
=> 2296228
irb#1(main):020:0> :BumpOfChicken.object_id
=> 2296228
irb#1(main):021:0> :BumpOfChicken.object_id
=> 2296228
  • シンボルは整数として扱われるため、文字列より高速に処理できます。
    • 同じシンボルであれば同じオブジェクトである理由で、シンボルの方がメモリ効率がよいです。
    • シンボルは、ソース上では文字列のように見え、内部では整数として扱われる、両者を仲立ちするような存在です。整数として扱えることが高速に処理できる理由です。class Symbol (Ruby 3.0.0 リファレンスマニュアル)
  • シンボルはイミュータブル(非破壊的)なオブジェクトです。
    • 文字列はミュータブル(破壊的)なオブジェクトのため変更は可能ですが、シンボルはイミュータブルなため変更できません。勝手に変更されては困る名前などの用途に向いています。
# 文字列は変更が可能です。
irb(main):002:0> name = 'bump'
=> "bump"
irb(main):003:0> name.upcase!
=> "BUMP"

# シンボルは変更できません。
irb(main):004:0> name = :bump
=> :bump
irb(main):005:0> name.upcase!
(irb):5:in `<main>': undefined method `upcase!' for :bump:Symbol (NoMethodError)

irbの情報を確認

irb_info コマンドで、irbの情報を確認することができます。

shiro@shiro:~$ irb
irb(main):001:0> irb_info
=>
Ruby version: 3.0.0
IRB version: irb 1.3.6 (2021-06-19)
InputMethod: ReidlineInputMethod with Reline 0.2.6
RUBY_PLATFORM: x86_64-darwin19

irbで、行を挿入する方法

option + Enter で、実行中のirbに行を挿入することができます。

とても便利で頻繁に使っています。

GitHub内をアドバンスドサーチ

advanced search とは、高度検索と訳せます。複数の検索パラメータを組み合わせることによって、指定された範囲でより正確な結果を得ることのできる機能です。

Googleなどが有名ですが、GitHubにも存在することを教えてもらいました。

Google 検索オプション

メソッドの使い方、記法など、希望する条件を入力し検索することができます。

GitHub · Where software is built

コントリビュートチャンス

チェリー本を読み進めていたとき、ふと英単語の誤植が目に入ってきました。

139頁の真ん中あたりのサンプルコード内で、

本来numbersの綴りが正しいはずですが、nunmbersとなっていました。

nunmbersのループは脱出するが、fruitsのループは継続する

コントリビュートというと大げさではありますが、人生初めての誤植発見というのもあり、嬉しくてテンション上がりました。笑

その後、すぐにフィヨルドブートキャンプ内のバグ・誤字脱字の窓口へ報告を行いました。

なんと直接、著者の伊藤さんにご対応いただき、無事に技術評論社の正誤表へ反映いただきました。

gihyo.jp

スタンプ

そんなこともあり、輪読会メンバーの(id:yana_g)さんが「コントリビュートチャンス」スタンプを作ってくれました笑。こういった遊び心とても素敵ですね。

絵文字ジェネレーター - Slack 向け絵文字を無料で簡単生成

f:id:shirotamaki:20210722140250p:plain

参考書籍

🍒 まとめ

今週で6週目を終えました。

始めた当初は苦手意識のあった「irb」と「るりま」ですが、日々使用することで親しみが増し、今ではだいぶ仲良くなれたと思います。わからないことに出くわしたときは、すぐに「るりま」を参照する習慣が身につきました。また「irb」を使用し手元で動きを確認することも、今では自然と手がスッと動くようになり、身についてきたと思います。

輪読会を通して、この大きな2つの武器を手に入れられたことは大きな収穫です。

引き続き、この武器を活用し技術を磨いていきたいと思います。

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