見出し画像

Rubyと仲良くなるための社内勉強会

ごきげんよう🌞ツクリンクでエンジニアをしているあっきー(@kuronekopunk)です。

週1回エンジニアが集まり、気になる技術やテーマを持ち寄ってやっているDev実験室という取り組みの中で、『RubyのEnumerableのメソッドを再実装してみる』という内容の勉強会をやったので紹介します。
Dev実験室の詳細は以下記事を読んでください。

RubyのEnumerableとは?

割と馴染み深い all? any? filter include? などのメソッドを定義しているモジュール
以下はドキュメントからの引用

繰り返しを行なうクラスのための Mix-in。このモジュールのメソッドは全て each を用いて定義されているので、インクルードするクラスには each が定義されていなければなりません。
Array, Hash, Range, Enumerator等のクラスで、 Enumerableモジュールはインクルードされています。ただし、効率化のため、そのクラスでEnumerableと同名・同等の機能を再定義(オーバーライド)しているケースも少なくなく、特にArrayクラスでは同名のメソッドを再定義していることが多いです。


メソッド再定義のやり方

例えば Array#all? を再実装する場合の雛形はこれ。
ドキュメントにもある通り、eachが定義されている前提なのでメソッド内でeachが使えます。

class Array
 def all?(*args)
   puts 'ここに再実装を書く'
 end
end

ドキュメントの仕様に合った簡単なテストコードを用意して自分の再実装が合っているか確認をします。

# テストコード
require 'minitest/autorun'
require_relative 'all'

class AllTest < MiniTest::Test
 # all? -> bool
 def test_all
   assert_equal true, [true, true].all?
   assert_equal false, [true, false].all?
 end

 # all? {|item| ... } -> bool
 def test_block_given
   assert_equal true, [5, 6, 7].all?(&:positive?)
   assert_equal false, [5, -1, 7].all?(&:positive?)
   assert_equal true, [].all?(&:positive?)
 end

 # all?(pattern) -> bool
 def test_pattern
   assert_equal false, %w[ant bear cat].all?(/t/)
   assert_equal true, %w[peach bear eat].all?(/ea/)
   assert_equal true, [1, 50, 99].any?(Integer)
 end
end

※記事の最後にGitHubに公開しているサンプル問題へのリンクがあります


実際に再実装をやってみた

8月から1名エンジニアが参画したので気になるメソッドを選んでもらい実際にEnumerableのメソッド再定義をやってみました。

選ばれたのは #chunk 

chunk {|elt| ... } -> Enumerator[permalink][rdoc][edit]
要素を前から順にブロックで評価し、その結果によって要素をチャンクに分けた(グループ化した)要素を持つ Enumerator を返します。
ブロックの評価値が同じ値が続くものを一つのチャンクとして取り扱います。すなわち、ブロックの評価値が一つ前と異なる所でチャンクが区切られます。
返り値の Enumerator は各チャンクのブロック評価値と各チャンクの要素を持つ配列のペアを各要素とします。
https://docs.ruby-lang.org/ja/latest/class/Enumerable.html#I_CHUNK

「これ何に使えるんだ…?」と思う謎メソッドに出会えるのも楽しみの一つです。
再実装してみてchunkを選んだエンジニアは
「なんでこんなメソッド選んじゃったんだろう…」
と嘆いていました😇

僕の回答はこちら

module Enumerable
 def chunk
   Enumerator.new { |y|
     result = []
     prev_key = nil

     each do |n|
       key = yield n

       # ブロックの評価値が nil もしくは :_separator であった場合、 その要素を捨てます。チャンクはこの前後で区切られます。
       if key.nil? || key == :_separator
         y << [prev_key, result] if result.size > 0
         result = []
         next
       end

       # ブロックの評価値 :_alone であった場合はその要素は 単独のチャンクをなすものと解釈されます。
       if prev_key != key || key == :_alone
         y << [prev_key, result] if result.size > 0
         result = []
       end

       result << n

       prev_key = key
     end

     y << [prev_key, result] if result.size > 0
   }
 end
end


Enumerableのメソッドを再実装は良いぞ

知らなかったメソッドを知れる
Enumerableのドキュメントを流し読みするだけでも知らなかったメソッドを知るいい機会になりました。

普段使っているメソッドの挙動を理解できる
「こんな引数を受け取れたんだ!?」といった発見がありました。

人の実装を見て勉強になる
各個人で再実装していくため人数分の実装が集まります。
想像しなかった記法やまとめ方など同じ課題だからこそ気付けるポイントも多くあります。

Rubyが好きになる
詳しく知ることで更に好きになれます💕
(謎なメソッドを選ぶと嫌いになる可能性もありますが…)


いくつかの問題をGitHubで公開しているので良かったら遊んでください。


さいごに

ツクリンク株式会社では一緒にサービスを良くしていく仲間を募集しています!まずはカジュアルにお話しましょう🙌



読んでくれてありがとうございます!少しでもいいなと思ったら「スキ❤️」してもらえると飛んで喜びます!シェアしてもらったらもっと嬉しいです!