気軽に楽しくプログラムと遊ぶ

自分が興味があってためになるかもって思う情報を提供しています。

Ruby Goldで問われる押さえておきたい仕様3

定数のスコープ

# トップレベル定数
VAR = "main"

module MyModule
  puts VAR + " main1" # => main main1
  puts ::VAR + " main2" # => main main2
  # モジュール内で定数定義
  VAR = "MyModule"
  puts VAR + " module1" # => MyModule module1 自スコープの定数が優先
  puts ::VAR + " main3" # => main main3トップレベル定数は上書きされていない
end

class MyClass
  puts VAR + " main1@MyClass" # => main main1@MyClass

  # クラス内定数
  VAR = "MyClass"
  puts VAR + " MyClass1@MyClass" # => MyClass MyClass1@MyClass
  
  def my_method
    puts VAR + " MyClass3@my_method"
    puts MyClass::VAR + " MyClass4@my_method"
    # メソッド内定数
    # VAR = "MyMethod" # => メソッド内で定義できない。
  end
  
  include MyModule
  puts VAR + " MyClass2@MyClass"  # => MyClass MyClass2@MyClass 自スコープの定数が優先
  puts MyModule::VAR + " MyModule1@MyClass" # => MyModule MyModule1@MyClass
end

# メソッド内から定数を参照
mc = MyClass.new
mc.my_method
# => MyClass MyClass3@my_method
# => MyClass MyClass4@my_method

class MyChildClass < MyClass
  puts VAR + " MyClass1" # => MyClass MyClass1
  # クラス(子)内の定数
  VAR = "MyChildClass"
  puts VAR + " MyChildClass1" # => MyChildClass MyChildClass1
  puts MyClass::VAR + " MyClass2" # => MyClass MyClass2
  puts MyModule::VAR + " MyModule1" # => MyModule MyModule1
end

再定義可能な演算子を再定義してみる

Team クラスを作成して
+ : メンバーの追加
- : メンバーの削除
<< : チームまるごとメンバーに追加
というクラスを作成してみます。

class Team
  attr_reader :members

  def initialize(members = [])
    @members = members
  end

  # メンバーの追加
  def +(member)
    @members << member
  end

  # メンバーの削除
  def -(member)
    @members.delete(member)
  end

  # チームの追加
  def <<(team)
    # teamにmembersメソッドが存在するかを確認
    fail 'error' unless team.respond_to?(:members)
    @members += team.members
  end
end

team_a = Team.new(%w(tanaka sato suzuki))
print team_a.members, "\n"
# => ["tanaka", "sato", "suzuki"]

team_a + 'obokata'
print team_a.members, "\n"
# => ["tanaka", "sato", "suzuki", "obokata"]

team_a - 'obokata'
print team_a.members, "\n"
# => ["tanaka", "sato", "suzuki"]

team_b = Team.new(%w(matz larry guido))
team_a << team_b
print team_a.members, "\n"
# => ["tanaka", "sato", "suzuki", "matz", "larry", "guido"]

YAML

YAML形式のファイルの書き方

配列

array.yaml

- aaa
-
  - b1
  -
    - b3.1
- ccc

ハッシュ

hash.yaml

A: aaa
B:
  B1: b1
  B2: b2
C: c2

YAMLファイルの読み込み

require 'yaml'

p YAML.load_file('./array.yaml')
#=> ["aaa", ["b1", ["b2.1", "b2.2"]], "ccc"]

p YAML.load_file('./hash.yaml')
#=>  {"A"=>"aaa", "B"=>{"B1"=>"b1", "B2"=>"b2"}, "C"=>"c2"}

ブロックにおけるreturn,break,next

ブロック内での動作

  • return: ブロック、メソッドを抜ける
  • break: ループを抜ける
  • next: 次のループを移る

動作確認

def block_return
  puts '----return-----'
  10.times do |i|
    return if i == 3
    puts i
  end
  puts 'end'
end

def block_break
  puts '----break-----'
  10.times do |i|
    break if i == 3
    puts i
  end
  puts 'end'
end

def block_next
  puts '----next-----'
  10.times do |i|
    next if i == 3
    puts i
  end
  puts 'end'
end

block_return
block_break
block_next

