休日に新しい言語に触れたい ~Ruby編 特徴的なコードを実装してみる~

# はじめに

https://qiita.com/MichaelHashimoto/items/7b9004668421ae2cbb59

こちらの記事の続きです。

私はC#やPythonを仕事で使っているため、これらの知識をベースにして理解を広げます。同じような境遇の方の理解の助けになれば幸いです。


# 特徴的なコードを実装してみる

## ブロック

rubyに特徴的なものとして、"ブロック"があります。

まずは、以下のサンプルコードを見てください。
```ruby
numbers = [1, 2, 3, 4, 5]
numbers.each do |number|
  puts number * 2  # putsで出力
end
```

出力結果は以下で、numbersの各要素が2倍されたものが出力されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1223968/ece19765-005c-5247-93ec-9de3f3b24902.png)

このように```do |引数| ~end```で囲まれたコードの部分がブロックです。今回の例では、numbersの各要素がこのブロックの引数numberに渡されています。

```do~end```の部分は、一行で```{~}```として書くこともできます
```ruby
numbers = [1, 2, 3, 4, 5]
numbers.each { |number| puts number * 2 }
# 上の結果と同じく、2,4,6,8,10が出力される
```

### ブロックを使ったメソッドを作り、理解を深める

まず、testというメソッドを作成しました。与えられた数字を3倍して出力するだけのメソッドです。

```ruby
def test(number)
  puts number * 3
end

test(2)
```

testに引数2を渡したため、6が出力されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1223968/3bce078a-0ee9-2aae-0ff8-52b61ff49375.png)

では、次にtestメソッドに変更を加えます。
```ruby
def test(number)
  puts yield number if block_given? # 追加した行
  puts number * 3
end

test(2)
```

実行結果は"6"です。変更を加える前と出力結果は変わっていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1223968/3bce078a-0ee9-2aae-0ff8-52b61ff49375.png)

さて、次にtestメソッドを呼び出すコードにブロックを加えます。

```ruby
def test(number)
  puts yield number if block_given?
  puts number * 3
end

test(2) do |number| # ブロックを追加した。
  number * 2 # numberを2倍する
end
```

実行結果は、"4 6"となりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1223968/f5a19033-c28e-3cd3-3cd1-a16d1ee42051.png)

4が出力された原因は、メソッドに加えた```puts yield number if block_given?```の部分にあります。
```if block_given``` : このメソッドにブロックが与えられたかどうかを判定する
```yield number``` : 与えらえたブロックに、引数numberを渡して実行する。

今回は、numberを2倍にするブロックを渡していたため、"4"が出力されるようになっています。

### ブロックのメリット

ブロックを使うことで、メソッドを使う側が独自の処理をメソッド内に入れ込むような設計が可能になります。上の例だったら、"2倍する"という独自の処理をtestメソッド内に入れ込んでいます。

### Pythonの似たコードから理解を深める

Pythonだったら、lambdaで作った独自の関数で配列を並び替えることがあると思います。
```python
# python
people = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
    {"name": "Charlie", "age": 35}
]

# ageキーでソートする
sorted_people = sorted(people, key=lambda person: person["age"])
print(sorted_people)
# => [{'name': 'Bob', 'age': 25}, {'name': 'Alice', 'age': 30}, {'name': 'Charlie', 'age': 35}]
```

rubyだったら、ブロックで同様のことができるようになります。
```ruby
# ruby
people = [
  { name: "Alice", age: 30 },
  { name: "Bob", age: 25 },
  { name: "Charlie", age: 35 }
]

# 年齢でソートする
sorted_people = people.sort_by { |person| person[:age] }
puts sorted_people.inspect
# => [{:name=>"Bob", :age=>25}, {:name=>"Alice", :age=>30}, {:name=>"Charlie", :age=>35}]
```

## ミックスイン

rubyでは、クラスにモジュールのメソッドを取り込む"ミックスイン"という機能があります。
といっても分かりづらいので、以下の例をみてください。

