Open12

Ruby 個人的メモ

ykttdnykttdn

=begin=end を使ったコメントの書き方

Ruby では =begin=end を使ってコメントを書くことができる。ただし RuboCop では非推奨とされている。

https://docs.rubocop.org/rubocop/cops_style.html#styleblockcomments

# bad
=begin
Multiple lines
of comments...
=end

# good
# Multiple lines
# of comments...

この書き方だと、=begin=end の前に空白を入れることができない。
また、このような形式は埋め込みドキュメントというらしい。

https://docs.ruby-lang.org/ja/latest/doc/spec=2flexical.html#embed

ykttdnykttdn

整数と小数の計算

  • Integer同士の計算では結果はIntegerになる
  • Float同士の計算では結果はFloatになる
  • IntegerFloatの計算では結果はFloatになる

というのは周知の事実かと思う。このあたりがRubyのリファレンスにどのように書かれているか気になったので調べてみたところ、

  • Floatに対する計算の結果はFloat
  • Integerに対する計算の結果はNumeric

となっていた。
https://docs.ruby-lang.org/ja/latest/class/Float.html#I_--2A

self * other -> Float

https://docs.ruby-lang.org/ja/latest/class/Integer.html#I_--2A

self * other -> Numeric

ykttdnykttdn

FloatをRationalに変換する

0.1r
# => (1/10)
0.1.rationalize
# => (1/10)
0.1.to_r
# => (3602879701896397/36028797018963968) 分母は2^55

0.9999999999999999r
# => (9999999999999999/10000000000000000)
0.9999999999999999.rationalize
# => (6004799503160661/6004799503160662)
0.9999999999999999.to_r
# => (9007199254740991/9007199254740992) 分母は2^53

3つ全てが同じRationalを返すわけではない。
https://docs.ruby-lang.org/ja/latest/method/Float/i/rationalize.html
https://docs.ruby-lang.org/ja/latest/method/Float/i/to_r.html
Float#to_rはコンピュータが内部的にもっている2進数を有理数に変換するっぽい。
https://qiita.com/Hiron0120/items/69672227a7400a06e577

ykttdnykttdn

Rubyでfalsyなもの

基本はnilfalseのみがfalsy

[-1, 0, 1, '', ' ', [], {}, true, false, nil].each { |val| p val unless val }
# false
# nil

empty?を使うと空文字列や空配列などをfalsy判定できる

['', ' ', [], {}].each { |val| p val if val.empty? }
# ""
# []
# {}

empty?メソッドを使えるのは他にもありそう
https://www.google.com/search?q="empty%3F"+site%3Aruby-lang.org

blank?を使うとfalsenilemptyなオブジェクト、空白のみで構成される文字列をfalsy判定できる

require 'active_support'
require 'active_support/core_ext'
[-1, 0, 1, '', ' ', [], {}, true, false, nil].each { |val| p val if val.blank? }
# ""
# " "
# []
# {}
# false
# nil

https://railsguides.jp/active_support_core_extensions.html#blank-questionmarkとpresent-questionmark

ykttdnykttdn

%記法でリテラルを作る

バックスラッシュ記法と式展開が有効なものとそうでないものがある。開き括弧と閉じ括弧の部分には空白、改行を含む任意の非英数字を使うことができる。ただし括弧を使うときは対応する開き括弧と閉じ括弧で囲む必要がある。

var = 'a b'