#----return-----
#0
#1
#2
#----break-----
#0
#1
#2
#end
#----next-----
#0
#1
#2
#4
#5
#6
#7
#8
#9
#end

Proc内部でのreturn,break,nextの動作は以下に記載
Procクラスをirbで確かめる - 気軽に楽しくプログラムと遊ぶ

Case式

  • 判定値の型は文字列以外にも数値、正規表現、Rangeなどで評価可能
  • 値の評価演算子は「===」文字列・数値は「==」、正規表現は「~=」、Rangeはinclude?を用いる
# 正規表現の場合
case name
when /Andy|Bob/
  p name
else
  p "who are you?"
end

# 範囲
case age
when 0..12
 p 'You are a child.'
when 13..19
 p 'You are a teen-ager.'
else
 p 'You are an adult.'
end

トップレベ

概要

  • selfはObjectオブジェクト(irbではmainオブジェクト)
  • main.classはObjectを返却
  • トップレベルで定義したメソッドはObjectクラスのprivateメソッドとして定義される
self #=> main
self.class #=> Object

# すべてのクラスから呼べるレシーバーなしの関数を定義できる
def my_method
  puts 'my_method'
end
my_method #=> my_method

# privateメソッドとして定義されている (・の部分は省略した)
self.private_methods(false) #=> ["public",・・・・・・・・・・ "my_method"]

# Objectはすべてのクラスで継承されているので、my_methodを呼び出せる
class C
  my_method
end
#=> my_method 呼び出せた!

参考URL:Rubyのオブジェクトは生物なんかじゃない、トップレベルこそが生物なんだ!

少数の加算時の誤差

浮動小数点数1/5や1/3は2進数で正確に表せないため、誤差が生じる。
上記の数を2進数で表すには途中で切り捨てて表現するため、その分が誤差になる。

a = 0.1 + 0.2
b = 0.3
p a == b #=> false

BigDecimalクラスを用いて、計算精度を上げると10進数の小数点で計算を行うため、丸め誤差がなくなり、計算精度が上がる。

require 'bigdecimal'
a = BigDecimal("0.1") + BigDecimal("0.2")
b = BigDecimal("0.3")
p a == b  #=> true

計算精度が上がったので==で比較できる誤算になった。
浮動小数点数を用いる限り、丸め誤差、桁落ちの問題はなくならない。

Classクラスからクラスを定義する

String、ArrayなどのクラスはClassクラスのオブジェクトにすぎない。
Classクラスよりクラスを定義する方法を確認する。

class Animal
  def initialize(a)
    @a = a
  end
  
  def method1()
    @a
  end
end

Dog = Class.new(Animal) do # Animalがスーパークラス
  def initialize(a, b)
    @b = b
    super(a)
  end
  
  def method2(c)
    @a + @b + c
  end
end

dog = Dog.new("hello", " world")
dog.method2(" !!") #=> "hello world !!"

変数、メソッドが定義される場所

class Foo
  @@var = 1
  def self.class_method
    @@var
  end
  def instance_method
    @var = 1
  end
end

# インスタンスオブジェクトを参照
foo = Foo.new
foo.instance_method
foo.instance_variables # => [:@var]

# クラスを参照
Foo.class_variables # => [:@@var]
Foo.instance_methods(false) # => [:instance_method]
# Fooクラスにはclass_methodsメソッドが存在しない。クラスメソッドは特異クラスに定義される
Foo.class_methods(false) # => NoMethodError: undefined method `class_methods' for Foo:Class
# 特異クラスのメソッドを参照するにはクラスの特異メソッドを確認
Foo.singleton_methods(false) #=> [:class_method]

クラスメソッドが特異クラスに定義される理由

Classクラスにメソッドを定義すると、すべてのクラスに適用されてしまう。

class Foo
end
class Class
  def c_method1
    puts "c_method1"
  end
end

Foo.c_method1 #=> c_method1

特定クラスに適用するために特異クラスに定義している。
特異クラスへのメソッド定義は特定オブジェクト(特定クラス)への定義となる。

クラスが保持するもの

クラスがメソッドの可視性を保持しているため、サブクラスで可視性の変更が可能。

参考URL

Ruby | 再定義できる演算子 で組み込み演算子風のメソッドを定義する #ruby - Qiita
Ruby - ブロックについてのあれこれ - Qiita
Ruby の case 文 - Qiita