```ruby
class Person
  def initialize(name)
    @name = name
  end
end

taro = Person.new('Taro')
```
Personというクラスを作成し、taroというインスタンスを生成したコードです。
Personクラスは、コンストラクタでnameを引数にとり、自身のインスタンス変数(C#でいうところのプロパティに相当)としてnameを保持しています。

この時点では、Personはnameというデータを持っているだけで、メソッドは定義していません。

次に、Greetableというモジュールを作成し、greetというメソッドを定義します。
そして、このモジュールをPersonクラスでincludeします。
```ruby
module Greetable
  def greet
    puts "Hello! I\'m #{@name}"
  end
end

class Person
  include Greetable # ミックスイン  

  def initialize(name)
    @name = name
  end
end
```

そうすると、Personクラスのインスタンスで"greet"メソッドが実行できるようになります。
```ruby
taro = Person.new('Taro')
taro.greet
```

出力結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1223968/b5c33fb8-a93c-c4cb-66e9-648173e81874.png)

これがミックスインです。

### 使いどころ

rubyでは、クラスの多重継承ができません。そこでミックスインを使います。

以下の例では、Personの派生クラスであるParent, Childを作成し、ParentのみにGreetableをミックスインしています。
```ruby
module Greetable
  def greet
    puts "Hello! I'm #{@name}"
  end
end

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

# ParentクラスはPersonクラスの派生で、Greetableをミックスイン。挨拶できる。
class Parent < Person
  include Greetable
end

# ChildクラスはPersonクラスの派生で、Greetableをミックスインしない。挨拶できない。
class Child < Person
end

parent = Parent.new("Taro")
parent.greet # => Hello! I'm Taro.

child = Child.new("Kenji")
# child.greet # この行を実行するとエラー
```

## オープンクラス

rubyでは、既存のクラスに新たなメソッドを追加/上書きすることができます。これを"オープンクラス"と呼びます。

以下の例では、文字列のStringクラスに対し、repeatというメソッドを追加しています。
```ruby
class String
  def repeat
    self + self # 文字列を2回繰り返して返す
  end
end

puts 'Hello'.repeat
```

出力結果です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1223968/a11f0ee4-a149-96af-82b6-6752136b4e0f.png)

## DSLの実装

rubyでは、DSL(domain-specific language, ドメイン固有言語)を実装しやすいことが知られています。動的な型・メソッドの定義ができることや、メタプログラミング(動的なコードの操作)ができることが理由だそうです。

例として、ここでは人物の設定ファイルのようなDSLを作ります。

```ruby
class Person
  def initialize(name)
    @name = name
    @age = nil
    @hometown = nil
  end

  def age(val)
    @age = val
  end

  def hometown(val)
    @hometown = val
  end

  def show_info
    "#{@name} => age:#{@age}, hometown:#{@hometown}"
  end
end

class PersonManager
  def initialize(&block) # blockを受け取る
    @persons = []
    instance_eval(&block) # blockをPersonManagerクラス内で実行する
  end

  def person(name, &block)
    person = Person.new(name)
    person.instance_eval(&block) # blockをPersonクラス内で実行する
    @persons << person # personsリストに追加
  end

  def puts_persons_info
    @persons.each { |person| puts person.show_info }
  end
end
```

上記のPersonManagerを使用することで、以下のようなDSLが記述できるようになります。見た目は、XMLやJSONのようなただの設定ファイルですね。
```ruby
PersonManager.new do
  person 'Taro' do
    age 20
    hometown 'Tokyo'
  end

  person 'Hanako' do
    age 30
    hometown 'Kanagawa'
  end

  person 'Takeru' do
    age 35
    hometown 'Hokkaido'
  end

  puts_persons_info
end
```


実際には、それぞれのブロック内でいろいろなメソッドが実行されます。
```ruby
PersonManager.new do 
# このdo~endのブロック内の内容は、instance_evalとして評価される。
# つまり、PersonManager内のメソッドが直書きできる。
  # PersonMangerのpersonメソッドに引数'Taro'を渡して実行
  person 'Taro' do  
  # このdo~endのブロック内の内容は、person.instance_evalとして評価される。
    # personクラスのage,hometownメソッドを実行
    age 20
    hometown 'Tokyo'
  end
end
```

出力結果です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1223968/ed84a817-d068-bbe1-5e85-0b687595e839.png)


# 感想

初めてDSLを作りましたが、確かに非プログラマでも記述できる設定ファイルを作れそうです。
設定ファイルを記述してもらう=>Rubyで処理してより複雑な設定ファイルを生成する=>他の言語がファイルを読み込みプログラムを実行、のような形で使える気がしました。

# 参考

https://qiita.com/getty104/items/b3fcc1f86846fb86f168