# 文字列
%(#{var}\sc) # => "a b c"
%q(#{var}\sc) # => "\#{var}\\sc"
%Q(#{var}\sc) # => "a b c"

# コマンド出力
%x(echo #{var}\sc) # => "a b c\n"

# 正規表現
%r{#{var}\sc} # => /a b\sc/

# 文字列の配列
%w[#{var} c\s \ d] # => ["\#{var}", "c\\s", " d"]
%W[#{var} c\s \ d] # => ["a b", "c ", " d"]

# シンボル
%s(#{var}\sc) # => :"\#{var}\\sc"

# シンボルの配列
%i[#{var} c\s \ d e] # => [:"\#{var}", :"c\\s", :" d", :e]
%I[#{var} c\s \ d e] # => [:"a b", :"c ", :" d", :e]

https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#percent

ykttdnykttdn

ヒアドキュメント (行指向文字列リテラル)

def print_text
  text = <<TEXT
    abc
      def
TEXT

  p text
end

print_text
# => "    abc\n      def\n"

# 終端行のインデントが無視される
def print_text
  text = <<-TEXT
    abc
      def
  TEXT

  p text
end

print_text
# => "    abc\n      def\n"

# 最もインデントが少ない行を基準にして、全ての行の先頭から空白を取り除く
def print_text
  text = <<~TEXT
    abc
      def
  TEXT

  p text
end

print_text
# => "abc\n  def\n"

TEXTの部分(識別子)は自由に付けられる。<<識別子は1つの式(値を返すもの)であり、以下のような書き方ができる。

<<TEXT.upcase
hoge
TEXT
# => "HOGE\n"

'foo'.concat(<<TEXT1, <<TEXT2)
bar
TEXT1
baz
TEXT2
# => "foobar\nbaz\n"

ヒアドキュメント内では式展開、バックスラッシュ記法が有効である。識別子を"'`で囲むと対応する文字列リテラルと同じ扱いになる。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#here

ykttdnykttdn

case文における比較

上から順番に、when の直後の式を評価した結果をレシーバ、 case の直後の式を評価した値を引数として === 演算子を呼び出し、最初に真を返した when 節の本体を実行します。

https://docs.ruby-lang.org/ja/latest/doc/spec=2fcontrol.html#case
=== 演算子は、ほとんどのクラスに対しては == 演算子と同じ意味をもつが、Range クラス、RegExp クラス、Module クラスに対しては独自に定義されている。
https://docs.ruby-lang.org/ja/latest/method/Range/i/=3d=3d=3d.html
https://docs.ruby-lang.org/ja/latest/method/Regexp/i/=3d=3d=3d.html
https://docs.ruby-lang.org/ja/latest/method/Module/i/=3d=3d=3d.html
上記3つの場合、self === otherは、「selfの表す集合がotherを含んでいるか」というイメージ。

ykttdnykttdn

Module#===は次のようなときに使える。

String === 'str'
# => true

左辺のStringはClassクラスのインスタンスで、===はClassクラスが継承しているModuleクラスのインスタンスメソッドである。

ykttdnykttdn

インスタンス変数

インスタンス内で共有される変数。

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

  def greet
    "Hello, my name is #{@name}"
  end
end

user = User.new('Alice')
user.greet
# => "Hello, my name is Alice"

存在しないインスタンス変数を参照してもエラーにならず、nilとして扱われる。

class User
  def initialize(name)
    # @name = name
  end

  def greet
    "Hello, my name is #{@name}"
  end
end

user = User.new('Alice')
user.greet
# => "Hello, my name is "

インスタンス変数をクラスの外部からは参照することはできない。

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

user = User.new('Alice')
user.name
# => undefined method `name' for an instance of User (NoMethodError)

参照するには読み取りメソッドが必要になる。

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

  def name
    @name
  end
end

user = User.new('Alice')
user.name
# => "Alice"

attr_readerを使うと読み取りメソッドの定義を省略できる。

class User
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

user = User.new('Alice')
user.name
# => "Alice"

また、インスタンス変数を外部から変更することはできない。

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

user = User.new('Alice')
user.name = 'Bob'
# => undefined method `name=' for an instance of User (NoMethodError)

変更するには書き込みメソッドが必要になる。

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

  def name=(value)
    @name = value
  end
end

user = User.new('Alice')
user.name = 'Bob'
# => "Bob"

attr_writerを使うと書き込みメソッドの定義を省略できる。

class User
  attr_writer :name

  def initialize(name)
    @name = name
  end
end

user = User.new('Alice')
user.name = 'Bob'
# => "Bob"

attr_accessorを使うと読み取りメソッドと書き込みメソッドの両方が定義される。

class User
  attr_accessor :name

  def initialize(name)
    @name = name
  end
end

user = User.new('Alice')
user.name = 'Bob'
user.name
# => "Bob"
ykttdnykttdn

クラスメソッドの定義の仕方

Rubyにおけるクラスメソッドとはクラスの特異メソッドである。

そもそも特異メソッドとはある特定のオブジェクトに固有のメソッドであり、次のように定義することができる。

class Person; end

alice = Person.new
def alice.hello
  'hello'
end
alice.hello
# => "hello"

bob = Person.new
bob.hello
# => undefined method `hello' for an instance of Person (NoMethodError)

特異メソッドを定義すると、そのメソッドを定義したオブジェクトのみが属する、特異クラスと呼ばれるクラスが内部的に作成され、そのクラスにメソッドが追加される。次のように特異クラスを作成することで、特異メソッドを定義することもできる。

class << alice
  def hello
    'hello'
  end
end
alice.hello
# => "hello"

最初に述べた通り、Rubyにおけるクラスメソッドとは、クラスというオブジェクトに対して定義された特異メソッドである。したがってクラスメソッドは次のように定義できる。

class Person; end

# 特異メソッド方式
def Person.create_people(num)
  Array.new(num) { Person.new }
end
Person.create_people(3)
# => [#<Person:0x0000ffff9889ec00>, #<Person:0x0000ffff9889ebd8>, #<Person:0x0000ffff9889ebb0>]

# 特異クラス方式
class << Person
  def create_people(num)
    Array.new(num) { Person.new }
  end
end
Person.create_people(3)
# => [#<Person:0x0000ffff7328a3e8>, #<Person:0x0000ffff7328a348>, #<Person:0x0000ffff7328a2d0>]

クラスの定義の中でselfがそのクラス自身を表すことを用いると、上のコードは次のようにも書ける。

# 特異メソッド方式
class Person
  def self.create_people(num)
    Array.new(num) { Person.new }
  end
end

# 特異クラス方式
class Person
  class << self
    def create_people(num)
      Array.new(num) { Person.new }
    end
  end
end

特異クラス方式の書き方はクラスメソッドを複数定義するときに便利である。

https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html
https://toshiocp.com/entry/2022/10/03/181427
https://qiita.com/k-penguin-sato/items/d637dced7af32e4ec7c0