Skip to content

Instantly share code, notes, and snippets.

@kyohei-shimada
Last active February 27, 2020 06:27
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kyohei-shimada/9aa61358abdc10e38bfa to your computer and use it in GitHub Desktop.
Save kyohei-shimada/9aa61358abdc10e38bfa to your computer and use it in GitHub Desktop.
RubyのちょっとしたTips

RubyのちょとしたTips

ちょっと便利なことだったり,今さら知った恥ずかしいことなど

ローカル変数のスコープ

小文字または`_'で始まる識別子はローカル変数また はメソッド呼び出しです。ローカル変数スコープ(クラス、モジュー ル、メソッド定義の本体)における小文字で始まる識別子への最初 の代入はそのスコープに属するローカル変数の宣言になります。宣 言されていない識別子の参照は引数の無いメソッド呼び出しとみな されます。 ローカル変数のスコープは、((宣言した位置から))その変数が宣 言されたブロック、メソッド定義、またはクラス/モジュール定義 の終りまでです。寿命もそのブロックの終りまで(トップレベルの ローカル変数はプログラムの終了まで)ですが、例外としてブロッ クが手続きオブジェクト化された場合は、そのオブジェクトが消滅 するまで存在します。同じスコープを参照する手続きオブジェクト 間ではローカル変数は共有されます。

  • 「ローカル変数のスコープは,宣言した位置からその変数が宣言された ブロック,メソッド定義,またはクラス/モジュール定義の終わりまで」なので,if ~ elseとかbegin ~ rescueはローカル変数のスコープを作らない

  • なのでいちいち,beginやifの中で使う変数をその処理の前で初期化する必要はない

# はじめにfを定義しておく必要はない
#f = nil

begin 
  f = File.open("/path/to/file")
rescue => e
  puts "ファイルを開けません"
  raise
end

puts f.read

mapで全要素に,その要素のメソッドを作用させる時

ユーザ一覧からそれぞれのメールアドレスを取るとき

users.map {|user| user.email }

とせずに,

users.map(&:email)

とかけますよというお話.

なぜか?

http://docs.ruby-lang.org/ja/2.1.0/doc/spec=2fcall.html#block

ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。 それを実現するのが手続きオブジェクト(Proc)です。 それをブロックとして渡すにはブロック付きメソッドの最後の引数として `&' で修飾した手続きオブジェクトを渡 します。Proc の代わりにメソッドオブジェクト(Method)を渡す ことも出来ます。この場合、そのメソッドを呼ぶ手続きオブジェクトが生成さ れ渡されます。

というわけで,mapなどでProcを渡すときはProcを渡すメソッドは以下のような形で渡せる

proc = Proc.new {|user| user.email }
emails = users.map(&proc) 

さらには同URLにて

to_proc メソッドを持つオブジェクトならば、`&' 修飾した引数として渡すことができます。

とあるのでto_procメソッドを持っているオブジェクトを&で渡せば,暗黙的に&で渡したインスタンスをto_procしてproc化される.実はSymbolオブジェクトにはto_procが定義されており,シンボル名と同名のメソッドを呼び出すProcオブジェクトを生成する. つまり下記1,2,3,4は結果としては同じものとなる(実装の詳細は違うかもしれないが)

# 1. 一番省略した形
users.map(&:email) 

# 2 to_procをあえて明示的に書くとこんな感じ
users.map (&(:email.to_proc)) 

# 3 さらにprocを明示的に作ったらこんな感じ(emailメソッドは引数0なのでargsは実際は関係なし)
# proc = Proc.new {|x, *args| x.email(*args) }   
proc = Proc.new {|x| x.email }
users.map (&proc)

# 4 Procをnewせずにブロックで直接指定した場合
users.map {|x| x.email }

each_with_index?

基本的にrubyでの繰り返し操作はmapやeachなどのイテレータを用いるが,純粋に各要素を順番に取り出すとともに1,2,3,4などと数字を取り出したいときもある. そんな場合each_with_indexを使う

%w(a b c d e).each_with_index do |ch, index|
  puts "#{index + 1}, #{ch}"
end
#出力
#1, a
#2, b
#3, c
#4, d
#5. e

でも0オリジンになってしまっているので,eachとは別にindexの底上げに気を使わなければいけない.そんなときはeach.with_indexがいい感じ

%w(a b c d e).each.with_index(1) do |ch, index|
  puts "#{index}, #{ch}"
end
#出力
#1, a
#2, b
#3, c
#4, d
#5. e

まず,eachでEnumeratorオブジェクトが得られる.Enumeratorオブジェクトはwith_indexメソッドを持っており,指定した数値を起点とするインデックスをもとのEnumeratorに追加する.詳細はリファレンスを参照

beginの省略

メソッド全体を例外で補足する場合begin ~ rescue ~ endのbeginを省略できる 次の2つは同じ

class Hoge
  def fuga
    begin
      # なんか処理
    rescue => e
      $stderr.puts e
      raise
    end
  end
end
class Hoge
  def fuga
    # なんか処理
  rescue => e
    $stderr.puts e
    raise
  end
end

Rangオブジェクトリテラル..と...の違い

..は終端を含む...は終端を含まない

(1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(1...10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]

循環を簡単につくる

Enumerableなオブジェクトに対しcycleを使うとよいcycleに引数を渡さないと無限列になるので注意

(1..5).cycle(3) # => #<Enumerator: ...>
(1..5).cycle(3).to_a
=> [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

Range#include? Range#cover?

  • include? は内部的には===による比較, cover? は <=> による比較

http://docs.ruby-lang.org/ja/2.2.0/method/Range/i/cover=3f.html

デバッグの役にたつかもしれないこと

クラスに対する色々

# Arrayで継承+(include)されているクラス一覧
Array.ancestors #=> [Array, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]
# Arrayにincludeされているクラス一覧
Array.included_modules #=> [Enumerable, PP::ObjectMixin, Kernel]

オブジェクト

class Point
  WIDTH_MAX = 640
  HEIGHT_MAX = 320
  @@class_variable1 = "とくに良い物がなかったので適当"
  @@class_variable2 = 111111

  attr_accessor :x, :y

  def initialize(x, y)
    @x = x
    @y = y
  end
  
  def abs
    raise "out of range" if out_of_range?
    Math.sqrt(@x ** 2 + @y ** 2)
  end
  
  def to_s
    raise "out of range" if out_of_range?
    "x = #{@x}, y = #{@y}"
  end

  def self.max_abs
    Math.sqrt(WIDTH_MAX ** 2 + HEIGHT_MAX ** 2)
  end

  def +(other)
    Point.new(@x + other.x, @y + other.y)
  end
  
  private
  
  def out_of_range?
    @x > WIDTH_MAX || @y > HEIGHT_MAX
  end
end

p = Point.new(100, 200)
# オブジェクトの変数
p.instance_variables #=> [:@x, :@y]
#p.instance_values #=> {"x"=>100, "y"=>200} Powerd by 'active_support'
# オブジェクトのmethod
p.public_methods(false) #=> [:x, :x=, :y, :y=, :abs, :to_s, :+]
p.protected_methods(false) #=> []
p.private_methods(false) #=> [:initialize, :out_of_range?]

# クラスの取得
p.class #=> #<Point:0x007fde4d13d890 @x=100, @y=200>
Point.class_variables #=> [:@@class_variable1, :@@class_variable2]
Point.public_methods(false) #=> [:max_abs, :allocate, :new, :superclass]
Point.ancestors #=> [Point, Object, PP::ObjectMixin, Kernel, BasicObject]

# methodいろいろ
p.method(:abs) #=> #<Method: Point#abs>
method = p.method(:+) #=> #<Method: Point#+>
method.receiver #=> #<Point:0x007fde4d13d890 @x=100, @y=200> (もとのオブジェクト)
method.parameters #=> [[:req, :other]] (see also: http://docs.ruby-lang.org/ja/2.2.0/method/Method/i/parameters.html)
method.source_location # ["path/to/source_file", line_number] が得られる
# pryだとcd, lsとかでcliライクにmethodやクラスを探せる
pry(main)> cd Point
pry(Point):1> ls
constants: HEIGHT_MAX  WIDTH_MAX
Point.methods: max_abs
Point#methods: +  abs  to_s  x  x=  y  y=
class variables: @@class_variable1  @@class_variable2
locals: _  __  _dir_  _ex_  _file_  _in_  _out_  _pry_
pry(Point):1> cd # クラスツリーのルートに戻る
# ソース見れる
pry(Point):1> show-method abs

From: [ファイルのパスが入る] @ line [行数が入る]:
Owner: Point
Visibility: public
Number of lines: 4

def abs
  raise "out of range" if out_of_range?
  Math.sqrt(@x ** 2 + @y ** 2)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment