Ruby
本書は、Rubyのチュートリアルです。 Rubyは、比較的習得が容易なオブジェクト指向スクリプティング言語です。 まつもとゆきひろによって開発されました。 Ruby on Railsの記述言語として選ばれたことで有名になりましたが、ウェブアプリケーション以外にもシステム管理やネットワークアプリケーションなどさまざまな用途に用いられます。
Rubyとは
Rubyの特徴
Rubyには、強力で多用途な言語とするための多くの特徴があります。その特徴とは以下の通りです:
- オブジェクト指向のプログラミング
- Rubyは完全なオブジェクト指向言語であり、Rubyのすべてがオブジェクトであることを意味します。このため、複雑なプログラムを簡単に作成することができ、メンテナンスも容易です。
- 動的な型付け
- Rubyは動的型付け言語であり、コンパイル時に変数の型がチェックされることはありません。このため、プログラムを再コンパイルすることなく、変数の型を簡単に変更することができます。
- インタプリタ型言語
- Rubyはインタープリタ型言語であり、機械語にコンパイルされるのではなく、コードが1行ずつ実行されます。そのため、Rubyのプログラムの開発やデバッグが容易に行えます。
- 簡潔な構文
- Rubyには簡潔な構文があり、コードを書くのが簡単です。そのため、初心者から経験豊富なプログラマーまで、幅広く利用することができます。
- 大規模なコミュニティ
- Rubyには、言語やライブラリに貢献する開発者たちの大規模で活発なコミュニティがあります。そのため、Rubyプログラミングのヘルプやサポートを簡単に見つけることができます。
Rubyは強力で汎用性の高い言語であり、さまざまなタスクに適しています。Rubyの学習と使用は簡単で、開発者の大規模で活発なコミュニティがあります。強力で汎用性が高く、習得が容易な言語をお探しなら、Rubyは良い選択です。
初級編
Rubyのインストール方法
Rubyを利用するためには、公式ウェブサイトからインストーラーをダウンロードしてインストールします。
以下は、主要なプラットフォームへのインストール手順の概要です:
- Windows
- RubyInstaller( https://rubyinstaller.org/ ) のページからインストーラーをダウンロードします。
- ダウンロードしたインストーラーを実行し、指示に従ってインストールを進めます。
- macOS
- macOSにはデフォルトでRubyがインストールされていますが、最新バージョンを利用する場合はRVM(Ruby Version Manager https://rvm.io/ ) や rbenvを使用することをお勧めします。
- Linux
- パッケージマネージャーを使用してインストールします。例えば、UbuntuやDebianでは次のコマンドでインストールできます:
sudo apt-get update sudo apt-get install ruby
Hello Worldプログラムの作成
RubyでのHello Worldプログラムは非常に簡単です。以下は、コンソールに"Hello, World!"と出力するプログラムです:
- hello.rb
puts "Hello, World!"
これをhello.rb
という名前で保存し、ターミナルでruby hello.rb
と実行すると、"Hello, World!"が表示されます。
$ ruby hello.rb Hello, World! $
データ型と変数
Rubyでは、様々なデータ型が利用できます。文字列、数値、配列、ハッシュなどがその代表的なものです。動的型付け言語であるRubyでは、変数の型宣言が不要です。以下に、主要なデータ型と変数の宣言と代入について詳細を示します。
- 数値 (Numeric)
- 数値には、整数 (Integer) や浮動小数点数 (Float) が含まれます。整数は例えば
42
のように表され、浮動小数点数は例えば3.14
のように表されます。 - 文字列 (String)
- 文字列は、ダブルクォーテーション (") またはシングルクォーテーション (') で囲まれた文字の並びです。例えば、
"Hello"
や'Ruby'
などが文字列です。 - 配列 (Array)
- 配列は、複数の要素を順序付けして格納するデータ構造です。要素は角かっこ
[ ]
で囲まれ、コンマで区切ります。例えば、[1, 2, 3]
や['a', 'b', 'c']
などが配列です。 - ハッシュ (Hash)
- ハッシュは、キーと値のペアを格納するデータ構造です。波かっこ
{ }
で囲まれ、キーと値はコロン:
で区切ります。例えば、{'name' => 'Alice', 'age' => 30}
などがハッシュです。
変数の宣言と代入
変数は代入演算子 =
を使用して宣言および初期化されます。変数名はアルファベットまたはアンダースコア _
で始まり、その後にはアルファベット、数字、アンダースコアを組み合わせることができます。型宣言は不要であり、変数は動的に型が決まります。
以下は、変数の宣言と代入の例です。
name = "Alice" # 文字列型の変数 age = 30 # 整数型の変数 scores = [85, 90, 75, 100] # 配列型の変数 person = {'name' => 'Bob', 'age' => 25} # ハッシュ型の変数
これらの例では、それぞれの変数に適切なデータ型の値が代入されています。
制御構造
Rubyには様々な制御構造が用意されており、以下のようなものがあります。
- 条件分岐
-
if
/elsif
/else
- 最も基本的な条件分岐構文
unless
/else
if not
と同等の意味case
/when
/else
- 値の一致で分岐
case
/in
/else
- パターンマッチ
- ループ
-
while
- 条件が真の間ループを実行
until
- 条件が偽の間ループを実行(whileの逆)
for
/in
- 反復可能オブジェクトの要素に対してループ
loop
- 無限ループ
break
/next
- ループから抜ける/次の反復へ進む
- 例外処理
-
begin
/rescue
/else
/ensure
- 例外の捕捉と処理
raise
- 例外を発生させる
- その他
-
times
/upto
/downto
- ブロックを繰り返し実行
tap
- 参照渡しでブロックを実行
yield
- ブロックを呼び出す
Rubyの制御構造はPerlやBashなどとよく似ていますが、独自の構文も存在します。 また、Rubyはブロックの概念があり、それを活用した制御構造があるのが特徴です。
- 制御構造の一例
# 条件分岐 name = 'Alice' if name == 'Alice' puts 'こんにちは、Aliceさん' elsif name == 'Bob' puts 'こんにちは、Bobさん' else puts "あなたの名前は#{name}ですね" end # ifは式 puts if name == 'Alice' 'こんにちは、Aliceさん' elsif name == 'Bob' 'こんにちは、Bobさん' else "あなたの名前は#{name}ですね" end # unless file_exists = false puts 'ファイルが存在しません' unless file_exists # case/when age = 25 case age when 0...18 puts '未成年者です' when 18...65 puts '働き盛りの年齢ですね' else puts 'シニア世代かもしれません' end # パターンマッチング puts case [2, 3, 5] in 0, 1, 2 '最初の整数' in 0, 2, 4 '最初の偶数' in 1, 3, 5 '最初の奇数' in 2, 3, 5 => n "最初の素数、三番目は#{n}" else 'どの数列でもない' end # 出力: "最初の素数、三番目は5" # 繰り返し # while i = 0 while i < 5 puts "i = #{i}" i += 1 end # until j = 0 until j > 5 puts "j = #{j}" j += 1 end # for/in array = [10, 20, 30, 40] for item in array puts "要素は#{item}" end # loop count = 1 loop do puts "回数は#{count}" count += 1 break if count > 5 end # 繰り返し制御 # break/next numbers = [1, 2, 3, 4, 5] numbers.each do |n| next if n.even? puts "奇数は#{n}" break if n == 5 end # 例外処理 # begin/rescue/else/ensure begin result = 10 / 0 rescue ZeroDivisionError => e puts "0で割ろうとしました: #{e.message}" else puts "結果は#{result}" ensure puts '計算処理が完了しました' end # each [2, 3, 5, 7, 11].each { |i| puts i } # times/upto/downto 5.times { puts 'Hello' } 1.upto(5) { |i| puts "uptoの値は#{i}" } 5.downto(1) { |i| puts "downtoの値は#{i}" } # tap string = 'Hello' result = string.tap { |s| puts "tap内: #{s}" } puts "tapの結果: #{result}" # yield def greet yield('Morning') yield('Evening') end greet { |time| puts "Good #{time}!" }
条件実行のイディオム
Rubyには条件実行を簡潔に記述するためのいくつかのイディオムがあります。
- 三項演算子
条件式 ? 真の場合の値 : 偽の場合の値
- 三項演算子を使うと、if...elseを1行で記述できます。
allowed = user.admin? ? "Full access" : "Read only"
- ||= (OR代入演算子)
- 値が nil または false の場合のみ右辺の値が代入されます。
name = params[:name] || "Guest" # nameがnilまたは空文字列の場合、"Guest"が代入される
- &&= (AND代入演算子)
- 値が真の場合のみ右辺の値が評価され、代入されます。
obj &&= obj.method # objがnilやfalseでない場合のみ、obj.methodが評価される
- safe navigation 演算子 (&.)
- レシーバがnilの場合にメソッドが呼ばれず、nilを返します。
name = params.dig(:user, :name)&.strip # params[:user]または params[:user][:name]がnilの場合にnilを返す
これらのイディオムを適切に使うことで、条件実行を簡潔で読みやすく記述できます。
反復実行のイディオム
Rubyには反復実行を簡潔に記述するためのいくつかのイディオムがあります。
- each と do...end
[1, 2, 3].each do |n| puts n end
- ブロック付きイテレータ
(1..5).map { |n| n**2 } # => [1, 4, 9, 16, 25] [1, 2, 3].select(&:even?) # => [2]
- times
5.times { puts "Ruby!" } # "Ruby!"が5回出力される
- loop do...end
loop do cmd = gets.chomp break if cmd == "exit" # 処理 end
- each と相当する for-in-end
[1, 2, 3].each { |n| puts n } for n in [1, 2, 3] do puts n end
- Rubyでは forよりは each の方が
- each はループ変数がメソッドに閉じている。
- eachは通常 self を返し、breakされると nil を返す
- eachはメソッドチェインを構成しやすい
- などの理由で好まれます。
- while/until
ary = [1, 2, 3] i = 0 while i < ary.length puts ary[i] i += 1 end
- Enumerable#each_with_index
["a", "b", "c"].each_with_index { |item, index| puts "#{index}: #{item}"}
- Enumerable#with_index
["a", "b", "c"].with_index { |item, index| puts "#{index}: #{item}"}
Rubyの反復構文はループだけでなく、ブロックやイテレータによる関数型のスタイルの記述も可能です。状況に応じて適切な方法を選ぶことで、簡潔でわかりやすいコードを書くことができます。
(中級編あるいはリファレンス編に移動予定)
条件分岐
条件分岐は、プログラムの中で特定の条件が満たされた場合に異なる処理を実行するための制御構造です。 Rubyには、if-elsif-else-end 構文、case-when-else-end 構文があります。
if-elsif-else-end
if
、elsif
、else
キーワードを使用して条件分岐を記述します。以下はその基本的な構文です:
if 条件式 # 条件が真の場合の処理 elsif 別の条件式 # 条件が真の場合の処理 else # どの条件も満たされない場合の処理 end
例えば、次のように使用します:
age = 20 if age >= 20 puts "You are an adult." else puts "You are not an adult yet." end
この場合、age
が20以上の場合は"You are an adult."が出力され、それ以外の場合は"You are not an adult yet."が出力されます。
case-when-else-end
Rubyのcase-when-else-end
文は、条件に応じて異なる処理を行うための制御構造です。
以下に、基本的な構文と使い方を解説します。
case 式 when 条件1 # 式が条件1に合致したときに実行されるコード when 条件2, 条件3 # 式が条件2または条件3に合致したときに実行されるコード else # どの条件にも合致しない場合に実行されるコード end
case
の後に評価したい式を置きます。その式の評価結果と、when
節の各条件を比較し、合致する場合に対応するブロックが実行されます。else
節は、どの条件にも合致しない場合のデフォルトの処理を提供します。
例えば、以下はcase-when-else-end
文の使用例です。
grade = 'B' puts case grade when 'A' then 'Excellent!' when 'B' then 'Good job!' when 'C' then 'You passed, but you could do better.' else 'Sorry, you failed.' end #=> Good job!
この例では、変数grade
の値に応じて異なるメッセージが出力されます。grade
が'A'の場合は"Excellent!"が出力され、'B'の場合は"Good job!"が出力されます。grade
が'C'の場合は"You passed, but you could do better."が出力され、どの条件にも一致しない場合は"Sorry, you failed."が出力されます。
case-when-else-end
文は、複数の条件をテストする必要がある場合や、条件に基づいてさまざまな処理を行う必要がある場合に便利です。
パターンマッチング
パターンマッチングは、プログラムの中で特定のパターンに処理を実行するための制御構造です。
Rubyには、case-in-else-end 構文と=>
演算子とin
演算子があります。
case-in-else-end
Rubyのパターンマッチング(Pattern Matching)は、構造化された値の深いマッチングを可能にする機能です。これにより、構造をチェックし、一致した部分をローカル変数にバインドすることができます。
Rubyのパターンマッチングは、case/in
式を使用して実装されています。
以下に、その基本的な構文と使い方を示します。
case 式 in パターン1 # 式がパターン1に合致したときに実行されるコード in パターン2 # 式がパターン2に合致したときに実行されるコード else # どのパターンにも合致しない場合に実行されるコード end
case
式の中のin
ブロックは、パターンと一致するかどうかをチェックし、マッチした場合に対応するコードを実行します。else
節は、どのパターンにも一致しない場合のデフォルトの処理を提供します。
以下は、パターンマッチングの使用例です。
puts case [2, 3, 5] in 0, 1, 2 '最初の整数' in 0, 2, 4 '最初の偶数' in 1, 3, 5 '最初の奇数' in 2, 3, 5 => n "最初の素数、三番目は#{n}" else 'どの数列でもない' end # 出力: "最初の素数、三番目は5"
このコードは、パターンマッチングを利用して、与えられた配列の最初の要素に応じて異なる処理を行います。与えられた配列 [2, 3, 5]
の最初の要素が特定のパターンに一致するかどうかをテストし、一致する場合に対応するメッセージを出力します。
具体的には、与えられた配列 [2, 3, 5]
の最初の要素が各条件に一致するかどうかをテストします。
[2, 3, 5]
は0, 1, 2
に一致せず、次の条件へ進みます。[2, 3, 5]
は0, 2, 4
に一致せず、次の条件へ進みます。[2, 3, 5]
は1, 3, 5
に一致せず、次の条件へ進みます。[2, 3, 5]
は2, 3, 5 => n
に一致し、n
に7
が割り当てられます。
最終的に "最初の素数、三番目は7"
というメッセージが出力されます。
else
節は、どの条件にも一致しない場合に実行されますが、この場合、どの数列でもないことを示すメッセージが出力されることになります。
=>演算子とin演算子
=>演算子とin演算子 は、パターンマッチングを簡潔に行う方法の1つです。通常、パターンマッチングは複数の行にわたるケース式や条件分岐を使用しますが、=>演算子とin演算子 は1行の式でパターンをマッチングする手法です。
具体的には、次のような形式で表現されます:
式 => パターン
あるいは
式 in パターン
この形式では、式
の値が パターン
にマッチするかどうかを評価します。
=>演算子とin演算子 でパターンマッチングに失敗すると、NoMatchingPatternKeyError例外が raise されます。
{a: 1, b: 2, c: 3} => { b: n } puts n # 2 {a: 1, b: 2, c: 3} => { x: n } # {:a=>1, :b=>2, :c=>3}: key not found: :x (NoMatchingPatternKeyError)
さまざまなパターン
パターンマッチングにはさまざまなパターンがあります:
- 値パターン(Value pattern)
- 配列パターン(Array pattern)
- ハッシュパターン(Hash pattern)
- バインディングパターン(Variable pattern)
- その他のパターン(Alternative pattern)
これらのパターンを組み合わせて、複雑なデータ構造をマッチングできます。また、パターンマッチングではマッチした部分をローカル変数にバインドすることもできます。
反復
Rubyにおける反復(ループ)は、いくつかの方法で行うことができます。主な方法は以下の通りです。
eachメソッド
配列やハッシュなどのコレクションに対して使われる最も一般的な反復方法です。以下はその例です。
array = [1, 2, 3, 4, 5] array.each do |item| puts item end
for文
for文も使用できますが、Rubyではあまり一般的ではありません。
for i in 1..5 puts i end
while文
while文を使って条件が満たされている間、ブロック内のコードを実行します。
x = 0 while x < 5 do puts x x += 1 end
until文
until文は条件が満たされるまでループします。
x = 0 until x == 5 do puts x x += 1 end
timesメソッド
固定回数のループを行う場合に便利です。
5.times do |i| puts i end
これらの方法を組み合わせて、さまざまな反復のニーズに対応できます。 ただし、Rubyではeachメソッドが最も一般的で、他の方法よりも好まれることが多いです。
メソッド
メソッドは、Rubyプログラムで特定の処理を実行するための手段を提供します。メソッドは、関数や手続きと同様の機能を持ち、再利用可能なコードブロックを定義します。Rubyでは、メソッドはオブジェクト指向プログラミングの基本的な概念であり、ほとんどの処理がメソッドとして定義されます。
メソッドの定義
メソッドは以下のような形式で定義されます:
def メソッド名(引数1, 引数2, ...) # メソッドの処理 end
ここで、def
キーワードを使ってメソッドを定義し、メソッド名を指定します。メソッドが引数を取る場合は、引数のリストを指定します。メソッドの本体は、end
キーワードで終了します。
以下は、簡単なメソッドの例です:
def greet(name) puts "Hello, #{name}!" end greet("Alice")
この例では、greet
という名前のメソッドが定義されています。このメソッドはname
という引数を取り、それを使用して挨拶を表示します。greet("Alice")
という呼び出しでは、"Hello, Alice!"というメッセージが表示されます。
Endlessなメソッドの定義
end
キーワードを伴わず、代入に似た形式でも定義できます:
def メソッド名(引数1, 引数2, ...) = 式
ここで、def
キーワードと=
を使ってメソッドを定義し、メソッド名を指定します。メソッドが引数を取る場合は、引数のリストを指定します。
以下は、簡単なメソッドの例です:
def greet(name) = puts "Hello, #{name}!" greet("Alice")
戻り値
また、Rubyではメソッド内で最後に評価された式が自動的にメソッドの戻り値となります。明示的なreturn
文は省略することができますが、必要に応じて使用することもできます。
ブロックを伴うメソッド
さらに、メソッドはオプションのブロックを受け取ることができます。ブロックを受け取るメソッドは、yield
キーワードを使ってブロックを実行します。
def greet(name) puts "Hello, #{name}!" yield if block_given? end
このメソッドでは、引数として受け取ったname
に対して挨拶を出力し、その後にyield
を使ってブロックを実行しています。block_given?
メソッドは、メソッドがブロックを受け取ったかどうかを判定します。
メソッドを呼び出す際には、通常の引数と一緒にブロックを渡すことができます。
greet("Alice") do puts "Nice to meet you!" end
このコードは、以下を出力します。
Hello, Alice! Nice to meet you!
配列とハッシュ
配列の操作
配列は複数の要素を格納するためのデータ構造であり、それぞれの要素は順序付けされています。Rubyでは、配列は角かっこ [ ]
で囲まれ、コンマで区切られた要素のリストとして表現されます。
要素の追加、削除、取得
- 要素の追加:
<<
演算子やpush
メソッドを使用して要素を配列に追加することができます。fruits = ['apple', 'banana', 'orange'] fruits << 'grape' fruits.push('kiwi')
- 要素の削除:
delete_at
メソッドやpop
メソッドを使用して配列から要素を削除することができます。fruits.delete_at(0) # インデックス0の要素を削除 fruits.pop # 末尾の要素を削除
- 要素の取得: インデックスを指定して配列から要素を取得することができます。
puts fruits[0] # => "banana"
ハッシュの操作
ハッシュは、キーと値のペアを格納するデータ構造であり、各要素はキーを使用して参照されます。Rubyでは、波かっこ { }
を使用してハッシュを定義します。
キーと値の追加、削除、取得
- キーと値の追加: ハッシュに新しいキーと値のペアを追加するには、単純に代入演算子
=
を使用します。person = {'name' => 'Alice', 'age' => 30} person['gender'] = 'female'
- キーと値の削除:
delete
メソッドを使用して、指定したキーとそれに対応する値を削除することができます。person.delete('age')
- キーと値の取得: ハッシュから特定のキーに関連付けられた値を取得するには、そのキーを指定します。
puts person['name'] # => "Alice"
クラスとオブジェクト指向プログラミング
クラスの定義とインスタンス化
クラスは、オブジェクトの設計図として機能し、それぞれのオブジェクトはそのクラスのインスタンスです。Rubyでは、class
キーワードを使用してクラスを定義します。
クラスの定義
class Person def initialize(name, age) @name = name @age = age end def introduce puts "My name is #{@name} and I'm #{@age} years old." end end
この例では、Person
という名前のクラスが定義されています。initialize
メソッドは、新しいインスタンスが作成されるときに呼び出され、インスタンス変数 @name
と @age
に値を設定します。introduce
メソッドは、そのインスタンスの情報を出力します。
インスタンス化
alice = Person.new("Alice", 30) bob = Person.new("Bob", 25)
これにより、Person
クラスの新しいインスタンス alice
と bob
が作成されます。それぞれのインスタンスは、initialize
メソッドによって設定された名前と年齢を持ちます。
alice.introduce # => "My name is Alice and I'm 30 years old." bob.introduce # => "My name is Bob and I'm 25 years old."
各インスタンスは、クラス内で定義されたメソッドを呼び出すことができます。
オブジェクト指向プログラミングの基礎
オブジェクト指向プログラミング(OOP)は、プログラムをオブジェクトとそれらの相互作用に基づいて構造化する方法です。OOPには、次のような重要な概念があります:
- カプセル化: データとそれに関連するメソッドを1つのユニットにまとめ、外部からのアクセスを制限します。
- 継承: 既存のクラスから新しいクラスを作成し、既存の機能を再利用します。
- ポリモーフィズム: 同じ名前のメソッドが異なるクラスで異なる振る舞いをすることを許容します。
これらの概念を使用することで、コードをより柔軟で保守しやすくすることができます。
中級編
例外処理
begin-rescue-end文を使った例外処理の実装
例外処理は、予期しないエラーが発生した場合にプログラムの実行を中断せずに制御を継続するための仕組みです。Rubyでは、begin
、rescue
、end
キーワードを使用して例外処理を実装します。
begin # 例外が発生する可能性のあるコード result = 10 / 0 rescue ZeroDivisionError => e # 例外が発生した場合の処理 puts "Error occurred: #{e.message}" end
上記の例では、begin
ブロック内でゼロ除算エラーが発生する可能性があります。rescue
ブロックでは、特定の例外(ここではZeroDivisionError
)が発生した場合の処理を指定します。例外が発生した場合、その例外オブジェクトがrescue
ブロックの変数e
に割り当てられ、message
メソッドを使ってエラーメッセージを取得します。
ファイル操作
ファイルの読み書き
ファイルの読み書きは、プログラムでよく行われる操作の1つです。Rubyでは、File
クラスを使用してファイルの読み書きを行います。
# ファイルの書き込み File.open("example.txt", "w") do |file| file.write("Hello, World!") end # ファイルの読み込み File.open("example.txt", "r") do |file| content = file.read puts content # => "Hello, World!" end
ファイルポインタの移動
ファイルポインタを移動して、ファイル内の特定の位置から読み書きを行うこともできます。
# ファイルのオープン File.open("example.txt", "r") do |file| # ファイルポインタの移動 file.seek(7) # 指定位置からの読み込み content = file.read puts content # => "World!" }
モジュールとMix-in
モジュールの作成と利用
モジュールは、関連するメソッドや定数をまとめるための仕組みであり、クラスにインクルードすることでその機能を利用することができます。
module Greeting def say_hello puts "Hello!" end end class MyClass include Greeting end obj = MyClass.new obj.say_hello # => "Hello!"
上記の例では、Greeting
モジュールを定義し、MyClass
クラスにインクルードしています。その結果、MyClass
のインスタンスであるobj
からsay_hello
メソッドが利用できるようになります。
Mix-inパターンの活用
Mix-inは、複数のクラスに同じメソッドや振る舞いを提供するためのデザインパターンです。モジュールを使ってMix-inを実現することができます。
module Debug def debug_info puts "#{self.class} - #{self.inspect}" end end class MyClass include Debug end obj = MyClass.new obj.debug_info # => "MyClass - #<MyClass:0x00007fb35a20b3b8>"
Debug
モジュールは、debug_info
メソッドを提供し、このメソッドを含むクラスにMix-inします。これにより、クラスのインスタンスがデバッグ情報を表示するメソッドを利用できるようになります。
テスト
Mintest
Minitestは、Rubyプログラミング言語用の軽量なテストフレームワークです。Minitestは、Rubyの標準ライブラリに含まれており、Rubyのバージョン1.9以降で利用可能です。Minitestは、テスト駆動開発(Test-Driven Development、TDD)や振る舞い駆動開発(Behavior-Driven Development、BDD)などのソフトウェア開発手法を支援するために使用されます。
Minitestは、単体テストや統合テストなど、さまざまな種類のテストをサポートしています。また、アサーションを用いて期待される振る舞いを確認するための豊富な機能も提供しています。Minitestの特徴は、シンプルで使いやすいインターフェース、高速な実行速度、そしてRubyの標準ライブラリに含まれているため、追加の依存関係を導入せずに利用できることです。
開発者がMinitestを使用することで、安定性や品質を向上させることができます。また、テストコードの記述や実行が容易であるため、素早く効果的なテストを作成することができます。
実際のコードを観てみましょう。
下記は、逆ポーランド記法の式を評価する機能を配列を継承して実装した例で、コメントを合わせても30行のコードですが、各演算子の実装の確認など基礎的なテストケースを書くだけで、数倍のコード量になります。
- Minitestの例
# frozen_string_literal: true # RPNクラスは逆ポーランド記法(Reverse Polish Notation)の式を評価するためのクラスです。 # スタック構造を使用して式を解析し、結果を計算します。 # Rubyでは、Arrayクラスがpop,pushなどのスタックとしてのメソッドが完備されているので継承しました。 class RPN < Array # 与えられた逆ポーランド記法の式を評価し、結果を返します。 # @param expression [String] 評価する式 # @return [Numeric] 式の評価結果 def eval(expression) expression.split.each do |token| case token when /\A-?\d+\z/, # 十進数 /\A[+-]?0[Bb][01]+\z/, # 2進数 /\A[+-]?0[Oo][0-7]+\z/, # 8進数 /\A[+-]?0[Xx][0-9A-Fa-f]+\z/ # 10進数 push Integer(token) when /\A-?\d+(\.\d+)?([eE][-+]?\d+)?\z/ # 浮動小数点数 push(token.to_f) when *%w[+ - * / % ** & | ^ << >>] # 二項演算子 left, right = pop(2) push left.send(token.to_sym, right) else raise RuntimeError, "Invalid operator: #{token}" end end # 最終的な結果はスタックの一番上に残る peek end # スタックの一番上の値を返しますが、スタックから削除しません。 # @return [Numeric, nil] スタックの一番上の値。スタックが空の場合はnilを返します。 alias peek last end require 'minitest/autorun' class TestRPN < Minitest::Test def setup @rpn = RPN.new end def test_eval_with_single_operand assert_equal 5, @rpn.eval('5') assert_equal 5, @rpn.eval('0b101') assert_equal -5, @rpn.eval('-0b101') assert_equal 5, @rpn.eval('+0b101') assert_equal 127, @rpn.eval('0o177') assert_equal -127, @rpn.eval('-0o177') assert_equal 127, @rpn.eval('+0O177') assert_equal 195935983, @rpn.eval('0xbadbeef') assert_equal -195935983, @rpn.eval('-0xbadbeef') assert_equal 1.5, @rpn.eval('1.5') assert_equal 1e234, @rpn.eval('1e+234') end def test_eval_with_addition assert_equal 8, @rpn.eval('3 5 +') assert_equal 10, @rpn.eval('2 +') end def test_eval_with_subtraction assert_equal 7, @rpn.eval('10 3 -') end def test_eval_with_multiplication assert_equal 24, @rpn.eval('4 6 *') end def test_eval_with_division assert_equal 5, @rpn.eval('15 3 /') end def test_eval_with_remain assert_equal 2, @rpn.eval('11 3 %') end def test_eval_with_exponentiation assert_equal 8, @rpn.eval('2 3 **') assert_equal 1.4142135623730951, @rpn.eval('2 0.5 **') assert_equal 633825300114114700748351602688, @rpn.eval('2 99 **') end def test_eval_with_bitwidth_and assert_equal 8, @rpn.eval('12 10 &') assert_raises(NoMethodError) { @rpn.eval('12.0 10 &') } assert_raises(TypeError) { @rpn.eval('12 10.0 &') } end def test_eval_with_bitwidth_or assert_equal 14, @rpn.eval('12 10 |') assert_raises(NoMethodError) { @rpn.eval('12.0 10 |') } assert_raises(TypeError) { @rpn.eval('12 10.0 |') } end def test_eval_with_bitwidth_exclusive_or assert_equal 6, @rpn.eval('12 10 ^') assert_raises(NoMethodError) { @rpn.eval('12.0 10 ^') } assert_raises(TypeError) { @rpn.eval('12 10.0 ^') } end def test_eval_with_shift_left assert_equal 12288, @rpn.eval('12 10 <<') assert_raises(NoMethodError) { @rpn.eval('12.0 10 <<') } assert_equal 12288, @rpn.eval('12 10.1 <<') end def test_eval_with_shift_right assert_equal 15, @rpn.eval('123 3 >>') assert_raises(NoMethodError) { @rpn.eval('123.0 3 >>') } assert_equal 15, @rpn.eval('123 3.9 >>') end def test_eval_with_invalid_expression assert_raises(TypeError) { @rpn.eval('2 +') } end def test_peek_returns_top_element @rpn.eval('1 2 3 + +') assert_equal 6, @rpn.peek end def test_nan_or_zero_division @rpn.eval('0.0 0 /') assert @rpn.peek.nan? @rpn.eval('0 0.0 /') assert @rpn.peek.nan? @rpn.eval('-0.0 0 /') assert @rpn.peek.nan? assert_raises(ZeroDivisionError) { @rpn.eval('0 0 /') } end def test_inf_or_zero_division @rpn.eval('1.0 0 /') assert_equal Float::INFINITY, @rpn.peek @rpn.eval('-1 0.0 /') assert_equal -Float::INFINITY, @rpn.peek @rpn.eval('1 -0.0 /') assert_equal -Float::INFINITY, @rpn.peek @rpn.eval('-1 -0.0 /') assert_equal Float::INFINITY, @rpn.peek assert_raises(ZeroDivisionError) { @rpn.eval('110 0 /') } end def test_peek_returns_nil_for_empty_stack assert_nil @rpn.peek end end
このコードは、逆ポーランド記法(Reverse Polish Notation)の式を評価するためのRPN(Reverse Polish Notation)クラスを定義しています。このクラスは、与えられた式をスタックを使用して解析し、計算します。以下に、このコードの機能と構造についての解説を行います。
- RPNクラス
# frozen_string_literal: true
: この行は、ファイル内の文字列リテラルが変更されないことを確認するためのリテラルです。RPN
クラスはArray
クラスを継承しており、スタックとしての機能を使用します。eval
メソッドは与えられた逆ポーランド記法の式を評価し、結果を返します。peek
メソッドは、スタックの一番上の値を返しますが、スタックから削除しません。
- テストケース
TestRPN
クラスはMinitest::Test
を継承しており、RPNクラスのテストを定義します。setup
メソッドは各テストメソッドの前に実行され、テストで使用するRPNオブジェクトをセットアップします。
- テストメソッド
test_eval_with_single_operand
からtest_eval_with_invalid_expression
までのメソッドは、eval
メソッドが正しく動作することを確認するためのテストです。test_peek_returns_top_element
はpeek
メソッドがスタックの一番上の値を返すことを確認するテストです。test_nan_or_zero_division
とtest_inf_or_zero_division
は、NaNやInfinityの扱い、ゼロ除算の例外処理をテストします。- これらのテストは、RPNクラスの各機能が正しく動作することを保証します。
このコードは、逆ポーランド記法の式の評価に関する機能を提供し、テストによってその正確性が検証されています。
RSpec
RSpecは、Rubyでのテストを行うためのフレームワークの1つです。RSpecを使用すると、テストをより明確に記述し、テスト結果を見やすく出力することができます。
- Gemfile
# Gemfile source 'https://rubygems.org' gem 'rspec'
- 導入
# コマンドライン bundle install
ユニットテストと統合テストの書き方
ユニットテストは、個々のコンポーネント(クラスやメソッドなど)が正しく機能するかをテストするものです。一方、統合テストは複数のコンポーネントが互いに連携して正しく動作するかをテストします。
- spec/my_class_spec.rb
# spec/my_class_spec.rb (ユニットテスト) require 'my_class' RSpec.describe MyClass do describe '#method_name' do it 'returns something' do obj = MyClass.new expect(obj.method_name).to eq(something) end end end
- spec/integration_spec.rb
# spec/integration_spec.rb (統合テスト) require 'app' RSpec.describe 'Integration Test' do it 'checks something' do # 統合テストの実装 end end
RSpecを使ってユニットテストと統合テストを書くことで、コードの品質を向上させ、予期せぬバグを見つけるのに役立ちます。
上級編
高度なオブジェクト指向プログラミング
継承、ポリモーフィズム、カプセル化の活用
継承は、既存のクラスから新しいクラスを作成し、そのクラスが親クラスのすべての特性を引き継ぐことができる機能です。ポリモーフィズムは、同じ名前のメソッドが異なるクラスで異なる振る舞いをすることを指します。カプセル化は、データやメソッドをオブジェクト内部に隠蔽し、外部からのアクセスを制限することです。これらの概念を活用することで、より柔軟で効率的なコードを書くことができます。
class Animal def speak raise NotImplementedError, "Subclasses must implement the 'speak' method." end end class Dog < Animal def speak "Woof!" end end class Cat < Animal def speak "Meow!" end end dog = Dog.new puts dog.speak # => "Woof!" cat = Cat.new puts cat.speak # => "Meow!"
上記の例では、Animal
クラスが親クラスとして定義され、speak
メソッドが定義されています。Dog
クラスとCat
クラスはそれぞれAnimal
クラスを継承し、speak
メソッドをオーバーライドしています。これにより、Dog
クラスとCat
クラスは同じ名前のメソッドを持ちつつも、それぞれ異なる振る舞いをします。
オブジェクト指向デザインパターンの理解と実装
オブジェクト指向デザインパターンは、再利用可能なソフトウェア設計のガイドラインです。これらのパターンは、特定の問題に対する解決策を提供し、コードの再利用性、拡張性、保守性を向上させます。代表的なデザインパターンには、Singleton、Factory、Observer、Decoratorなどがあります。
# Singleton パターンの例 require 'singleton' class SingletonClass include Singleton def initialize @counter = 0 end def increment_counter @counter += 1 end def get_counter @counter end end instance1 = SingletonClass.instance instance1.increment_counter puts instance1.get_counter # => 1 instance2 = SingletonClass.instance puts instance2.get_counter # => 1
上記の例では、Singletonパターンを使用して、特定のクラスのインスタンスが常に1つしか存在しないことを保証しています。Singleton
モジュールをインクルードすることで、インスタンスが複数作成されることを防ぎます。
並行プログラミング
スレッド、プロセスの管理
Rubyにおける並行プログラミングは、スレッドとプロセスを使用して実現されます。スレッドはプロセス内で動作し、複数のスレッドが同時に実行されることで並行処理が可能になります。一方、プロセスは独立した実行単位であり、それぞれが独自のメモリ空間を持ちます。
以下では、Rubyにおけるスレッドとプロセスの管理について解説し、コード例を示します。
スレッド
RubyではThread
クラスを使用してスレッドを作成し、Thread.new
メソッドを使って新しいスレッドを生成します。生成されたスレッドは並行して実行され、メインスレッドと同時に動作します。スレッドの制御にはjoin
メソッドを使用して、メインスレッドがスレッドの終了を待機することができます。
threads = [] # スレッドを生成して配列に格納 threads << Thread.new do 3.times do sleep 1 puts "Thread 1 executing" end end threads << Thread.new do 3.times do sleep 2 puts "Thread 2 executing" end end # 全てのスレッドが終了するのを待機 threads.each(&:join) puts "All threads finished"
上記の例では、2つのスレッドを生成し、それぞれが一定間隔でメッセージを出力しています。join
メソッドにより、メインスレッドが全てのスレッドの終了を待機します。
プロセス
Rubyでは、fork
メソッドを使用して新しいプロセスをフォークし、子プロセス内で処理を実行することができます。プロセスの終了を待機するにはProcess.wait
メソッドを使用します。
child_pid = fork do puts "Child process executing" sleep 3 puts "Child process finished" end puts "Parent process waiting for child to finish" Process.wait(child_pid) puts "Parent process finished waiting"
上記の例では、親プロセスが子プロセスをフォークし、子プロセスが一定時間待機した後に終了します。親プロセスはProcess.wait
メソッドで子プロセスの終了を待機します。
Rubyのスレッドとプロセスを使った並行プログラミングには、他にも多くの機能や制御方法がありますが、基本的な部分はこのようになります。
スレッド、プロセス以外の並行プログラミング
Rubyには、スレッドとプロセス以外にも並行プログラミングを行うためのさまざまな手段があります。以下にいくつかの方法を紹介します。
Fiber
Fiberは、イテレータと似たコンテキストを持つ軽量なスレッドのようなものです。複数のFiberを作成し、それらを切り替えながら処理を行うことで、非同期の並行処理を実現することができます。
fiber1 = Fiber.new do puts "Fiber 1 executing" Fiber.yield puts "Fiber 1 resumed" end fiber2 = Fiber.new do puts "Fiber 2 executing" Fiber.yield puts "Fiber 2 resumed" end fiber1.resume fiber2.resume fiber1.resume fiber2.resume
Concurrent Ruby gem
Concurrent Ruby gemは、並行プログラミングを行うための機能を提供するGemです。ThreadPoolやFuture、Promise、Actorなどの機能を使って、複雑な並行処理を実現することができます。
require 'concurrent' pool = Concurrent::FixedThreadPool.new(5) future = Concurrent::Future.execute(executor: pool) do # 並行処理を実行するコード sleep 1 "Future result" end puts "Waiting for future to complete..." puts future.value
Celluloid gem
Celluloid gemは、アクターモデルをベースにした並行プログラミングを行うためのGemです。アクターモデルでは、個々のアクターがメッセージを送受信しながら処理を実行します。
require 'celluloid' class MyActor include Celluloid def initialize puts "Actor initialized" end def process_message(message) puts "Received message: #{message}" end end actor = MyActor.new actor.async.process_message("Hello from main thread!")
これらの手法を使うことで、Rubyでより効果的な並行プログラミングを実現することができます。選択肢はプロジェクトの要件や好みに応じて異なりますが、Rubyの並行処理に関する柔軟性と機能性は広範囲にわたっています。
並行処理の問題点と解決策
並行プログラミングでは、競合状態やデッドロックなどの問題が発生する可能性があります。競合状態は、複数のスレッドが同時に共有されたリソースにアクセスしようとすることで起こります。デッドロックは、複数のスレッドがお互いにリソースを解放するのを待ち合わせることで発生します。
これらの問題を回避するための解決策として、適切なロックの使用、スレッドセーフなデータ構造の選択、並行処理の慎重な設計などがあります。
並行処理の問題点と解決策について、コード例を示して説明します。
問題点: 競合状態
競合状態は、複数のスレッドが同時に共有されたリソースにアクセスしようとすることで発生します。
以下は、競合状態が発生する可能性のあるコード例です。
counter = 0 threads = 10.times.map do Thread.new do 1000.times { counter += 1 } end end threads.each(&:join) puts "Counter: #{counter}"
このコードでは、複数のスレッドがcounter
変数にアクセスしています。しかし、counter += 1
の操作は複数のスレッドで同時に実行される可能性があり、結果として期待通りにカウンターが増加しない可能性があります。
解決策: ロックの使用
競合状態を回避するために、ロックを使用して複数のスレッドが同時に共有されたリソースにアクセスできないようにします。Rubyでは、Mutex
クラスを使用してスレッドセーフなロックを実装できます。
counter = 0 mutex = Mutex.new threads = 10.times.map do Thread.new do 1000.times do mutex.synchronize { counter += 1 } end end end threads.each(&:join) puts "Counter: #{counter}"
このコードでは、Mutex
を使用してcounter
変数へのアクセスを同期化しています。
これにより、複数のスレッドが同時にcounter
変数にアクセスすることができず、競合状態が回避されます。
問題点: デッドロック
デッドロックは、複数のスレッドがお互いにリソースを解放するのを待ち合わせることで発生します。
以下は、デッドロックが発生する可能性のあるコード例です。
mutex1 = Mutex.new mutex2 = Mutex.new thread1 = Thread.new do mutex1.lock sleep 1 mutex2.lock mutex1.unlock mutex2.unlock end thread2 = Thread.new do mutex2.lock sleep 1 mutex1.lock mutex2.unlock mutex1.unlock end thread1.join thread2.join
このコードでは、thread1
がmutex1
をロックし、同時にthread2
がmutex2
をロックする可能性があります。その後、各スレッドは相手のロックを解放するのを待ち合わせることになり、デッドロックが発生します。
解決策: ロックの順序付け
デッドロックを回避するために、ロックを取得する順序を一貫させることが重要です。 この方法により、複数のスレッドが同じ順序でロックを取得しようとしても、デッドロックを引き起こすことがありません。
mutex1 = Mutex.new mutex2 = Mutex.new thread1 = Thread.new do mutex1.lock sleep 1 mutex2.lock mutex1.unlock mutex2.unlock end thread2 = Thread.new do mutex1.lock # ロックの順序を変更 sleep 1 mutex2.lock mutex1.unlock mutex2.unlock end thread1.join thread2.join
この修正では、thread2
がmutex1
を先にロックするように変更されており、ロックの順序が一致しています。これにより、デッドロックが発生する可能性が低くなります。
以上が、並行処理の問題点とその解決策をコード例を交えて説明したものです。競合状態やデッドロックは並行プログラミングにおける一般的な問題であり、適切な対処が必要です。
Webアプリケーション開発
Ruby on Railsなどのフレームワークを使用したWebアプリケーションの開発
Ruby on Railsは、RubyでのWebアプリケーション開発を支援する人気のあるフレームワークです。Railsを使用することで、MVC(Model-View-Controller)アーキテクチャをベースにした効率的なWebアプリケーションを開発することができます。
# Railsアプリケーションの作成 rails new myapp # モデルの作成 rails generate model User name:string email:string # データベースマイグレーションの実行 rails db:migrate # コントローラの作成 rails generate controller UsersController
- config/routes.rb
# ルーティングの設定 Rails.application.routes.draw do resources :users end
Railsでは、コマンドラインツールを使用してモデルやコントローラを簡単に生成し、ルーティングを設定することができます。これにより、短期間で効率的なWebアプリケーションを構築することができます。
MVCアーキテクチャの理解
MVC(Model-View-Controller)アーキテクチャは、アプリケーションの設計を3つの主要なコンポーネントに分割するアーキテクチャパターンです。
- モデル (Model)
-
- データやビジネスロジックを担当します。
- アプリケーションの状態やデータを管理し、ビューに表示されるデータを提供します。
- モデルは通常、データベースや外部サービスとのやり取りも担当します。
- ビュー (View)
-
- ユーザーインターフェース(UI)の表示を担当します。
- ユーザーにデータを視覚的に提示し、ユーザーからの入力を受け付けます。
- ビューは通常、モデルからデータを受け取り、それをユーザーに見やすい形で表示します。
- コントローラ (Controller)
-
- ユーザーからの入力を受け付け、それに基づいてモデルやビューの操作を制御します。
- ユーザーの操作に応じてモデルの状態を変更し、ビューの更新をトリガーします。
- コントローラはユーザーとアプリケーションの主要な仲介者であり、ビジネスロジックを含む場合があります。
- 相互関係
-
- ユーザーがビューで何かしらの操作を行うと、それに対するイベントがコントローラに送信されます。
- コントローラはそのイベントを受け取り、必要に応じてモデルを更新したり、新しいデータをビューに送ったりします。
- モデルが変更されると、モデルはその変更をビューに通知します。
- ビューは通知を受け取り、表示を更新します。
このように、MVCアーキテクチャは各コンポーネントが疎結合であり、変更が発生した際に一部のコンポーネントを変更しても他のコンポーネントに影響を与えにくい特徴があります。これにより、柔軟性が向上し、保守性が高まります。
RoRにおけるMVC
RoR(Ruby on Rails)は、MVC(Model-View-Controller)アーキテクチャの理念に基づいて設計されたWebアプリケーションフレームワークです。RoRにおけるMVCの役割と機能を説明します。
- モデル (Model)
- RoRのモデルは、アプリケーションのデータベースとのやり取りを担当します。
- モデルはActive Recordとして実装されており、データベースのテーブルと対応するオブジェクトを表現します。
- モデルはビジネスロジックを含み、データの検証や処理を行います。
class User < ApplicationRecord validates :name, presence: true end
- ビュー (View)
- RoRのビューは、ユーザーにデータを表示するためのテンプレートを提供します。
- HTMLやERB(Embedded Ruby)などのテンプレートエンジンを使用して、動的なコンテンツを生成します。
- ビューは主にユーザーインターフェースの生成に使用されます。
<% @users.each do |user| %> <p><%= user.name %></p> <% end %>
- コントローラ (Controller)
- RoRのコントローラは、ユーザーからのリクエストを受け取り、それに応じて適切な処理を行います。
- リクエストの解析やデータの操作など、ビジネスロジックの一部もコントローラで処理されます。
- ビューの表示を制御し、モデルへのデータの渡し方もコントローラで決定されます。
class UsersController < ApplicationController def index @users = User.all end end
- ルーティング (Routing)
- RoRでは、URLとコントローラアクションの対応をルーティングで定義します。
- ルーティングは
config/routes.rb
ファイルに記述され、特定のURLパスへのリクエストを特定のコントローラアクションにマップします。
Rails.application.routes.draw do resources :users, only: [:index] end
- MVCの相互関係
- ユーザーからのリクエストはルーティングで適切なコントローラアクションに送信されます。
- コントローラは必要に応じてモデルからデータを取得し、適切なビューをレンダリングしてユーザーに表示します。
- ビューはユーザーにデータを表示し、必要に応じてユーザーの入力を受け付けます。
- ユーザーの入力やアクションによっては、再びコントローラへリクエストが送信され、その処理が繰り返されます。
Ruby on RailsはMVCアーキテクチャにより、アプリケーションの各コンポーネントが明確に分離され、保守性が向上し、柔軟性が高まります。
データベース連携
SQLデータベースとの接続
Railsでは、アクティブレコードと呼ばれるオブジェクト関係マッピング(ORM)ツールを使用して、データベースとのやり取りを簡単に行うことができます。アクティブレコードを使用することで、SQLクエリを直接記述せずに、Rubyオブジェクトとしてデータベースのテーブルを操作することができます。
# ユーザーデータの取得 users = User.all # 新しいユーザーの作成 user = User.new(name: "Alice", email: "alice@example.com") user.save
Railsでは、データベースへの接続設定を行うことで、アプリケーション内でアクティブレコードを使用してデータベースとやり取りすることができます。
ORM(Object-Relational Mapping)の活用
ORMは、オブジェクトとリレーショナルデータベースとの間のマッピングを行うためのツールです。ORMを使用することで、データベースのテーブルをオブジェクトとして操作することができ、SQLクエリの記述を最小限に抑えることができます。これにより、コードの可読性や保守性が向上します。
パフォーマンスチューニング
メモリ管理、コードの最適化
メモリ管理とコードの最適化は、Rubyプログラミングにおいて重要な要素です。効率的なメモリ管理を行うことで、アプリケーションのメモリ使用量を最小限に抑え、パフォーマンスを向上させることができます。また、コードの最適化により、処理速度を高速化し、リソースの効率的な利用を実現することができます。
- メモリ管理のベストプラクティス
- 不要なオブジェクトの解放: 不要なオブジェクトを定期的に解放することで、メモリリークを防止しましょう。
- 大量のデータの処理: 大規模なデータ処理が必要な場合は、メモリを過剰に消費することがあります。代わりに、イテレータやジェネレータを使用してデータを効率的に処理しましょう。
- メモリフラグメンテーション: メモリフラグメンテーションが発生すると、メモリが不連続に配置されるため、効率的なメモリ使用が妨げられます。適切なメモリ管理を行い、フラグメンテーションを最小限に抑えましょう。
- コードの最適化のベストプラクティス
- 効率的なアルゴリズムの選択: 適切なアルゴリズムを選択することで、処理速度を向上させることができます。時間計算量や空間計算量を考慮して、最適なアルゴリズムを選択しましょう。
- 不要なループの削減: 不要なループや再帰呼び出しを削減することで、処理速度を向上させることができます。ループのネストを最小限に抑え、効率的なコードを作成しましょう。
- 遅延読み込み: 大きなデータセットを扱う場合は、遅延読み込みを活用して、必要な時点でデータを取得するようにしましょう。これにより、不要なデータの読み込みを回避し、メモリ使用量を最小限に抑えることができます。
プロファイリングツールの使用
プロファイリングツールは、アプリケーションのパフォーマンスを詳細に分析し、ボトルネックを特定するための有用なツールです。プロファイリングツールを使用することで、どの部分が最もリソースを消費しているかや、どの部分が最も遅いかなどを把握することができます。
- 代表的なプロファイリングツール
- RubyProf: Rubyのプロファイリングツールの1つであり、メソッドごとの実行時間や呼び出し回数などを詳細に分析することができます。
- StackProf: スレッドごとのスタックトレースを収集し、アプリケーションのホットスポットを特定することができるプロファイリングツールです。
- DTrace: オペレーティングシステムレベルでのパフォーマンス分析を行うための強力なツールであり、Rubyアプリケーションのボトルネックを特定するのに役立ちます。
これらのプロファイリングツールを使用して、アプリケーションのパフォーマンスを詳細に分析し、効率的な最適化を行いましょう。
- プロファイリング手法と注意点
- アプリケーション全体のプロファイリング: アプリケーション全体のプロファイリングを行うことで、全体のパフォーマンスを把握し、最も時間がかかる部分を特定します。この情報をもとに、重点的に最適化を行いましょう。
- 繰り返し実行による平均化: プロファイリングを行う際には、複数回の実行を行い、結果を平均化することが重要です。これにより、ランダムな外部要因の影響を排除し、より正確なプロファイリング結果を得ることができます。
- プロファイリングの結果の解釈: プロファイリング結果を解釈する際には、アプリケーションのコードや処理の特性を理解することが重要です。高負荷がかかっている部分やボトルネックを正確に特定し、効果的な最適化を行いましょう。
- プロファイリングの定期的な実施: アプリケーションのコードや処理が変更されるたびに、プロファイリングを定期的に実施することで、パフォーマンスの変化や問題点を早期に検出し、適切な対策を講じることができます。
- セキュリティとプライバシー: プロファイリングツールを使用する際には、セキュリティとプライバシーに配慮しましょう。機密情報や個人情報が含まれる可能性がある場合は、適切な対策を講じて情報漏洩を防止しましょう。
- 総括
- メモリ管理、コードの最適化、およびプロファイリングツールの使用は、Rubyプログラミングにおいてアプリケーションのパフォーマンスを向上させるために重要なスキルです。効率的なメモリ管理とコードの最適化により、リソースの効率的な利用を実現し、プロファイリングツールを使用してボトルネックを特定し、効果的な最適化を行うことで、より高速で効率的なアプリケーションを開発することができます。
さらなるトピック
オブジェクト指向デザインパターンの活用
上級レベルでは、オブジェクト指向デザインパターンの理解と実装に焦点を当てましたが、実際のアプリケーション開発では、これらのパターンを活用して柔軟で効率的なコードを書くことが重要です。より実践的な例や、実際の問題に対する解決策を学ぶことで、より高度な開発スキルを身につけることができます。
セキュリティとエラーハンドリング
プロの開発者になるためには、セキュリティとエラーハンドリングの重要性を理解し、適切な対策を講じることが不可欠です。セキュリティホールや悪意のある攻撃からアプリケーションを保護し、エラーが発生した際にユーザーやシステムに影響を与えないようにするための方法を学びましょう。
テスト駆動開発(TDD)
テスト駆動開発(Test-Driven Development, TDD)は、コードを書く前にテストを書き、そのテストをパスするようなコードを実装する開発手法です。TDDを実践することで、より品質の高いコードを生産し、バグの早期発見やリファクタリングの容易化が可能となります。
コード品質とリファクタリング
コード品質は、プロの開発者にとって非常に重要な要素です。コードを読みやすく、保守しやすく、拡張しやすくするためのリファクタリングのテクニックやベストプラクティスを学び、コードベースを常に改善していきましょう。
これらのトピックを網羅的に学ぶことで、より高度なRubyプログラミングスキルを身につけることができます。プロの開発者としてのキャリアを築くために、着実にスキルを向上させていきましょう。
リファレンス編
変数
Rubyの変数は、オブジェクトに名前をつけるために使われます。
Rubyでは、変数は宣言する必要はありません。
オブジェクトを変数に代入するには
変数名 = オブジェクト
の様に代入演算子 =
を使います。
- 変数へのオブジェクトの代入と参照
a = 1 p a # => 1 a = "abc" p a # => "abc" a = [1, 2, 3] p a # => [1, 2, 3] b = a p b # => [1, 2, 3] b[1] = 999 p a # => [1, 999, 3] # bはaと同じ配列を参照しているため、bの変更がaにも反映されます。
これに対し、pメソッドはオブジェクトにinspectメソッドで適用した結果を表示します。
- putsとp
puts "Hello, world!" # => Hello, world! p "Hello, world!" # => "Hello, world!"
定数
Rubyにおける定数は、変更できない値のことです。定数は大文字で始まり、一度値を代入するとその後変更することができません。定数を宣言するには、変数名の先頭に大文字のアルファベットを付けます。
定数は、クラスやモジュールの定義内で宣言される場合、そのクラスやモジュールのスコープにのみ存在します。クラスやモジュール外で宣言された定数は、グローバルスコープに属します。
定数にアクセスするには、定数名を参照します。定数が未定義の場合、RubyはNameError例外を発生させます。
以下は、定数を宣言して参照する例です。
# クラス内で定数を宣言する class MyClass MY_CONSTANT = 100 end # クラス外で定数を参照する puts MyClass::MY_CONSTANT #=> 100
また、Rubyでは組み込み定数もいくつか存在します。例えば、Math::PI
は円周率を表す定数です。
グローバル変数
グローバル変数は、プログラム中のどこからでもアクセス可能な変数です。グローバル変数は、$
記号で始まる変数名を持ちます。一般的に、グローバル変数は、複数のメソッドで共有するデータを格納するために使用されます。ただし、グローバル変数は多用すべきではなく、できるだけ避けるべきです。
$global_variable = 10 def print_global puts "Global variable is #$global_variable" end print_global
このコードでは、$global_variable
というグローバル変数が定義されています。そして、print_global
メソッド内でその値が参照されて表示されます。グローバル変数はプログラム中のどこからでも参照可能であるため、異なるメソッド間でデータを共有する場合に使用されます。しかし、グローバル変数の使用は避けるべきであり、できるだけローカル変数やインスタンス変数を使用することが推奨されます。
特殊変数
特殊変数は、Rubyがあらかじめ定義している変数で、プログラム中で直接代入することができません。これらの変数は、プログラムの実行中に自動的に設定され、Rubyの様々な機能で使用されます。
$0
: 現在のプログラムファイル名$~
: 最後にマッチした正規表現$&
: 最後にマッチした文字列$'
: 最後にマッチした文字列より後ろの文字列$'
: 最後にマッチした文字列より後ろの文字列$1
,$2
,$3
...: 最後にマッチした正規表現の1番目、2番目、3番目...のキャプチャグループにマッチした文字列$stdin
: 標準入力$stdout
: 標準出力$stderr
: 標準エラー出力$LOAD_PATH
: ライブラリの検索パス$:
: ライブラリの検索パス($LOAD_PATH
の別名)
puts "This program's name is #{$0}" puts "The Ruby version is #{RUBY_VERSION}" puts "The current line number is #{$.}"
オブジェクト
Rubyは完全なオブジェクト指向言語であり、すべてがオブジェクトです。オブジェクトは、データとそれに対する操作をカプセル化したものであり、オブジェクトを操作するためにメソッドを使用します。
以下は、オブジェクトについての基本的な説明です。
- オブジェクトは、Rubyの世界で唯一の存在であり、それぞれが固有の状態と振る舞いを持っています。
- オブジェクトは、何らかのデータ(文字列、数値、配列、ハッシュ、ファイルなど)を表します。例えば、整数、浮動小数点数、有理数や複素数などの数値を表すオブジェクトがあり、文字列オブジェクトはテキストを表します。
- オブジェクトは、そのデータを操作するためのメソッドを提供します。メソッドは、オブジェクトの状態を変更することができます。例えば、数値オブジェクトには、加算や減算などの算術演算を行うためのメソッドがあります(演算子もメソッドです)。文字列オブジェクトには、文字列の結合や部分文字列の取得などのメソッドがあります。
- オブジェクトは、クラスと呼ばれるテンプレートから作成されます。クラスは、オブジェクトのデータやメソッドを定義するための設計図です。オブジェクトは、クラスのインスタンスとして作成されます。
- オブジェクトは、他のオブジェクトとやり取りすることができます。オブジェクトは、別のオブジェクトをメソッドの引数として受け取ることができ、戻り値としても返すことができます。
これらのオブジェクトのクラスを知ることは、そのオブジェクトがどのような振る舞いをするかを理解するために非常に重要です。
Objectクラス
Rubyにおけるすべてのオブジェクトは、Object
から継承されています。Object
には、すべてのオブジェクトに共通するメソッドが定義されています。
以下は、Object
に関する基本的な説明です。
- Rubyのすべてのオブジェクトは、
Object
のサブクラスであるため、Object
に定義されているメソッドはすべてのオブジェクトで利用できます。 Object
には、例外を発生させるraise
メソッドや、オブジェクトのクラスを取得するclass
メソッド、オブジェクトが同一かどうかを判定するequal?
メソッドなど、多くの便利なメソッドが定義されています。Object
には、to_s
メソッドも定義されており、すべてのオブジェクトはto_s
メソッドを呼び出すことができます。to_s
メソッドは、オブジェクトを文字列に変換します。このメソッドは、デバッグやログ出力などに活用されます。Object
には、同一性比較に使用される==
メソッドも定義されています。==
メソッドは、2つのオブジェクトが同じかどうかを比較します。オブジェクトの同一性を比較する場合は、equal?
メソッドを使用します。Object
には、.initialize
メソッドも定義されており、新しいオブジェクトを作成する際に呼び出されます。.initialize
メソッドをオーバーライドすることで、新しいオブジェクトが初期化される際の処理をカスタマイズすることができます。
オブジェクトのクラスを調べる
オブジェクトのクラスを調べるには、Objectクラスのclass
メソッドを使います。
puts "Hello, world!".class # ==> String puts 10.class # ==> Integer puts 3.14.class # ==> Float puts [2, 3, 5, 7, 11].class # ==> Array puts ({"key1" => "value1", "key2" => "value2"}).class # ==> Hash puts /hello/.class # ==> Regexp puts (1..10).class # ==> Range puts :abc.class # ==> Symbol puts false.class # ==> FalseClass puts true.class # ==> TrueClass puts nil.class # ==> NilClass puts (lambda {|x, y| x + y }).class # ==> Proc puts Complex(3, 4).class # ==> Complex puts Rational(22, 7).class # ==> Rational puts Time.now.class # ==> Time puts Struct.new("Cat", :sex, :age).class # ==> Class
Object
クラスの代表的なメソッドメソッド名 引数の数 説明 !=
1 オブジェクトと引数が等しくない場合に true
、等しい場合にfalse
を返す!
0 オブジェクトが false
またはnil
の場合にtrue
を返す==
1 オブジェクトと引数が等しい場合に true
、等しくない場合にfalse
を返す===
1 引数がオブジェクトと等しい場合に true
、そうでない場合にfalse
を返す__id__
0 オブジェクトの object_id
を返す__send__
1以上 メソッドを動的に呼び出す class
0 オブジェクトのクラスを返す clone
0 オブジェクトを複製して返す dup
0 オブジェクトを浅く複製して返す equal?
1 オブジェクトと引数が同じオブジェクトである場合に true
、そうでない場合にfalse
を返すinstance_eval
0または1 レシーバーオブジェクトに対してブロックを評価する instance_of?
1 オブジェクトが指定したクラスのインスタンスである場合に true
、そうでない場合にfalse
を返すinstance_variable_defined?
1 指定されたインスタンス変数が定義されている場合に true
、そうでない場合にfalse
を返すinstance_variable_get
1 指定されたインスタンス変数の値を取得する instance_variable_set
2 指定されたインスタンス変数に値を設定する kind_of?
1 オブジェクトが指定したクラスのインスタンスである場合に true
、そうでない場合にfalse
を返すmethod
1 指定されたメソッドオブジェクトを返す nil?
0 オブジェクトが nil
式と演算子
Rubyにおける式と演算子について説明します。
式
Rubyにおける式とは、ある値を評価するためのコードのことを指します。例えば、以下のような式があります。
1 + 2
この式は、1
と2
を足した結果を評価するための式です。この式を実行すると、3
という値が返されます。
演算子
演算子は、式を組み合わせたり、値を変更するために使用されます。Rubyにはさまざまな種類の演算子があります。以下はいくつかの例です。
数値演算子
数値演算子には、加算、減算、乗算、除算、剰余演算子などがあります。
以下はいくつかの例です。
- operators.rb
# 整数 p 10 + 3 # 13: 加算 p 10 - 3 # 7: 減算 p 10 * 3 # 30: 乗算 p 10 / 3 # 3: 除算 p 10 % 3 # 1: 剰余演算 # 文字列 p "abc" + "xyz" # abcxyz: 連結 p "abc" * 3 # abcabcabc: 反復 p "%x" % 77 # 4d: 書式化 # 配列 p [1, 2, 3] + [7, 8, 9] # [1, 2, 3, 7, 8, 9]: 連結 # 有理数 p Rational(2, 3) + 3 # (11/3) p Rational(2, 3) - 3 # (-7/3) p Rational(2, 3) * 3 # (2/1) p Rational(2, 3) / 3 # (2/9) # 複素数 p (-2)**0.5 # (0.0+1.4142135623730951i) p Complex(2, 3) + 3 # (5+3i) p Complex(2, 3) - 3 # (-1+3i) p Complex(2, 3) * 3 # (6+9i) p Complex(2, 3) / 3 # ((2/3)+1i)
Rubyの四則演算子と剰余演算子は、通常の数値演算に使用されることが一般的ですが、オブジェクト指向プログラミング言語であるRubyでは、これらの演算子がオーバーロードされることがあります。
数値演算子の使用は、さまざまなデータ型に対して行われます。整数、文字列、配列、有理数、複素数など、Rubyは様々なデータ型をサポートしており、それぞれのデータ型に対して適切な演算が行われます。
例えば、文字列の場合、加算演算子は文字列の連結を行います。同様に、整数や有理数の場合は数値演算が行われますが、複素数の場合は複素数の演算が行われます。
また、Rubyでは四則演算子や剰余演算子などの演算子がオーバーロードされることがあります。これにより、ユーザー定義のクラスやオブジェクトに対して独自の演算を定義することができます。オブジェクト指向の特性を活かした柔軟な演算の定義が可能です。
数値演算子の適切な使用は、プログラムの正確性や効率性に直結します。Rubyの数値演算子を適切に理解し、適切に活用することで、より効率的で読みやすいコードを書くことができます。
比較演算子
比較演算子とComparableモジュールは、Rubyにおいてオブジェクトの比較を可能にする重要な機能です。
比較演算子には以下のようなものがあります:
==
: 等しい!=
: 等しくない>
: より大きい<
: より小さい>=
: 以上<=
: 以下
以下はいくつかの例です。
a = 1 b = 2 puts a == b # 等しい puts a != b # 等しくない puts a > b # より大きい puts a < b # より小さい puts a >= b # 以上 puts a <= b # 以下
これらの演算子を使って、オブジェクト同士を比較することができます。例えば、整数同士や文字列同士の比較などが挙げられます。
Comparableモジュール
Comparableモジュールは、比較可能なオブジェクトを定義するためのモジュールです。このモジュールをクラスにincludeすることで、そのクラスのインスタンス同士を比較可能にすることができます。
Comparableモジュールでは、<=>
演算子(スペース船演算子)を実装する必要があります。この演算子は、二つのオブジェクトを比較して以下のような値を返します:
- 自身が他のオブジェクトより小さい場合は負の整数
- 自身が他のオブジェクトと等しい場合はゼロ
- 自身が他のオブジェクトより大きい場合は正の整数
Comparableモジュールを使用することで、クラス内で<=>
演算子を定義するだけで、そのクラスのインスタンス同士を比較することができます。これにより、コードの可読性を高め、一貫した比較処理を実現することができます。
例えば、数値や文字列など、大小関係を持つオブジェクトを扱う場合に便利です。また、min
やmax
などのメソッドを利用して、比較可能なオブジェクトの最小値や最大値を簡単に取得することもできます。
class Person include Comparable attr_accessor :name, :age def initialize(name, age) @name = name @age = age end def <=>(other) @age <=> other.age end end p1 = Person.new("Alice", 25) p2 = Person.new("Bob", 30) puts p1 < p2 # true puts p1 == p2 # false puts p1 > p2 # false
Person
クラスでは、<=>
演算子を定義して、age
インスタンス変数を比較しています。そのため、Person
クラスのインスタンスは、<
, <=
, ==
, >
, >=
, between?
演算子によって比較可能になります。
論理演算子
論理演算子には、AND、OR、NOT演算子があります。以下はいくつかの例です。
a = true b = false puts a && b # false: AND puts a and b # true: AND(低い優先度なのでputsと先に結合) puts a || b # true: OR puts a or b # true: OR(低い優先度) puts !a # false: NOT
&&
の優先度は||
より強いです(論理積と論理和なので)。||
の優先度はand
より強いです(単語版の方が弱いとおぼえてください)。and
の優先度はor
より強いです(論理積と論理和なので)。
以下に、論理積 (AND
)、論理和 (OR
)、論理否定 (NOT
) を表現するための真理値表を示します。
A | B | A AND B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
A | B | A OR B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
A | NOT A |
---|---|
0 | 1 |
1 | 0 |
上記の表で、A と B はそれぞれ真 (1) または偽 (0) の値を表し、AND や OR などの論理演算子によって表された結果は、真 (1) または偽 (0) のいずれかです。
また、論理否定では、NOT A は A の真偽を反転させた値となります。
短絡評価
Rubyの短絡評価とは、論理式の評価において、最初の式で結果が確定した場合に、その後の式を評価しないことを指します。つまり、式が最初の部分で真偽値が確定した場合、後続の式を評価せずに即座に結果を返します。
例えば、以下のようなコードがあったとします。
def check_name(name = nil) if name && name.length > 0 puts "名前は#{name}です" else puts "名前が未入力です" end end check_name() # => "名前が未入力です" check_name("") # => "名前が未入力です" check_name("John") # => "名前はJohnです"
このコードでは、name
が nil
でないかつ文字列の長さが0より大きいかを同時にチェックしています。
具体的には、条件式 name && name.length > 0
によってチェックしています。
この条件式では、短絡評価 (short-circuit evaluation) が利用されています。短絡評価とは、条件式の全ての部分を評価せずに、最初に結果が確定した部分で評価を終了するという評価方法です。
具体的には、この場合はname
がnil
である場合、name.length > 0
の評価は行われず、条件式全体がfalse
と判定されます。
このような短絡評価の利用により、コードのパフォーマンスを向上させることができます。また、name
が nil
の場合に name.length
を呼び出すことがなくなるため、エラーを避けることができます。
しかし、短絡評価を乱用すると、プログラムの可読性が低下したり、バグを引き起こしたりすることがあります。適切な場面でのみ利用するようにしましょう。
- 論理演算子(AND, OR)は演算子の姿をしていますが、内実は制御構造です。そのため論理演算子は演算子オーバーロード出来ません。
Rubyでは演算子もメソッド
「Rubyでは演算子もメソッド」とは、Rubyにおいて演算子 (たとえば +
, -
, *
, /
など) が単なる記号ではなく、それぞれが対応するメソッドとして定義されているということを指します。
例えば、+
演算子は、2つの数値を足し合わせるために使われますが、実際には +
メソッドとして定義されています。以下のように +
メソッドを使って足し算を行うことができます。
a = 1 b = 2 c = a.+(b) # a + b と同じ puts c # => 3
また、*
演算子も同様に *
メソッドとして定義されています。以下のように、文字列に対して *
メソッドを使って、指定した回数だけ繰り返した新しい文字列を作ることができます。
str = "hello" new_str = str.*(3) # str * 3 と同じ puts new_str # => "hellohellohello"
このように、Rubyでは演算子も単なる記号ではなく、それぞれが対応するメソッドとして定義されているため、オブジェクト指向の考え方に基づいた柔軟なプログラミングが可能になっています。
Rubyで演算子メソッドをオーバーロード
Rubyでは、クラスやモジュールで演算子に対応するメソッドを定義することができます。このようにして定義された演算子メソッドをオーバーロードと呼びます。
以下は、Vector2
クラスで +
演算子に対応するメソッドを定義する例です。このメソッドでは、Vector2
クラスのインスタンス同士を足し合わせることができます。
class Vector2 attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def +(other) Vector2.new(@x + other.x, @y + other.y) end def to_s() "[#{@x}, #{@y}]" end end v1 = Vector2.new(1, 2) v2 = Vector2.new(3, 4) v3 = v1 + v2 # => Vector2オブジェクト puts v3 # => [4, 6]
このコードは、2次元ベクトルを表す Vector2
クラスを定義し、2つのベクトルを足し合わせるメソッドを実装しています。
まず、attr_accessor
によって、x
と y
のインスタンス変数に対する getter/setter メソッドが定義されています。これによって、外部から v1.x
のようにしてインスタンス変数にアクセスしたり、v1.x = 10
のようにしてインスタンス変数に値を代入したりできます。
次に、initialize
メソッドが定義されています。これは、new
メソッドが呼ばれた時に、引数として渡された x
と y
をインスタンス変数 @x
と @y
に代入するためのコンストラクタです。
+
メソッドは、引数として与えられた other
という別の Vector2
オブジェクトを足し合わせ、新しい Vector2
オブジェクトを作成して返します。これによって、v1 + v2
のようにして2つのベクトルを足し合わせることができます。
最後に、to_s
メソッドが定義されています。これは、オブジェクトを文字列に変換するためのメソッドで、ベクトルの座標を [x, y]
のような形式の文字列に変換して返します。
実際に、v1
と v2
を足し合わせた結果を v3
に代入し、puts v3
で v3
を表示しています。v3
の値は [4, 6]
となります。
このように演算子メソッドをオーバーロードすることで、プログラムの読みやすさや柔軟性を高めることができます。ただし、意図しない挙動を招くこともあるため、注意して使いましょう。
演算子オーバーロード
Rubyでは、演算子をオーバーロードすることが出来ます。 オーバーロード可能な演算子には、組み込みクラスの演算子も含まれます。
class String def /(s) = split(s) end ary = "The quick brown fox jumps over the lazy dog" / " " p ary # => ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"] str = ary * " ! " p str # => "The ! quick ! brown ! fox ! jumps ! over ! the ! lazy ! dog"
このコードは、Rubyの String
クラスに、新しいメソッド /
を追加しています。
/
メソッドは、文字列を引数 s
で分割するためのメソッドです。
実装は split
メソッドを呼び出すだけで、文字列オブジェクト自身を self
で表しています。
まず、/
メソッドによって、文字列 "The quick brown fox jumps over the lazy dog"
がスペース " "
で分割され、配列 ary
に格納されます。
ここで ary
は、["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
という値を持ちます。
次に、*
メソッドによって、配列 ary
が感嘆符 "!"
で連結され、文字列 str
が作成されます。
ここで str
は、"The ! quick ! brown ! fox ! jumps ! over ! the ! lazy ! dog"
という値を持ちます。
このように、/
と *
メソッドを利用することで、簡単に文字列の分割や連結を行うことができます。
ただし、他の開発者がこのようなメソッドを利用しているコードを読む場合、このような振る舞いを予想できない可能性があるため、注意が必要です。
/
と *
を、それぞれ文字列の分割と結合に割り当てる例は、Pikeに見ることが出来ます。
演算子の優先順位と結合方向
- 演算子の優先順位
演算子には優先順位があります。
例えば、以下のようなコードがあった場合、演算子の優先順位に従って評価されます。
a = 2 + 3 * 4 ** 2 / 2
ここで、**は先に評価されます。次に、*と/が同じ優先順位なので、左から右に評価されます。最後に+が-よりも優先度が高いので、+が評価されます。
a = 2 + ( ( 3 * ( 4 ** 2 ) ) / 2 )
つまり、aの値は50になります。
- 演算子の結合方向
同じ優先順位の演算子が続いた場合、演算子の結合方向により実行順序が決まります。
例えば、以下のコードがある場合、
a = 2 ** 3 ** 2
**
は右結合なので、3 ** 2
が先に評価されます。そして、2 ** 9
が評価されて、aは512になります。
a = 2 ** ( 3 ** 2 )
以下は、Rubyの演算子の優先順位と結合方向の表です。
Rubyの演算子の優先順位と結合方向 演算子 説明 優先順位 結合方向 :: クラスやオブジェクトのネスト 1 左結合 [] 配列やハッシュから要素を取得する 2 左結合 + 単項演算子(正) 3 右結合 ! 論理否定 ~ ビット否定 ** べき乗 4 右結合 - 単項演算子(負) 5 右結合 * 乗算 6 左結合 / 除算 % 剰余算 + 加算 7 左結合 - 減算 << ビットシフト(左) 8 左結合 >> ビットシフト(右) & ビット積 9 左結合 | ビット和 10 左結合 ^ ビット排他的論理和 > 大なり 11 左結合 >= 以上 < 未満 <= 以下 <=> 比較 12 左結合 == 一致 === 厳密に一致 != 不一致 =~ パターンマッチング !~ アンマッチ && 論理積 13 左結合 || 論理和 14 左結合 .. 範囲を生成する(終端を含む) 15 左結合 ... 範囲を生成する(終端を含まない) ? : 条件演算子 16 右結合 = 代入 17 右結合 op= 代入演算子 not 論理否定 18 右結合 and 論理積 19 左結合 or 論理和
- 優先順位が高い演算子から順に、結合方向の左右を記載しています。
- 表中の "op= " は、代入演算子の一般形を表しています。具体的な代入演算子には、"+= "、"-= "、"*= "、"/= "、"%= "、"||= "、"&&= "、"||= "、"|= "、"&= "、"^= "などがあります。
- コード例
a = 5 b = 10 puts "a = #{a}" puts "b = #{b}" puts "-" * 20 # 算術演算子 puts "算術演算子:" puts "a + b = #{a + b}" puts "a - b = #{a - b}" puts "a * b = #{a * b}" puts "b / a = #{b / a}" puts "b % a = #{b % a}" puts "a ** 2 = #{a ** 2}" puts "-" * 20 # 比較演算子 puts "比較演算子:" puts "a == b : #{a == b}" puts "a != b : #{a != b}" puts "a > b : #{a > b}" puts "a < b : #{a < b}" puts "b >= a : #{b >= a}" puts "a <= b : #{a <= b}" puts "-" * 20 # 論理演算子 puts "論理演算子:" puts "a == 5 && b == 10 : #{a == 5 && b == 10}" puts "a == 5 || b == 5 : #{a == 5 || b == 5}" puts "! (a == 5) : #{!(a == 5)}" puts "-" * 20 # 代入演算子 puts "代入演算子:" puts "a += b : #{a += b}" puts "a -= b : #{a -= b}" puts "a *= b : #{a *= b}" puts "a /= b : #{a /= b}" puts "a %= b : #{a %= b}" puts "a **= 2 : #{a **= 2}" puts "-" * 20 # 三項演算子 puts "三項演算子:" puts "a > b ? 'a is greater than b' : 'a is less than or equal to b'" puts "-" * 20 # ビット演算子 puts "ビット演算子:" puts "a & b : #{a & b}" puts "a | b : #{a | b}" puts "a ^ b : #{a ^ b}" puts "~a : #{~a}" puts "a << 1 : #{a << 1}" puts "a >> 1 : #{a >> 1}" puts "-" * 20 # 演算子メソッド puts "演算子メソッド:" puts "a == b : #{a.==(b)}" puts "a != b : #{a.!=(b)}" puts "a > b : #{a.>(b)}" puts "a < b : #{a.<(b)}" puts "b >= a : #{b.>=(a)}" puts "a <= b : #{a.<=(b)}" puts "a <=> b : #{a.<=>(b)}" puts "a === b : #{a.===(b)}" puts "a =~ b : #{a.=~(b)}" puts "-" * 20 # その他の演算子 puts "その他の演算子:" puts "defined? a : #{defined?(a)}" puts "(1..10).each do |i| puts i end" puts ":symbol.to_s : #{:symbol.to_s}" puts "%w(one two three) : #{%w(one two three)}" puts "-" * 20
- 実行結果
a = 5 b = 10 -------------------- 算術演算子: a + b = 15 a - b = -5 a * b = 50 b / a = 2 b % a = 0 a ** 2 = 25 -------------------- 比較演算子: a == b : false a != b : true a > b : false a < b : true b >= a : true a <= b : true -------------------- 論理演算子: a == 5 && b == 10 : true a == 5 || b == 5 : true ! (a == 5) : false -------------------- 代入演算子: a += b : 15 a -= b : 5 a *= b : 50 a /= b : 5 a %= b : 5 a **= 2 : 25 -------------------- 三項演算子: a > b ? 'a is greater than b' : 'a is less than or equal to b' -------------------- ビット演算子: a & b : 8 a | b : 27 a ^ b : 19 ~a : -26 a << 1 : 50 a >> 1 : 12 -------------------- 演算子メソッド: a == b : false a != b : true a > b : true a < b : false b >= a : false a <= b : false a <=> b : 1 a === b : false a =~ b : -------------------- その他の演算子: defined? a : local-variable (1..10).each do |i| puts i end :symbol.to_s : symbol %w(one two three) : ["one", "two", "three"] --------------------
制御構造
Rubyには、if-else-elsif-end、unless-else-end、case-when-else-end、case-in-else-end、while文、until文、for文、そしてbegin-rescue-else-ensure-endのような例外処理構文など、様々な制御構造があります。 これらの制御構造を使うことで、プログラムの実行を条件に応じたループや分岐によって制御することができます。 制御構造を正しく理解し、適切に使用することで、効率的で洗練されたプログラムを書くことができます。
また広義の制御構造にはブロックを伴う each や loop などのメソッドも含まれ、ここではそれらについても解説します。
- 書式概観
# if-elsif-else-end if 条件式1 then 処理1 elsif 条件式2 then 処理2 else 処理3 end # if修飾子 式 if 条件式 # unless-else-end unless 条件式 then 処理1 else 処理2 end # unless修飾子 式 unless 条件式 # case-when-else-end case 対象のオブジェクト when 条件式1 then 処理1 when 条件式2 then 処理2 when 条件式3 then 処理3 else 処理4 end # while文 while 条件式 do 処理 end # while修飾子 式 while 条件式 # until文 until 条件式 do 処理 end # until修飾子 式 until 条件式 # for文 for 変数 in 範囲 do 処理 end # loopメソッド loop do 処理 end # eachメソッド コレクション.each do |変数| 処理 end # timesメソッド 回数.times do |変数| 処理 end
条件分岐
Rubyの条件分岐には、if-elsif-else-end、unless-else-end、そしてcase-when-else-endがあります。 また、if, unless には修飾子構文もあります。
if-elsif-else-end
if-elsif-else-endは、条件に応じて処理を分岐するためのもっとも基本的な構文です。
if 条件式1 処理1 elsif 条件式2 処理2 else 処理3 end
例えば、ある数値変数numが0未満なら「negative」、0なら「zero」、0より大きいなら「positive」と表示するプログラムは以下のように書けます。
num = -3 if num < 0 puts "negative" elsif num == 0 puts "zero" else puts "positive" end
if修飾子
if修飾子は、条件式が肯定されたとき式を評価する構文です。
式 if 条件式
例えば、ある数値変数numが0未満なら「negative」と表示するプログラムは以下のように書けます。
num = -3 puts "negative" if num < 0
unless-else-end
unless-else-endは、if-elsif-else-endと似たような構文ですが、条件式が否定されたときに処理が実行されます。
unless 条件式 処理1 else 処理2 end
例えば、ある数値変数numが0以上なら「non-negative」、0未満なら「negative」と表示するプログラムは以下のように書けます。
num = -3 unless num >= 0 puts "negative" else puts "non-negative" end
unless修飾子
unless修飾子は、if修飾子に似ていますが、条件式が否定されたときに式を評価する構文です。
式 unless 条件式
例えば、ある数値変数numが0以上でなければ「negative」と表示するプログラムは以下のように書けます。
num = -3 puts "negative" unless num >= 0
case-when-else-end
case-when-else-endは、複数の値に応じて処理を分岐するための構文です。if-elsif-else-endと比較して、1つのオブジェクトに対して複数の条件式を一度に評価できるという利点があります。
case オブジェクト when 条件式1 処理1 when 条件式2 処理2 else 処理3 end
例えば、ある文字変数cに対して、それが母音なら「vowel」、子音なら「consonant」、数字なら「number」、それ以外なら「other」と表示するプログラムは以下のように書けます。
c = 'a' case c when 'a', 'e', 'i', 'o', 'u' puts "vowel" when '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' puts "number" when 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z' puts "consonant" else puts "other" end
when へのオブジェクト
Rubyの case
文では、比較に用いるオブジェクトの型に応じて条件分岐することができます。具体的には、case
文の式の型が以下のいずれかである場合に、対応する条件分岐が行われます。
- 整数型 (
Integer
) の場合 - 文字列型 (
String
) の場合 - 正規表現型 (
Regexp
) の場合 - クラスオブジェクト (
Class
) の場合
以下に、それぞれの場合の例を示します。
- 整数型の場合
case num when 0 puts "num is zero" when 1 puts "num is one" when 2 puts "num is two" else puts "num is something else" end
- 文字列型の場合
case str when "foo" puts "str is foo" when "bar" puts "str is bar" when /baz/ puts "str includes baz" else puts "str is something else" end
- 正規表現型の場合
case str when /foo/ puts "str includes foo" when /bar/ puts "str includes bar" when Regexp.new("baz") puts "str includes baz" else puts "str is something else" end
- クラスオブジェクトの場合
case obj when Integer puts "obj is an instance of Integer" when String puts "obj is an instance of String" when Array puts "obj is an instance of Array" else puts "obj is an instance of something else" end
これらの例では、case
文の式の型によって、比較に用いるオブジェクトの型や正規表現パターンが異なることに注意してください。また、どの条件にも一致しない場合に実行される else
節は省略可能です。
if
、unless
、case
は、制御フロー構文としての使い方に加えて、式としても使うことができます。
例えば、if
式を使ってみましょう。通常の if
は、条件が true
ならば処理を実行します。
if x > 10 puts "x is greater than 10" end
この if
文を式として使うこともできます。具体的には、以下のように if
の後ろに式を書き、式の値が true
であれば、if
の条件式の評価値が式の値となります。
a = if x > 10 "x is greater than 10" else "x is less than or equal to 10" end puts a
同様に、unless
も式として使うことができます。
a = unless x > 10 "x is less than or equal to 10" else "x is greater than 10" end puts a
また、case
式もあります。case
式は、複数の条件に対する分岐処理を行う場合に使われます。以下は、case
式の例です。
a = case x when 1 "one" when 2 "two" else "other" end puts a
case
式では、when
句を使って、複数の条件式を書くことができます。case
式の評価値は、when
句の条件式と一致する最初のものの右辺の式の値となります。パターンマッチング
Rubyのパターンマッチングは、バージョン2.7で導入された新機能であり、構造化された値に対してパターンをマッチングすることができます。これにより、より複雑なデータ構造を柔軟に操作できるようになります。
以下に、Rubyのパターンマッチングの基本的な構文と機能について説明します。
基本的な構文
case 式 in パターン1 # パターン1にマッチする場合の処理 in パターン2 # パターン2にマッチする場合の処理 else # どのパターンにもマッチしない場合の処理 end
パターン
パターンは以下のようなものがあります:
- 値パターン(Value pattern)
- 配列パターン(Array pattern)
- ハッシュパターン(Hash pattern)
- 検索パターン(Find pattern)
- 変数キャプチャ(Variable capture)
- その他のパターン(Alternative pattern)
変数バインディング
パターンマッチングによって、マッチした部分をローカル変数にバインドすることができます。
case [1, 2] in Integer => a, Integer puts "マッチしました: #{a}" else puts "マッチしませんでした" end
ガード節
if
を使用して、パターンがマッチした場合に追加の条件を指定することができます。
case [1, 2] in a, b if b == a * 2 puts "マッチしました" else puts "マッチしませんでした" end
パターンの合成
複数のパターンを組み合わせてマッチングを行うこともできます。
case [1, 2] in [1, Integer] puts "マッチしました" else puts "マッチしませんでした" end
例外の扱い
パターンマッチングが失敗した場合には、例外が発生します。これは、NoMatchingPatternError
として知られています。
メソッドの利用
カスタムクラスに対してパターンマッチングを行う場合、deconstruct
やdeconstruct_keys
メソッドを実装することでマッチングを行うことができます。
パターンマッチングの利点
- より読みやすいコードを書くことができます。
- より安全なコードを書くことができます。
- より柔軟なデータ操作が可能になります。
以上が、Rubyのパターンマッチングについての基本的な説明です。この機能は、プログラミングのパラダイムをより関数型プログラミング寄りのスタイルに変えることができます。
反復
Rubyの反復構文は、特定の条件が満たされるまでコードのブロックを繰り返すことができる文のセットです。最も一般的な反復文は、while、until、for、loop です。
- while文は、ある条件が真である限り、コードのブロックを実行します。
- until文は、ある条件が偽である限り、コードのブロックを実行します。
- for文は、要素のコレクションを繰り返し処理し、各要素に対してコードのブロックを実行します。
また、while, until には修飾子構文もあります。
これらの基本的な反復処理文に加えて、Ruby には break、next、redo、lazy などの反復処理文があります。これらの文により、コードの実行の流れを制御することができます。
while文
- 例
# このコードでは、1から10までの数字を表示します。 while i <= 10 do puts i i += 1 end
while文の構文は以下の通り:
- 構文
while 条件 do # 処理 end
条件とは、ループの実行を継続するためにtrueと評価されなければならないブール値の式です。 処理の部分は、条件が真である限って実行されるコードのブロックです。
まえの例では、条件は「i <= 10」です。これは、iの値が10以下である限り、ループが実行され続けることを意味します。 処理の部分は、puts i行です。この行はiの値をコンソールに出力します。
while文は、特定の条件が満たされる限り、コードのブロックを繰り返すことができる強力なツールです。
while修飾子
while修飾子は、if修飾子に似ていますが、条件式が肯定されている間は式を評価し続ける構文です。
式 while 条件式
例えば、ある数値変数numが0未満の間、num に 2 を足し続けるプログラムは以下のように書けます。
num = -3 num += 2 while num < 0
until文
- 例
# このコードでは、10から1までの数字を表示します。 i = 10 until i <= 1 do puts i i -= 1 end
until文の構文は以下の通り:
- 構文
until 条件 do # 処理 end
条件とは、ループの実行を継続するためにfalseと評価されなければならないブール値の式です。 処理の部分は、条件が偽である限って実行されるコードのブロックです。
まえの例では、条件はi <= 1である。これは、iの値が1以上である限り、ループが実行され続けることを意味します。 処理の部分は、puts i行です。この行はiの値をコンソールに出力します。
until文は、特定の条件が満たされない限り、コードのブロックを繰り返すために使用できる強力なツールです。
until修飾子
until修飾子は、while修飾子に似ていますが、条件式が否定されている間は式を評価し続ける構文です。
式 until 条件式
例えば、ある数値変数numが0以上でない間、num に 2 を足し続けるプログラムは以下のように書けます。
num = -3 num += 2 until num >= 0
for文
- 例
# このコードでは、配列の要素を順に表示します。 array = [2, 3, 5, 7, 11] for element in array puts element end puts element #=> 11 ## forループを抜けてもスコープは終わらない
for文の構文は以下の通り:
- 構文
for ループ変数 in コレクション # ループ変数にまつわる処理 end
ループ変数は、コレクション内の現在の要素のプレースホルダーです。 コレクションは、配列、範囲、その他の反復可能なオブジェクトのいずれでもかまいません。 ループ変数にまつわる処理の部分は、コレクション内の各要素に対して実行されるコードのブロックです。
この例では、コレクションは配列[2, 3, 5, 7, 11]です。ループ変数はelementです。 ループ変数にまつわる処理の部分は、puts element行です。この行は、elementの値をコンソールに表示します。
for文は、要素のコレクションを繰り返し処理するために使用できる強力なツールです。
反復制御文
Rubyには、反復制御を行う文が5つあります。
- break : break文は、現在のループを終了させる。
- next : 現在のループの繰り返しをスキップする。
- redo : 現在のループの繰り返しを再び実行する。
- retry: 例外処理をリトライする。
- return : メソッドを終了します。現在のループの繰り返しも終了します。
反復制御文は、Rubyのコードをより効率的に、より読みやすくするために使用できる強力なツールです。
break
break文は、現在のループを終了させます。
- 例
(1..).step(12) do |i| break if i >= 50 puts i end #=> 1 # 13 # 25 # 37 # 49
このコードは、1
から始まり、12
ずつ増加する無限の数列を生成し、その数列の各要素をdo..end
ブロック内で処理しています。各要素は変数i
に割り当てられ、puts
文を使って出力されます。
しかし、break if i >= 50
の部分によって、i
が50
以上になった場合、ループが終了します。つまり、ループは50
未満の値のときだけ実行されます。
このコードは、1, 13, 25, 37, 49
といった50
未満の数列を出力します。そして、i
が50
以上の値になった時点でループが終了します。
next
next文は、ループの現在の反復をスキップさせます。
- 例
1.upto(10) do |i| next if i == 5 puts i end #=> 1 # 2 # 3 # 4 # 6 # 7 # 8 # 9 # 10
このコードは、1
から10
までの数列を生成し、その数列の各要素をdo..end
ブロック内で処理しています。各要素は変数i
に割り当てられ、puts
文を使って出力されます。
next if i == 5
の部分によって、i
が5
の場合、それ以降の処理をスキップして次の要素の処理に進みます。つまり、5
は出力されません。
したがって、このコードは1, 2, 3, 4, 6, 7, 8, 9, 10
という数列を出力します。5
がスキップされ、それ以外の値が出力されます。
redo
redo文は、現在のループの繰り返しを再度実行します。
- 例
count = 0 (1..10).each do |i| count += 1 redo if i == 5 and count < 20 puts "#{count}: #{i}" end #=> 1: 1 # 2: 2 # 3: 3 # 4: 4 # 20: 5 # 21: 6 # 22: 7 # 23: 8 # 24: 9 # 25: 10
retry
例外が発生した場合に、その処理をやり直します。
begin # 何らかの処理 rescue SomeException retry end
return
メソッド内でループを含む場合、return
を使ってループからも抜けることができます。以下はその例です。
def example_method (1..10).each do |i| return if i == 5 puts i end end example_method
この場合、return
が呼び出されると、example_method
自体が終了し、その場でループも終了します。
これらのキーワードやメソッドを使うことで、ループの挙動を制御することができます。
反復メソッド
Rubyでは、反復処理を用いて演算を行うメソッドを反復処理メソッドと呼びます。 反復処理とは、コードのブロックを指定された回数だけ、あるいは特定の条件が満たされるまで繰り返す処理のことです。
コレクションのイテレーションメソッド
Rubyには、ArrayやHashなどのコレクションオブジェクトを簡単にイテレーションできる多数のメソッドがあります。以下はいくつかの例です:
- 例
# eachメソッド array = [1, 2, 3] array.each { |x| puts x } # mapメソッド array = [1, 2, 3] new_array = array.map { |x| x * 2 } # selectメソッド array = [1, 2, 3] new_array = array.select { |x| x.even? } # reduceメソッド array = [1, 2, 3] sum = array.reduce(0) { |total, x| total + x } # each_with_indexメソッド array = [1, 2, 3] array.each_with_index { |value, index| puts "#{index}: #{value}" }
上記の例では、以下のように動作しています。
- eachメソッド
- 配列の各要素に対して、ブロック内の処理を1回ずつ実行します。この例では、配列の各要素を出力しています。
- mapメソッド
- 配列の各要素に対して、ブロック内の処理を1回ずつ実行し、その結果を新しい配列に格納します。この例では、配列の各要素を2倍にして新しい配列を作成しています。
- selectメソッド
- 配列の各要素に対して、ブロック内の条件がtrueになる要素だけを抽出して、新しい配列に格納します。この例では、配列の偶数要素だけを抽出して新しい配列を作成しています。
- reduceメソッド
- 配列の各要素に対して、ブロック内の演算を順番に適用して、最終的な結果を返します。この例では、配列の各要素を加算して合計を求めています。
- each_with_indexメソッド
- 配列の各要素とそのインデックスに対して、ブロック内の処理を1回ずつ実行します。この例では、配列の各要素とそのインデックスを出力しています。
これらのメソッドを使用することで、コレクションの要素に簡単にアクセスできます。また、each_with_indexを使用することで、要素のインデックスにも簡単にアクセスできます。
暗黙のブロック引数
Rubyには、様々なメソッドやイテレータで使用される暗黙のブロック引数があります。これらは _1
, _2
などの形式で表され、ブロック内で引数を明示的に書く必要がなくなります。代表的なものを以下に示します。暗黙のブロック引数はRuby 2.7からサポートされました。
each
メソッドなどのイテレータでの使用:numbers = [1, 2, 3, 4, 5] numbers.each { |num| puts num } # 通常の引数を使用する場合 numbers.each { puts _1 } # `_1` を使用することで、暗黙の引数として要素を取得 hash = { a: 1, b: 2, c: 3 } hash.each { |key, value| puts "#{key}: #{vale}" } hash.each { puts "#{_1}: #{_2}" }
map
メソッドでの使用:doubled = numbers.map { |num| num * 2 } # 通常の引数を使用する場合 doubled = numbers.map { _1 * 2 } # `_1` を使用することで、暗黙の引数として要素を取得して計算
このように、暗黙のブロック引数は通常の引数の代替として使われ、ブロック内でのコードをより簡潔にすることができます。ただし、可読性の観点から、コードが複雑になりすぎないよう注意が必要です。
整数のイテレーションメソッド
Rubyには、数値を連続して扱うための便利なメソッドがあります。その中でもupto、downto、stepはよく使われるものです。これらのメソッドについて解説します。
- 例
# uptoの例 1.upto(5) do |i| puts i end # 実行結果: 1 2 3 4 5 # downtoの例 5.downto(1) do |i| puts i end # 実行結果: 5 4 3 2 1 # stepの例 1.step(10, 2) do |i| puts i end # 実行結果: 1 3 5 7 9
上記の例では、以下のように動作しています。
- uptoメソッド
- 整数オブジェクトから別の整数オブジェクトまで、1ずつ増加させながら、ブロック内の処理を繰り返します。この例では、1から5までの整数を順に出力しています。
- downtoメソッド
- 整数オブジェクトから別の整数オブジェクトまで、1ずつ減少させながら、ブロック内の処理を繰り返します。この例では、5から1までの整数を順に出力しています。
- stepメソッド
- 開始値から終了値まで、指定されたステップで数値を増加させながら、ブロック内の処理を繰り返します。この例では、1から10までの奇数を順に出力しています。stepメソッドで
は、第2引数に指定した数値が増加量として使用されます。この例では、2ずつ増加しているため、1, 3, 5, 7, 9という結果になっています。
loopメソッド
過去の編集で、loopをloop文と紹介されていましたが、loopはKernelオブジェクトのメソッドで構文ではありません。
loopメソッドの使い方は以下の通り:
loop do # 処理 end
処理の部分は、無限に実行されるコードのブロックです。
- 例
# このコードは、永久に "This will print forever!" を表示します。 loop do puts "This will print forever!" end
この例では、処理パートは puts "This will print forever!"
です。
この行は、"This will print forever!"というメッセージをコンソールに無限に出力します。
- loopとEnumrator
enum = [1, 2, 3].each puts enum.class #=> Enumerator loop do puts enum.next end #=> 1 # 2 # 3
enum
は[1, 2, 3].each
から生成されたEnumerator
です。Enumerator
は、each
メソッドをブロックなしで呼び出すことで生成され、配列内の各要素を反復処理することができます。
その後、loop
構文が使われています。loop
構文は、無限ループを作成するためのもので、ループ内のコードを繰り返し実行します。
ループ内では、enum.next
が呼び出されています。これにより、Enumerator
から次の要素が取得され、その要素がputs
メソッドを使って出力されます。最初は1が出力され、次に2、そして3が出力されます。
しかし、3を出力した後、Enumerator
からはもう要素がないため、StopIteration
例外が発生します。この例外がloop
構文によって自動的に捕捉され、ループが終了します。
反復メソッドと反復制御文
break、next、redo文もこれらのメソッドと一緒に使うことができます。
たとえば、次のようなものです:
- break : break 文は、ループの現在の繰り返しを終了させます。
- 例
array = [1, 2, 3] array.each do |element| puts element break if element == 2 end
このコードでは、配列の要素のうち、要素2までを表示します。
- next : next文は、ループの現在の繰り返しをスキップする。
- 例
array = [1, 2, 3] array.each do |element| puts element next if element == 2 end
このコードでは、配列の要素を、要素2をスキップして表示します。
- redo : redo文は、ループの現在の繰り返しを再度実行します。
- 例
array = [1, 2, 3] array.each do |element| puts element redo if element == 2 end
このコードは、配列の要素を表示しますが、要素2を無限に表示します。
イテレーションとブロック
Rubyのイテレーションメソッドは、繰り返し処理を行うためのメソッドで、通常はブロックを引数に取ります。ブロックは、繰り返し処理を行うためのコードブロックで、メソッドに渡されたオブジェクトの要素に対して、一つずつ実行されます。
イテレーションメソッドに渡されるブロックには、引数を指定することができます。引数は、ブロック内で処理する要素を表します。引数を指定する方法には、以下の2つがあります。
- パイプ(| |)で囲んだ引数を指定する方法
array = [1, 2, 3] array.each do |x| puts x end # 出力結果: 1, 2, 3
- ブロック引数を指定する方法
array = [1, 2, 3] array.each { puts _1 } # 出力結果: 1, 2, 3
このように、イテレーションメソッドは、ブロックを引数に取り、ブロック内で要素に対する処理を実行するため、とても柔軟性が高く、コードの再利用性を高めることができます。
通常は配列自身ですが、breakでループを中断すると nil を返します。 この特徴を利用すると、ZigやPythonのループと結合したelseの様に「ループを完走したときだけ実行する処理」を記述できます。
- 100以下の素数を求めるコード
primes = [] (2..100).each do |i| primes.push(i) if primes.each do |prime| break if i % prime == 0 end end p primes
- 実行結果:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
- 素数集合(primes)から1つ素数(prime)を取出し、候補(i)をそれで割り、割切れたら break します。
- break すると each は nil を返し、素数集合への候補の追加は行われません。
例外処理
Rubyにおける例外処理は、プログラムが想定外のエラーに遭遇した場合に、プログラムの強制終了を防ぐために使用されます。例外処理を使用すると、プログラムはエラーが発生しても継続して実行されるため、エラーに含まれる情報をログファイルに記録したり、エラーメッセージを表示したりすることができます。
以下は、Rubyで例外処理を行うための基本的な書式です。
begin # 例外が発生する可能性のある処理 rescue [エラークラス => 変数] # エラークラスが発生した場合に実行する処理 else # 例外が発生しなかった場合に実行する処理 ensure # 必ず実行される処理 end
例外処理の流れは、まずbeginブロック内で例外が発生する可能性のある処理を書きます。そして、rescueブロックで発生しうる例外の種類を指定し、例外が発生した場合に行う処理を書きます。elseブロックは、例外が発生しなかった場合に実行する処理を書く部分です。ensureブロックには、必ず実行される処理を書きます。
以下は、例外処理を用いたコードの例です。
def divide(a, b) begin result = a / b rescue ZeroDivisionError => e puts "Error: #{e.message}" result = nil else puts "Result is #{result}" ensure puts "Process finished" end return result end puts divide(10, 0) puts divide(10, 2)
この例では、divideメソッド内で引数bが0である場合にZeroDivisionErrorが発生する可能性があるため、beginブロック内で計算を行っています。また、rescueブロックではZeroDivisionErrorを補足して、エラーメッセージを表示しています。elseブロックでは、計算結果を表示しています。ensureブロックでは、処理が必ず実行されるように設定しています。
このコードを実行すると、以下のような結果が得られます。
Error: divided by 0 Process finished nil Result is 5 Process finished 5
修飾子
Rubyでは、制御構造の修飾子版も提供されています。これは一行で短く書くことができ、可読性を向上させる場合があります。
制御構造の修飾子版には、以下のものがあります:
if修飾子
puts "Hello" if flag
この場合、"Hello"はflagが真の場合にのみ出力されます。
unless修飾子
puts "Hello" unless cond
この場合、"Hello"はcondが偽の場合にのみ出力されます。
while修飾子
x = 0 puts x += 1 while x < 5
この場合、ループが続行される条件が満たされている間、xの値がインクリメントされ、その値が出力されます。
until修飾子
x = 0 puts x += 1 until x == 5
この場合、ループが続行される条件が満たされるまで、xの値がインクリメントされ、その値が出力されます。
rescue修飾子
Rubyのrescue
修飾子は、単一行の式での例外処理を行うために使用されます。通常のbegin..rescue..end
ブロックと異なり、修飾子版はコードを短く保ち、よりコンパクトに書くことができます。
rescue
修飾子の構文は以下の通りです:
式1 rescue 式2
式1が例外を発生させた場合、rescue
修飾子はその例外をキャッチし、代わりに式2を評価します。
以下はrescue
修飾子を使用した例です:
result = risky_operation() rescue default_value
この例では、risky_operation
というメソッドを呼び出しますが、もし例外が発生した場合には、デフォルト値を代入することでエラーハンドリングを行っています。
rescue
修飾子は、例外が発生する可能性のある単一の式を簡潔に記述する場合に便利ですが、複雑な例外処理を行う場合や、複数の式に対する例外処理を行う場合には、通常のbegin..rescue..end
ブロックを使用する方が適しています。
これらの修飾子版は、単純な条件の場合や短いコードブロックの場合に便利です。 ただし、複雑な制御構造や複数の条件がある場合は、通常の制御構造を使用した方が可読性が高くなります。
メソッド
Rubyのメソッドとは、ある一定の処理をまとめて名前をつけたもので、複数の場所で同じような処理を書く必要がある場合に便利です。
以下は、Rubyでメソッドを定義する方法の例です。
def メソッド名(仮引数) 処理 end
上記のコードでは、def
キーワードを使ってメソッドを定義します。メソッド名は自分で決定し、仮引数は必要に応じて任意の数を指定できます。また、end
キーワードでメソッドの終わりを示します。
- このように、トップレベルで定義されたメソッドは、スクリプト中どこならでも呼び出すことができ、関数と呼ばれことがあります。
以下は Ruby における関数定義における引数に関する機能の例です。
# 固定順位引数 def greet(name) puts "Hello, #{name}!" end greet("Alice") # => "Hello, Alice!" greet("Bob") # => "Hello, Bob!" # 固定順位引数のデフォルト値 def greet_with_default(name="world") puts "Hello, #{name}!" end greet_with_default # => "Hello, world!" greet_with_default("Alice") # => "Hello, Alice!" # キーワード引数 def greet_with_keyword(name:) puts "Hello, #{name}!" end greet_with_keyword(name: "Alice") # => "Hello, Alice!" greet_with_keyword(name: "Bob") # => "Hello, Bob!" # キーワード引数のデフォルト値 def greet_with_keyword_default(name: "world") puts "Hello, #{name}!" end greet_with_keyword_default # => "Hello, world!" greet_with_keyword_default(name: "Alice") # => "Hello, Alice!" # ブロック引数 def greet_with_block(name) puts "Hello, #{name}!" yield end greet_with_block("Alice") { puts "Glad to see you!" } # => "Hello, Alice!" # => "Glad to see you!" # 残余引数 def greet_with_remainder(*names) names.each { |name| puts "Hello, #{name}!" } end greet_with_remainder("Alice", "Bob", "Charlie") # => "Hello, Alice!" # => "Hello, Bob!" # => "Hello, Charlie!"
greet 関数は固定順位引数を使用しています。greet_with_default 関数では、name のデフォルト引数が指定されています。greet_with_keyword 関数では、キーワード引数を使用しています。greet_with_keyword_default 関数では、キーワード引数のデフォルト引数が指定されています。greet_with_block 関数では、ブロック引数を使用しています。最後に、greet_with_remainder 関数は残余引数を使用しています。
これらの機能を駆使することで、より柔軟かつ読みやすいコードを作成することができます。
固定順位引数
例えば、以下のようなメソッドを定義してみましょう。
def say_hello(name) puts "Hello, #{name}!" end
このメソッドは、引数に渡した名前に対して「Hello, 〇〇!」というメッセージを出力します。引数の「name」には、任意の名前を指定することができます。
このメソッドを呼び出すためには、以下のように書きます。
say_hello("John") #=> Hello, John!
引数に指定した名前が、「name」の部分に代入され、puts
メソッドによってメッセージが出力されます。
固定順位引数のディフォルト値
また、Rubyには引数を指定しないデフォルト値を設定する方法もあります。以下は、デフォルト値を設定したメソッドの例です。
def say_hello(name = "World") puts "Hello, #{name}!" end
この場合、引数を指定しなかった場合は「World」という名前が、引数を指定した場合は指定した名前が代入されます。例えば、以下のように呼び出します。
say_hello #=> Hello, World! say_hello("Mary") #=> Hello, Mary!
キーワード引数
キーワード引数では、引数名を指定することができます。これにより、引数の並び順を気にすることなく、任意の引数を指定することができます。
def person(name:, age:) puts "名前は#{name}、年齢は#{age}才です。" end person(name: "山田太郎", age: 30) #=> 名前は山田太郎、年齢は30才です。
キワード引数の実引数でに順序は任意です。
キーワード引数のディフォルト値
キーワード引数もディフォルト値を設定することができます。引数を指定しなかった場合は、初期値が使われるようになります。
def person(name: "名無し", age: 0) puts "名前は#{name}、年齢は#{age}才です。" end person(name: "山田太郎") #=> 名前は山田太郎、年齢は0才です。
ブロック引数
ブロック引数とは、メソッドの引数としてブロックを受け取るための仕組みです。 ブロックを受け取るメソッドの引数リストの最後に、"&ブロック名"という形式の引数を指定することで、ブロックをメソッド内で扱うことができます。
以下に例を示します。
def my_method(&block) block.call end my_method do puts "Hello, World!" end # => "Hello, World!"
上記の例では、my_method
メソッドにブロックを渡しています。my_method
メソッド内では、ブロックを扱うための引数として"&block"
を指定しており、ブロックを呼び出すために"block.call"を用いています。
yield
yield
は、メソッドに渡されたブロックを実行するためのキーワードです。yield
を使用することで、メソッド内でブロックを実行することができます。
以下に例を示します。
def my_method yield end my_method do puts "Hello, World!" end # => "Hello, World!"
上記の例では、my_method
メソッド内でyield
を用いてブロックを実行しています。my_method
メソッドが呼び出されると、引数として渡されたブロックが実行されます。
ブロック呼び出しへの引数
ブロック引数の呼び出しやyield
は引数を渡すこともできます。
ブロック引数の呼び出しへの引数
def my_method(name, &block) block.call(name) end my_method("John") do |name| puts "Hello, #{name}!" end # => "Hello, John!"
上記の例では、my_method
メソッドにブロックを渡し、ブロック引数に"name"
を指定しています。
ブロック内で引数を使用するために、block.callで"name"
を渡しています。
ブロック内では、引数として渡された"name"
を使用して文字列を出力しています。
Procクラスは[]
をオーバーロードしており。
def my_method(name, &block) block[name] end
と書くことも出来ます。
yield への引数
def my_method(name) yield(name) end my_method("John") do |name| puts "Hello, #{name}!" end # => "Hello, John!"
上記の例では、my_method
メソッドに引数として"name"
を渡し、yield
に"name"
を引数として渡しています。
ブロック内では、引数として渡された"name"
を使用して文字列を出力しています。
ブロック引数と yield どちらを使うべきか?
再帰を行う場合、通常はブロック引数を使用する方が適切です。再帰的な関数では、ブロック引数を使用することで、再帰呼び出しのたびに異なるブロックを渡すことができます。これにより、再帰呼び出しのたびに異なる処理を行うことが可能になります。
例えば、再帰的なメソッドを定義する際には、ブロック引数を使用して次の再帰呼び出し時に実行される処理を指定します。
def recursive_method(n, &block) if n > 0 # ブロックを実行 block[n] # 再帰呼び出し recursive_method(n - 1, &block) end end recursive_method(5) { |num| puts num }
このように、ブロック引数を使うことで、再帰呼び出し時に実行される処理を柔軟に指定することができます。これにより、再帰メソッドがより汎用的で柔軟なものになります。
ブロック引数の有無
Rubyでは、メソッド内でブロックが渡されたかどうかを判定する方法があります。これを行うには、block_given?
メソッドを使用します。このメソッドは、メソッド内でブロックが渡された場合にtrue
を返し、それ以外の場合にfalse
を返します。
以下は、block_given?
メソッドを使用してブロックが渡されたかどうかを判定する例です。
def method_with_block if block_given? puts "ブロックが渡されました" # yield が使えます else puts "ブロックが渡されていません" # yield は使えません end end method_with_block { puts "Hello, block!" } # => ブロックが渡されました method_with_block # => ブロックが渡されていません
このように、block_given?
メソッドを使うことで、メソッド内でブロックが渡されたかどうかを簡単に判定することができます。
ブロック引数を省略した時の戻り値
ブロック引数を受け取るメソッドの呼び出しで、ブロック引数を省略した場合の戻り値は、Enumerator
オブジェクトとする慣習があります。
例えば、配列をeach
メソッドで呼び出した場合、ブロックが省略されるとEnumerator
オブジェクトが返ります。
array = [2, 3, 5, 7] enum = array.each puts enum.class # => Enumerator begin puts enum.next while true rescue StopIteration => e puts e.inspect end # => 2 # 3 # 5 # 7 # #<StopIteration: iteration reached an end> enum2 = 10.upto(15) loop do puts enum2.next end # => 10 # 11 # 12 # 13 # 14 # 15 # 一見無限ループになりそうですが # loop は StopIteration をキャッチすると静かに繰り返しを終了します。
このように、each
メソッドがブロック引数を省略された場合、戻り値はイテレータとして機能するEnumerator
オブジェクトとなります。このオブジェクトは、コレクションの各要素を順番に処理するために使用されます。
- 典型的なブロック引数処理
def inorder_traversal(node = @root, &block) return to_enum(__method__, node) unless block def core(node, &block) return if node.nil? core(node.left, &block) yield node.value core(node.right, &block) end core(node, &block) self end
残余引数
Rubyにおける「残余引数(rest argument)」とは、メソッド定義の中で最後の引数として指定され、そのメソッドに渡された全ての追加の引数を1つの配列として受け取る機能です。残余引数は、メソッドが可変長の引数を受け取ることを可能にします。
残余引数は、メソッド定義の引数の前にアスタリスク * を付けて指定されます。これにより、メソッドが受け取る引数の数を動的に決定することができます。
以下は、残余引数を使用する例です。
- rest-args.rb:
def greet(greeting, *names) names.each { |name| puts "#{greeting}, #{name}!" } end greet("Hello", "Alice", "Bob", "Charlie") # 出力: # Hello, Alice! # Hello, Bob! # Hello, Charlie!
この例では、greet メソッドは greeting という通常の引数と、*names という残余引数を持っています。greet メソッドは最初の引数を挨拶の文字列として受け取り、残りの引数を配列 names として受け取ります。その後、names 配列内の各要素に対して、挨拶と名前を組み合わせたメッセージを出力します。
残余引数を使用することで、メソッドが任意の数の引数を受け取る柔軟性が向上し、可読性の高いコードを記述することができます。
戻り値
メソッド内で最後に評価される値が、そのメソッドの戻り値になります。例えば、次のようなメソッドを考えてみましょう。
def add(a, b) return a + b end
このメソッドは2つの引数を受け取り、それらの和を返します。return
キーワードを使用して、明示的に戻り値を指定しています。このように return
を使わずに値を返すと、メソッドの最後に評価された値が自動的に戻り値として返されます。
def add(a, b) a + b end
このように、明示的に return
を書かなくても、メソッド内で最後に評価された値が自動的に戻り値となります。
さらに、1つの式からなる関数は =
を使い end
を伴わない構文で定義できます。
def add(a, b) = a + b
メソッドを呼び出す際には、その戻り値を変数に代入したり、別のメソッドの引数として渡したりすることができます。 戻り値が必要な場合や、逆に必要ではない場合など、メソッドの設計時にはしっかりと考慮して実装する必要があります。
スコープ
Rubyには4つのスコープがあります。以下にそれぞれのスコープとコード例を示します。
- ローカルスコープ
- 変数が定義されたブロック内でしか使用できません。
def local_scope x = 10 puts x # output: 10 end puts x # undefined local variable or method `x' for main:Object (NameError)
- インスタンススコープ
- インスタンス変数は同じインスタンスの中であればどこでも使用できます。
class Person def initialize(name) @name = name end def greet puts "Hello, #{@name}!" end end person = Person.new("Alice") person.greet # Hello, Alice! puts @name # undefined method `name' for main:Object (NameError)
- クラススコープ
- クラス変数はそのクラス及びサブクラスのどこからでも使用できます。
class Counter @@count = 0 def initialize @@count += 1 end def self.count @@count end end counter1 = Counter.new counter2 = Counter.new puts Counter.count # 2
- グローバルスコープ
- グローバル変数はどこからでも使用できます。ただし、他のスコープで同じ名前の変数が定義されている場合はその変数が使用されます。
$global_var = "I'm global" def check_global_var puts $global_var end check_global_var # I'm global def set_global_var $global_var = "I'm changed globally" end set_global_var check_global_var # I'm changed globally
以上がRubyの4つのスコープです。
p(
と )
は、曖昧さのない場合は省略可能です。
上の例は、puts(s)
と書く代わりに、put s
と書く事ができます。
- コード
s = 'Hello World!' puts s
- 実行結果
Hello World!
再帰的呼び出し
Rubyでは、メソッド内で再帰を行うことができます。以下は、再帰を行うシンプルな例です。
def countdown(num) if num > 0 puts num countdown(num - 1) else puts "Blast off!" end end countdown(5)
このコードでは、countdown
メソッドが自分自身を呼び出して、カウントダウンを実行しています。呼び出し結果は以下の通りです。
5 4 3 2 1 Blast off!
このように、再帰を使うと、同じ処理を繰り返し実行することができます。
クロージャー
Rubyにはクロージャーという概念があります。クロージャーとは、変数とブロックを組み合わせたもので、後からブロック内で変数が参照された場合でも、当時の変数の値を保持している特殊なオブジェクトです。
以下は、クロージャーを使った例です。
def greeting_proc(name) Proc.new { puts "Hello, #{name}!" } end hello = greeting_proc("Alice") hello.call # => "Hello, Alice!" changing_name = "Bob" hello.call # => "Hello, Alice!" (value of name is retained)
上記の例では、greeting_proc
メソッドがブロックを返し、そのブロックがhello
変数に代入されます。このブロック内で参照される変数name
の値が保持され、hello.call
を複数回実行しても、最初に指定した名前が表示されます。
また、changing_name
という変数を新しく定義しても、クロージャー内で参照されるname
の値には影響がなく、hello.call
を実行すると、"Hello, Alice!"
が表示されます。
クラス
Rubyにおけるクラスは、オブジェクト指向プログラミングにおいて重要な役割を持ちます。クラスは、同じ構造や属性を持つオブジェクトの集合を定義するもので、それぞれのオブジェクトはクラスのインスタンスであると考えることができます。
クラス定義とインスタンスの生成とメソッドの呼び出し
# クラス定義 class MyClass # クラス変数 @@class_variable = "Class Variable" # インスタンス変数 attr_accessor :instance_variable # コンストラクター def initialize(arg) @instance_variable = arg end # クラスメソッド def self.class_method puts @@class_variable end # インスタンスメソッド def instance_method puts @instance_variable end end # クラスの利用 my_object = MyClass.new("Instance Variable") MyClass.class_method #=> "Class Variable" my_object.instance_method #=> "Instance Variable" my_object.instance_variable = "New Instance Variable" my_object.instance_method #=> "New Instance Variable"
クラス定義はclass
キーワードを使い、インスタンス変数を作成するためにattr_accessor
を使っています。コンストラクターはinitialize
メソッドを使い、引数を受け取ってインスタンス変数を初期化します。
クラスメソッド、インスタンスメソッドはそれぞれdef self.method_name
、def method_name
の形式で定義されます。また、クラス変数は@@
で始められ、インスタンス変数は@
で始められます。
このように、Rubyのクラスを使用することで、簡単にオブジェクト指向プログラミングを実現することができます。
アクセス修飾子
Rubyには3種類のアクセス修飾子があります。
- public
- どこからでもアクセス可能なメソッドや変数を定義するときに使用します。
- クラスの外部からもアクセス可能です。
- protected
- 自クラスまたはそのサブクラスであればアクセス可能なメソッドや変数を定義するときに使用します。
- クラスの外部からはアクセスできません。
- private
- 同じオブジェクト内でしかアクセスできないメソッドや変数を定義するときに使用します。
- クラスの外部からはアクセスできません。
以下は、それぞれのアクセス修飾子を使用した例です。
class Animal def swim # public puts "I can swim!" end protected def run # protected puts "I can run!" end private def fly # private puts "I can fly!" end end class Cat < Animal def can_run run # protectedなのでAnimalクラスのサブクラス内から呼び出し可能 end def can_fly fly # privateなので呼び出し不可能 end end cat = Cat.new cat.swim # publicなので呼び出し可能 cat.can_run # protectedなので呼び出し可能 cat.can_fly # privateなので呼び出し不可能
上記の例では、Animal
クラスに swim
、run
、fly
メソッドを定義しています。 swim
メソッドは public
、run
メソッドは protected
、fly
メソッドは private
となっています。
また、Cat
クラスを Animal
クラスのサブクラスとして定義し、can_run
メソッドで、Animal
クラスで protected
として定義した run
メソッドを呼び出しています。一方で、can_fly
メソッドで private
として定義した fly
メソッドを呼び出しているため、エラーが発生しています。
クラスの継承
# 親クラス class Animal def speak puts "動物が鳴いています" end end # 子クラス class Dog < Animal def speak puts "わんわん" end end # 子クラス class Cat < Animal def speak puts "にゃーにゃー" end end # 子クラスのインスタンス化 dog = Dog.new cat = Cat.new # 子クラスのメソッドを呼び出し dog.speak #=> わんわん cat.speak #=> にゃーにゃー # 親クラスのメソッドも呼び出し可能 dog.superclass #=> Animal dog.superclass.superclass #=> Object(親クラスの親クラス)
この例では、Animal
という親クラスを作成し、 Dog
と Cat
という子クラスを作成しています。 Dog
と Cat
はそれぞれ speak
メソッドを持っており、それぞれ異なる出力をします。
Dog
と Cat
は Animal
クラスを継承しています。このため、 Dog
と Cat
も Animal
クラスの speak
メソッドを呼び出すことができます。
演算子オーバーロード
以下はRubyにおける演算子オーバーロードの例です。
class Person attr_accessor :name, :age def initialize(name, age) @name = name @age = age end def +(other) Person.new("#{self.name} #{other.name}", self.age + other.age) end def ==(other) self.name == other.name && self.age == other.age end end person1 = Person.new("John", 30) person2 = Person.new("Doe", 40) # カスタム演算子 '+' の利用 person3 = person1 + person2 puts person3.name #=> John Doe puts person3.age #=> 70 # カスタム演算子 '==' の利用 puts person1 == person3 #=> false person4 = Person.new("John Doe", 70) puts person3 == person4 #=> true
- この例では、Personクラスで+演算子と==演算子をオーバーロードしています。
- +演算子は2つのPersonオブジェクトを受け取り、新しいPersonオブジェクトを返します。
- ==演算子は2つのPersonオブジェクトを比較し、nameプロパティとageプロパティが両方とも同じ場合にtrueを返します。
クラスへの動的なメソッドの追加
class MyClass def initialize(name) @name = name end end my_object = MyClass.new("Bob") class MyClass def greet() puts "Hello, #{@name}!" end end my_object.greet #=> "Hello, Bob!"
- 最初に、「MyClass」というクラスを定義します。
- 次に、MyClassのインスタンスを生成し変数my_objectに代入しています。
- その後、件のMyClassに新しいメソッドgreet()を定義しています。
- 最後の行で、「my_object.greet」というコードが実行され、オブジェクトの「greet」メソッドが呼び出されます。これにより、「Hello, Bob!」という文字列が出力されます。
注目すべきは:
- クラス定義の後でもメソッドの追加はできる
- インスタンス化とメソッドの追加定義が前後しても、メソッド呼び出しは可能である
の2点です。
特異メソッド
Rubyでは、オブジェクトに固有のメソッドを定義することができます。これを特異メソッドと呼びます。通常、メソッドはクラス定義内で定義されますが、特異メソッドは特定のオブジェクトに対して定義されます。
特異メソッドの定義
特異メソッドを定義するには、以下のように書きます。
オブジェクト名.define_singleton_method(シンボル) do |引数| # メソッドの中身 end
例えば、以下のように書くことで、特定のオブジェクトにhelloメソッドを定義することができます。
str = "Hello, world!" str.define_singleton_method(:hello) do puts "Hello!" end
これで、strオブジェクトにhelloメソッドが定義されました。呼び出すには以下のようにします。
str.hello #=> "Hello!"
特異クラスと特異メソッド
Rubyでは、オブジェクトに対して特異クラスというものが存在します。これは、そのオブジェクトだけが持つクラスのことです。この特異クラスに対して特異メソッドを定義することで、そのオブジェクトだけにメソッドを追加できます。
特異クラスは、以下のように記述します。
class << オブジェクト名 # メソッドの定義 end
例えば、以下のように記述することで、上記のstrオブジェクトに対してメソッドを定義することができます。
class << str def goodbye puts "Goodbye!" end end
これで、strオブジェクトにgoodbyeメソッドが定義されました。呼び出すには以下のようにします。
str.goodbye #=> "Goodbye!"
まとめ
以上が、Rubyの特異メソッドについてのチュートリアルです。オブジェクトに対して固有のメソッドを定義することで、便利なプログラミングをすることができます。
クラス定義の例
都市間の大圏距離
Go/メソッドとインターフェースの都市間の大圏距離を求めるメソッドを追加した例を、Rubyに移植しました。
- 都市間の大圏距離
class GeoCoord attr_accessor :longitude, :latitude def initialize(longitude, latitude) @longitude, @latitude = longitude, latitude end def to_s() ew, ns = "東経", "北緯" long, lat = @longitude, @latitude ew, long = "西経", -long if long < 0.0 ns, lat = "南緯", -lat if lat < 0.0 "(#{ew}: #{long}, #{ns}: #{lat})" end def distance(other) i, r = Math::PI / 180, 6371.008 Math.acos(Math.sin(@latitude * i) * Math.sin(other.latitude * i) + Math.cos(@latitude * i) * Math.cos(other.latitude * i) * Math.cos(@longitude * i - other.longitude * i)) * r end end def GeoCoord(longitude, latitude) GeoCoord.new(longitude, latitude) end Sites = { "東京駅": GeoCoord(139.7673068, 35.6809591), "シドニー・オペラハウス": GeoCoord(151.215278, -33.856778), "グリニッジ天文台": GeoCoord(-0.0014, 51.4778), } Sites.each{|name, gc| puts "#{name}: #{gc}" } keys, len = Sites.keys, Sites.size keys.each_with_index{|x, i| y = keys[(i + 1) % len] puts "#{x} - #{y}: #{Sites[x].distance(Sites[y])} [km]" }
- 実行結果
東京駅: (東経: 139.7673068, 北緯: 35.6809591) シドニー・オペラハウス: (東経: 151.215278, 南緯: 33.856778) グリニッジ天文台: (西経: 0.0014, 北緯: 51.4778) 東京駅 - シドニー・オペラハウス: 7823.269299386704 [km] シドニー・オペラハウス - グリニッジ天文台: 16987.2708377249 [km] グリニッジ天文台 - 東京駅: 9560.546566490015 [km]
このコードは、GeoCoord
というクラスを定義し、そこに緯度と経度を持つ「地球上の位置」を表現しています。それを利用して、Sites
というディクショナリに都市名と位置を紐付けています。
このコードのメインの処理は、各都市に対して、その距離を計算して出力しています。すなわち、都市の位置情報から、地球上での距離を求めることができます。
この「地球上の距離を求める」処理は、distance
メソッドで定義されており、球面三角法を使って計算されています。具体的には、2点の緯度経度から、その2点間の地表面上の距離を求める数式を用いています。
また、都市名と位置情報を紐付けたSites
に対しては、各都市の位置を出力しています。それぞれの都市の位置は、東経か西経か、北緯か南緯かで表され、丸括弧でくくられ、「東京駅」の場合は (東経: 139.7673068, 北緯: 35.6809591)
という文字列が出力されます。
包含と継承
JavaScript/クラス#包含と継承を、Rubyに移植しました。
- 包含と継承
class Point def initialize(x = 0, y = 0) @x, @y = x, y end def inspect() = "x:#{@x}, y:#{@y}" def move(dx = 0, dy = 0) @x, @y = @x + dx, @y + dy self end end class Shape def initialize(x = 0, y = 0) @location = Point.new(x, y) end def inspect() = @location.inspect() def move(x, y) @location.move(x, y) self end end class Rectangle < Shape def initialize(x = 0, y = 0, width = 0, height = 0) super(x, y) @width, @height = width, height end def inspect() = "#{super()}, width:#{@width}, height:#{@height}" end rct = Rectangle.new(12, 32, 100, 50) p ["rct=", rct] p ['rct.instance_of?( Rectangle ) => ', rct.instance_of?(Rectangle)] p ['rct.instance_of?( Shape ) => ', rct.instance_of?(Shape)] p ['rct.instance_of?( Point ) => ', rct.instance_of?(Point)] rct.move(11, 21) p ["rct=", rct]
- 実行結果
["rct=", x:12, y:32, width:100, height:50] ["rct.instance_of?( Rectangle ) => ", true] ["rct.instance_of?( Shape ) => ", false] ["rct.instance_of?( Point ) => ", false] ["rct=", x:23, y:53, width:100, height:50]
このコードは、Rubyでオブジェクト指向プログラミングを用いた簡単な図形クラスの例です。
Point
クラスは、座標を表現するためのクラスであり、x
とy
の2つのインスタンス変数を持っています。 コンストラクタで、x
とy
の初期値が設定できます。inspect
メソッドは、オブジェクトを文字列に変換するために使用されます。move
メソッドにより、(dx, dy)だけ点を移動することができます。
Shape
クラスは、点の座標を持つ図形を表すためのクラスであり、initialize
メソッド内で、Point
クラスのオブジェクトを作成して、点の座標を設定します。inspect
メソッドは、点の座標を返します。 move
メソッドによって、図形を(dx, dy)だけ移動することができます。
Rectangle
クラスは、Shape
クラスを継承しています。コンストラクタで、左上の点の座標と幅と高さを設定できます。inspect
メソッドは、左上の点の座標と、幅と高さを表現する文字列を返します。
最後の行では、Rectangle
クラスのインスタンスrct
を作成し、その後、rct
がRectangle
クラス、Shape
クラス、Point
クラスのどれに対してインスタンスかを調べるようにしています。また、rct
の位置を(11, 21)だけ移動させます。
高階関数
Rubyの高階関数とは、関数を引数や戻り値として扱うことができる関数のことを指します。これにより、コードの再利用性が向上し、より柔軟なプログラミングが可能になります。
例えば、以下のような高階関数があります。
def manipulate_array(arr, func) arr.map(&func) end
この関数は、配列と関数を受け取り、配列の各要素に対してその関数を適用した新しい配列を返します。このように、関数自体を引数として渡せることで、どのような処理でも適用することができます。
また、RubyのProcオブジェクトを使うことで、無名関数を定義して高階関数に渡すこともできます。例えば、以下のようなコードです。
add_num = Proc.new {|x| x + 1 } puts manipulate_array([1, 2, 3], add_num) # => [2, 3, 4]
このように、Procオブジェクトを定義して関数に渡すことで、より柔軟なプログラミングが可能になります。
lambda
Rubyのlambdaとは、無名関数(関数を名前を付けずに定義すること)の一種であり、関数をオブジェクトとして扱うことができます。
以下は、lambda式の基本構文です。
lambda { |引数| 処理 }
lambda
には引数を渡すことができ、渡された引数を処理に渡すことができます。また、処理の中で return
を使うことができ、lambda
の戻り値として処理結果を返すことができます。以下が、lambda
を利用した簡単な例です。
# lambda式を変数に代入する add = lambda { |x, y| x + y } # lambda式の引数の数 puts add.arity #=> 2 # lambda式を直接呼び出す puts lambda { |x, y| x + y }.call(2, 3) #=> 5 # lambda式を変数に代入したものを呼び出す puts add.call(2, 3) #=> 5 # ブラケット記法による呼び出し puts add[2, 3] #=> 5
Ruby 1.9 からは
-> (引数) { 処理 }
のような構文が追加され
# lambda式を変数に代入する add = -> (x, y) { x + y } # lambda式の引数の数 puts add.arity #=> 2 # lambda式を直接呼び出す puts -> (x, y) { x + y }[2, 3] #=> 5
とも書けるようになりました。
以上が、Rubyのlambda
の基本的な使い方です。
Procとlambdaの違い
RubyのProcとlambdaは、両方とも無名関数を定義するための方法ですが、いくつかの違いがあります。
引数の扱い方 Procの場合、渡された引数の数が関係なく、無視されます。一方、lambdaの場合は、正確な数の引数が期待されます。引数が足りない場合はエラーが発生します。
return文の扱い方 Procの場合、return文は元の呼び出し元に戻りますが、そのブロック内の後続のコードも実行されます。lambdaの場合、return文はただちにブロックから返され、コードの後続部分は実行されません。
以下は、Procとlambdaの振る舞いの違いを示す例です。
def proc_test p = Proc.new { return "Proc" } p.call return "proc_test method" end def lambda_test l = lambda { return "Lambda" } l.call return "lambda_test method" end puts proc_test # 出力結果: Proc puts lambda_test # 出力結果: lambda_test method
この例では、Procはブロック内でreturn文を呼び出していますが、lambdaは呼び出していません。そのため、Procはreturn文で元の呼び出し元に戻って、その後のコードも実行されなくなっています。一方、lambdaはreturn文を呼び出すことがないので、lambda_testメソッドが正常に終了します。
並行処理
Rubyには以下のような並行処理の仕組みが提供されています。
- Threadクラス
- Threadクラスを使用することで、Rubyでのマルチスレッドプログラミングが可能になります。Thread.newメソッドを呼び出すことで新しいスレッドを作成し、その中で並列に実行したい処理をブロックとして渡します。スレッド間でのデータ共有については、QueueやMutexといった同期プリミティブを使用することができます。
- Fiberクラス
- Fiberクラスを使用することで、Rubyでのコルーチンを実現できます。コルーチンは一般的なスレッドとは異なり、スケジューラを持っておらず、明示的に切り替える必要があります。Fiber.yieldメソッドを呼び出すことで一旦コルーチンの実行を中断し、Fiber.resumeメソッドを呼び出すことで再開します。
- Processクラス
- Processクラスを使用することで、Rubyでのプロセス間通信が可能になります。プロセス同士はメモリを共有せず、別々のプロセスとして実行されます。プロセス間で通信を行うには、パイプや共有メモリなどの仕組みを使用する必要があります。
なお、Rubyには以上の他にも、ThreadクラスやFiberクラスと同様に、GVL(Global VM Lock)によってスレッドセーフに実行されるイベントマシンであるEventMachineや、並行処理のためのツールキットであるCelluloidなどが存在します。
スレッド(Thread)
Rubyにおけるスレッドは、軽量な並列処理を実現するための機能であり、プログラム内で独立して動作する変数やメソッドの集合を表します。スレッドは、複数のタスクを同時に処理することで、CPUを最大限活用することができます。
スレッドは、Threadクラスを使用して作成することができます。Thread.newメソッドを使用して、新しいスレッドを作成した後、startメソッドを呼び出すことでスレッドを開始します。また、Thread.currentメソッドを使用することで、現在のスレッドを取得することができます。
以下は、スレッドを使用した簡単な例です。
t1 = Thread.new { # スレッド1の処理 puts "Thread 1 started" sleep(2) puts "Thread 1 ended" } t2 = Thread.new { # スレッド2の処理 puts "Thread 2 started" sleep(1) puts "Thread 2 ended" } t1.join t2.join
この例では、スレッド1とスレッド2が開始されます。それぞれのスレッドは、sleepメソッドを使用して一定時間待機した後、メッセージを出力します。joinメソッドを呼び出すことで、スレッドが完了するまでプログラムの実行をブロックします。
Rubyにおけるスレッドには、マルチスレッドに関する問題がいくつか存在します。例えば、同一のリソースにアクセスする複数のスレッドによる競合状態や、デッドロックに陥ってしまう可能性があります。これらの問題を回避するため、適切なロック処理や排他制御が必要な場合があります。
ファイバー(Fiber)
Rubyのファイバー(Fiber)は、擬似並行処理を実現するための仕組みです。標準的なスレッドと異なり、Rubyのファイバーはコルーチン的な動作をし、明示的に制御を渡す必要があります。
ファイバーは、Ruby 1.9以降から標準機能として組み込まれています。ファイバーを利用することで、特定の処理を中断したあと再開したり、独自のスケジューリングに基づいた処理を実現したりすることができます。
ファイバーは以下のように作成できます。
fiber = Fiber.new do puts "start" Fiber.yield puts "end" end
このコードでは、Fiber.newメソッドにブロックを渡してファイバーを作成します。puts "start"を実行した後、Fiber.yieldで処理を中断しています。この時点でプログラムは停止しており、puts "end"はまだ実行されていません。
ファイバーを再開するには、Fiber#resumeメソッドを呼び出します。
fiber.resume
このコードを実行すると、puts "start"が表示された後にプログラムが停止します。その後、再びfiber.resumeを実行すると、puts "end"が表示されます。
このように、ファイバーを利用することで、特定の処理を中断してから再開することができます。また、ファイバーを複数作成し、独自のスケジューリングに基づいた処理を行うことができます。
以下は、複数のファイバーを作成し、それぞれの処理を交互に実行する例です。
f1 = Fiber.new do loop do puts "1" Fiber.yield end end f2 = Fiber.new do loop do puts "2" Fiber.yield end end f1.resume f2.resume f1.resume f2.resume # ...
このように、ファイバーを利用することで、擬似的な並行処理を実現することができます。しかし、ファイバーはスレッドと異なり、OSレベルでのスケジューリングを行わないため、適切な制御を行わないとデッドロックや競合状態などの問題が生じることがあります。注意して使用する必要があります。
プロセス(Process)
Rubyには、プロセスを管理するための Process モジュールが用意されています。このモジュールを使用することで、現在のプロセスや子プロセスを管理することができます。以下に、Processモジュールの主な機能をコードを交えて解説します。
現在のプロセス情報の取得
Process モジュールに用意されている、 Process.pid メソッドを使用することで、現在のプロセスのIDを取得することができます。以下は、 Process.pid を使用して現在のプロセスIDを取得し、表示する例です。
puts "現在のプロセスID: #{Process.pid}"
プロセスの生成
Process モジュールには、新しいプロセスを生成するためのメソッドが複数用意されています。ここでは、代表的な2つのメソッドを紹介します。
Process.spawn メソッド
Process.spawn メソッドを使用することで、外部プログラムを実行する新しいプロセスを生成することができます。以下は、 ls コマンドを実行する新しいプロセスを生成する例です。
pid = Process.spawn("ls")
fork メソッド
fork メソッドを使用することで、現在のプロセスの子プロセスを生成することができます。 fork メソッドは、呼び出し元のプロセスからコピーされた新しいプロセスを生成します。以下は、 fork メソッドを使用して子プロセスを生成し、子プロセス内で puts メソッドを呼び出す例です。
pid = fork if pid.nil? puts "子プロセスのID: #{Process.pid}" else puts "親プロセスのID: #{Process.pid}, 子プロセスのID: #{pid}" end
プロセスの終了
生成したプロセスを適切に終了することも重要です。以下は、プロセスを終了するためのメソッドを紹介します。
Process.kill メソッド
Process.kill メソッドを使用することで、指定したシグナルを送信してプロセスを終了することができます。以下は、 kill メソッドを使用して、先程生成した ls コマンドを実行しているプロセスを終了する例です。
pid = Process.spawn("ls") puts "新しいプロセスのID: #{pid}" sleep 1 Process.kill("TERM", pid) puts "プロセスを終了しました"
まとめ
このように、Rubyの Process モジュールを使用することで、プロセスの生成や管理を容易に行うことができます。プロセスを扱う場合は、適切な終了処理も行うようにしましょう。
ライブラリ編
プログラミング言語Rubyの大きな魅力の1つは、豊富なライブラリが用意されていることです。Rubyには標準添付ライブラリとgemと呼ばれるパッケージ管理システムを通じて配布されるライブラリがあり、開発者はこれらのライブラリを活用することで、効率的で堅牢なプログラミングが可能になります。
ライブラリ編では、Rubyにおけるライブラリの概念と、主要なライブラリの機能およびユースケースを解説します。ライブラリのインストール方法から、具体的なコード例に至るまでカバーし、Rubyでのライブラリ活用の第一歩を支援します。
リテラル表記
Rubyでは、数値、文字列、正規表現、シンボル、配列、ハッシュ、範囲などのオブジェクトを直接記述する方法としてリテラル表記が用意されています。
- 数値(Numeric)
- 整数と浮動小数点数のリテラルは通常の数値表記で記述します。有理数は接尾辞
r
を補い、複素数は虚数単位i
を伴い表記します。 42 0o177 0xbad 3.14 123e45 55/7r 3+4i
- 整数と浮動小数点数のリテラルは通常の数値表記で記述します。有理数は接尾辞
- 文字列(String)
- 文字列リテラルは
"
や'
で囲んで記述します。 "Hello, World!" 'Ruby is fun'
- 文字列リテラルは
- 正規表現(Regexp)
- 正規表現リテラルは
/
で開始し/
で終了する形式で記述します。 /ruby/ /^start/
- 正規表現リテラルは
- シンボル(Symbol)
- シンボルリテラルはコロン
:
から始まる名前で表します。 :symbol :name
- シンボルリテラルはコロン
- 配列(Array)
- 配列リテラルは
[]
の中に要素をカンマ区切りで記述します。 [1, 2, 3, 4, 5] ["apple", "banana", "orange"]
- 配列リテラルは
- ハッシュ(Hash)
- ハッシュリテラルは
{}
の中にキーと値をハッシュロケット=>
で対応付けて記述します。 {"name" => "Jane", "age" => 25} {:name => "John", :age => 30}
- キーがシンボルの場合は、短縮表記があります。
{name: "John", age: 30}
- ハッシュリテラルは
- 範囲(Range)
- 範囲リテラルは開始値と終了値を
..
または...
で囲んで記述します。 (1..5) # 1から5まで ("a"..."d") # "a"から"c"まで
- 範囲リテラルは開始値と終了値を
これらのリテラル表記は、対応するクラスのオブジェクトを直接生成するための簡潔な記法となっています。また、配列やハッシュの中でもリテラルを入れ子にすることができます。オブジェクトの初期化を手軽に記述できるため、Rubyのコードの可読性が高まります。
数値(Numeric)
RubyのNumericは、数値を表す抽象クラスです。具体的な数値の型、例えば整数(Integer)や浮動小数点数(Float)などは、このNumericクラスのサブクラスとして実装されています。
Numericクラスは、数値演算や比較などの基本的な操作を提供します。例えば、加算、減算、乗算、除算、比較演算子(<、<=、==、>、>=)などがこのクラスで定義されています(ただし複素数に大小関係は定義できないので大小比較演算子も定義されません)。 また、単項の加算と減算を表す+@や-@といったメソッドも定義されています。
このクラスは、Rubyの数値型の共通の振る舞いを定義するための基盤となっています。そのため、IntegerやFloatなどの具体的な数値型が共通のメソッドや振る舞いを持てるように、Numericクラスでこれらのメソッドが定義されています。 数値演算や比較などの一般的な操作に加えて、Numericクラスは他の数値型間での演算を可能にするためのメソッドも提供しています。例えば、coerceメソッドを使うことで、異なる数値型同士での演算が可能になります。
整数(Integer)
Rubyには整数を扱うためのIntegerクラスがあります。
Integerクラスは、以下のような数値リテラルで整数値を表現することができます。
123 #=> 123 0b1111 #=> 15 0o777 #=> 511 0xFF #=> 255
また、整数の四則演算や比較演算子なども、Integerクラスで定義されています。
1 + 2 #=> 3 3 - 4 #=> -1 5 * 6 #=> 30 7 / 8 #=> 0 9 % 4 #=> 1 1 == 1 #=> true 2 < 3 #=> true 4 > 5 #=> false
浮動小数点数(Float)
Rubyには浮動小数点数を扱うためのFloatクラスがあります。 Floatクラスは、IEEE 754のbinary64をRubyのクラス化したものです。
以下のような数値リテラルで浮動小数点数を表現することができます。
1.23 #=> 1.23 1.2e3 #=> 1200.0 -4.56 #=> -4.56 4E10 #=> 40000000000.0
また、浮動小数点数の四則演算や比較演算子なども、Floatクラスで定義されています。
1.2 + 3.4 #=> 4.6 5.6 - 7.8 # -2.1999999999999997 9.0 * 1.2 # 10.8 3.4 / 5.6 # 0.6071428571428571 1.2 == 1.2 #=> true 2.3 < 4.5 # true 6.7 > 8.9 # false 1.0 / 0.0 #=> Infinity 1.0 / -0.0 # -Infinity 0.0 / 0.0 # NaN
coerceメソッドによる型強制
Rubyのcoerce
メソッドは、数値演算の際に異なる型のオブジェクトを同じ型に変換するために使用されます。一般的には、coerce
メソッドは二項演算子(たとえば+
、-
、*
、/
など)が呼び出されたときに使用されます。
例えば、1 + 2.0
の場合、整数型の1
と浮動小数点数型の2.0
が加算されます。このとき、Rubyは1
をFloat
型に変換してから加算を行います。この変換はcoerce
メソッドによって行われます。
具体的には、1 + 2.0
の場合、Rubyは以下のように処理を行います:
- 整数型の
1
がFloat
型の2.0
に合わせるために、1.coerce(2.0)
を呼び出します。 coerce
メソッドは、[1.0, 2.0]
という配列を返します。これは1
がFloat
型に変換された結果です。- Rubyは、
1.0 + 2.0
(1.coerce(2.0).reduce(&:+)
)を計算し、結果の3.0
を返します。
このように、coerce
メソッドは異なる型のオブジェクトを同じ型に変換して演算を行う際に使用されます。
有理数(Rational)
RubyのRational
クラスは、有理数を表現するためのクラスです。
有理数は、整数のペアとして表現されます:a/b(b>0)、ここでaは分子であり、bは分母です。
数学的には、整数aは有理数a/1と等価です。
以下は、Rational
クラスのインスタンスを作成する方法の例です。
p 1r #=> (1/1) p 3/4r # (3/4) p Rational(1) # (1/1) p Rational(3, 4) # (3/4) p Rational(0.3) # (5404319552844595/18014398509481984) p Rational('0.3') # (3/10) p Rational(3)/10 # (3/10) p 3.to_r/10 # (3/10))
Rational
クラスのインスタンスは、有理数の四則演算や比較演算を行うことができます。
a = 2/3r b = 4/5r p a + b #=> (22/15) p a - b # (-2/15) p a * b # (8/15) p a / b # (10/9) p a == b #=> false p a != b # true p a > b # false p a >= b # false p a <=> 0.0/0.0 #=> nil
複素数(Complex)
RubyのComplex
クラスは、複素数を表現するためのクラスです。
このクラスを使用すると、複素数を直交座標形式または極座標形式で作成し、演算を行うことができます。
直交座標形式の複素数は、実数部と虚数部のペアで表されます。以下は、複素数を直交座標形式で作成する方法の例です。
c1 = 3 + 4i C2 = Complex.rect(3, 4) c3 = Complex(3, 4) c4 = '3 + 4i'.to_c c5 = 3 + 4 * Complex::I
c1 = 3 + 4i
: この行では、虚数単位i
を使って直接複素数を表現しています。3
は実部であり、4
は虚部です。c2 = Complex.rect(3, 4)
: ここでは、Complex
クラスのrect
メソッドを使用して複素数を作成しています。これは、実部と虚部を指定する方法です。c3 = Complex(3, 4)
: この行では、Complex
クラスのコンストラクタを使用して複素数を作成しています。引数として実部と虚部を指定します。c4 = '3 + 4i'.to_c
: ここでは、文字列'3 + 4i'
を複素数に変換しています。to_c
メソッドは、文字列が複素数の形式である場合に使用できます。c5 = 3 + 4 * Complex::I
: 最後の行では、Complex::I
定数を使って複素数を作成しています。Complex::I
は虚数単位を表します。
極座標形式の複素数は、絶対値(大きさ)と偏角で表されます。以下は、複素数を極座標形式で作成する方法の例です。
Complex.polar(5, Math::PI / 4) # 絶対値が5、偏角がπ/4の複素数を作成
これらの方法を使って、異なる形式の入力を扱い、複素数を生成することができます。
直交座標形式と極座標形式の相互変換
c = Complex(3, 4) p c.real # 3 p c.imaginary # 4 p c.rect # [3, 4] p c.polar # [5.0, 0.9272952180016122] z = p Complex.polar(10, Math::PI / 4) p z.real # 7.0710678118654755 p z.imag # 7.071067811865475
Complex
クラスでは、複素数の演算もサポートされています。例えば、加算や乗算、除算などが可能です。
a = Complex(2, 3) b = Complex(4, 5) c = a + b # 6 + 8i d = a - b # -2 - 2i e = a * b # -7 + 22i f = a / b # 0.5609756097560976 + 0.0487804878048781i
また、複素数同士の比較も可能です。
==
演算子を用いることで、2つの複素数が等しいかを比較することができ、
!=
演算子を用いることで、2つの複素数が等しくないを比較することができます。
a = Complex(2, 3) b = Complex(2, 3) puts a == b # true puts a != b # false
複素数の一致・不一致は定義されますが、大小関係はないので <
, >
, <=
, >=
や <=>
は定義されていません
複素数の演算や比較において、実数と複素数の演算や比較も可能です。 この場合、実数は複素数の虚数部が0のものとして扱われます。例えば、以下のような計算が可能です。
a = Complex(2, 3) b = 4 c = a + b # 6 + 3i d = a * b # 8 + 12i e = a > b # true
Numericと、そのサブクラスのメソッド
Numericと、そのサブクラスのメソッドはの実装は均質ではなく、整数だけがビット演算が可能であったり、複素数だけが大小の比較が出来なかったりします。
このため実装状況を調べたいのですが、手でテストケースを書くのも手数がかかるので、自動的に表にまとめるプログラムを書いてみました。
- メソッドの実装状況表を書く
def build_table(types) headers = types.map(&:to_s) objects = types.map do |t| eval("#{t}.new") rescue StandardError eval("#{t}(0)") end # メソッドの和集合を求める all_methods = objects.map(&:methods).flatten.sort.uniq all_methods -= Object.methods all_methods += Comparable.methods all_methods.sort! all_methods.uniq! method_str_width = all_methods.map(&:to_s).map(&:length).max # ヘッダー head = ['Method'.rjust(method_str_width), *headers.map { _1.center 8 }].join(' ') # 各オブジェクトについて、メソッドが存在するかどうかを確認し表を構築する table = '' index = 0 all_methods.each do |method| next unless objects.any? { |obj| obj.methods.include? method } table << "#{head}\n#{'-' * head.length}\n" if (index % 10).zero? table << method.to_s.rjust(26) << ' ' table << objects.map do |obj| (obj.methods.include?(method) ? 'o' : '-').center(8) end.join(' ') table << "\n" index += 1 end puts table end build_table([Numeric, Integer, Float, Rational, Complex])
- 実行結果
Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- ! o o o o o != o o o o o !~ o o o o o % o o o o - & - o - - - * - o o o o ** - o o o o + - o o o o +@ o o o o o - - o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- -@ o o o o o / - o o o o < o o o o - << - o - - - <= o o o o - <=> o o o o o == o o o o o === o o o o o =~ o o o o o > o o o o - Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- >= o o o o - >> - o - - - [] - o - - - ^ - o - - - __id__ o o o o o __send__ o o o o o abs o o o o o abs2 o o o o o allbits? - o - - - angle o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- anybits? - o - - - arg o o o o o between? o o o o - bit_length - o - - - ceil o o o o - chr - o - - - clamp o o o o - class o o o o o clone o o o o o coerce o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- conj o o o o o conjugate o o o o o define_singleton_method o o o o o denominator o o o o o digits - o - - - display o o o o o div o o o o - divmod o o o o - downto - o - - - dup o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- enum_for o o o o o eql? o o o o o equal? o o o o o even? - o - - - extend o o o o o fdiv o o o o o finite? o o o o o floor o o o o - freeze o o o o o frozen? o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- gcd - o - - - gcdlcm - o - - - hash o o o o o i o o o o - imag o o o o o imaginary o o o o o infinite? o o o o o inspect o o o o o instance_eval o o o o o instance_exec o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- instance_of? o o o o o instance_variable_defined? o o o o o instance_variable_get o o o o o instance_variable_set o o o o o instance_variables o o o o o integer? o o o o o is_a? o o o o o itself o o o o o kind_of? o o o o o lcm - o - - - Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- magnitude o o o o o method o o o o o methods o o o o o modulo o o o o - nan? - - o - - negative? o o o o - next - o - - - next_float - - o - - nil? o o o o o nobits? - o - - - Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- nonzero? o o o o o numerator o o o o o object_id o o o o o odd? - o - - - ord - o - - - phase o o o o o polar o o o o o positive? o o o o - pow - o - - - pred - o - - - Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- prev_float - - o - - private_methods o o o o o protected_methods o o o o o public_method o o o o o public_methods o o o o o public_send o o o o o quo o o o o o rationalize - o o o o real o o o o o real? o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- rect o o o o o rectangular o o o o o remainder o o o o - remove_instance_variable o o o o o respond_to? o o o o o round o o o o - send o o o o o singleton_class o o o o o singleton_method o o o o o singleton_method_added o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- singleton_methods o o o o o size - o - - - step o o o o - succ - o - - - taint o o o o o tainted? o o o o o tap o o o o o then o o o o o times - o - - - to_c o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- to_enum o o o o o to_f - o o o o to_i - o o o o to_int o o o o o to_r - o o o o to_s o o o o o truncate o o o o - trust o o o o o untaint o o o o o untrust o o o o o Method Numeric Integer Float Rational Complex ----------------------------------------------------------------------- untrusted? o o o o o upto - o - - - yield_self o o o o o zero? o o o o o | - o - - - ~ - o - - -
文字列(String)
Rubyでは、文字列(String)はダブルクォート(")で囲まれた文字列リテラルで表します。シングルクォート(')で囲まれた文字列リテラルは、単なる文字列であり、式の展開やエスケープシーケンスの展開が行われません。
name = "Alice" puts "Hello, #{name}!" #=> "Hello, Alice!" puts 'Hello, #{name}!' #=> "Hello, \#{name}!"
文字列中の式の展開
Rubyでは、ダブルクォートで囲まれた文字列リテラルの中で式を展開することができます。式を展開する場合は、#{}を使います。式を#{}で囲んで文字列中に埋め込むことができます。
x = 10 puts "xの値は#{x}です" #=> "xの値は10です" puts "xの二乗は#{x ** 2}です" #=> "xの二乗は100です"
また、式を展開する場合は、式が評価されてから文字列中に埋め込まれます。そのため、式の中で定義された変数などの値も展開されます。
x = 10 puts "xの値は#{x}です" #=> "xの値は10です" x = 20 puts "xの値は#{x}です" #=> "xの値は20です"
なお、シングルクォートで囲まれた文字列リテラルでは、式の展開は行われず、文字列中にそのまま#{...}が表示されます。
x = 10 puts 'xの値は#{x}です' #=> "xの値は\#{x}です"
エスケープシーケンス
以下がRubyで使用できる主なエスケープシーケンスです。
エスケープシーケンス エスケープシーケンス 意味 \\ バックスラッシュ \' シングルクォート \" ダブルクォート \a ベル音(BEL) \b バックスペース \f 改ページ(FF) \n 改行 \r キャリッジリターン(CR) \t 水平タブ \v 垂直タブ \e エスケープ文字(ESC) \s 空白 \nnn 8進数表記の文字コード(nは0〜7の数字) \xnn 16進数表記の文字コード(nは0〜9またはA〜F) \unnnn Unicode文字コード(nは0〜9またはA〜F) \Unnnnnnnn Unicode文字コード(nは0〜9またはA〜F)
なお、Rubyではダブルクォートで囲まれた文字列リテラル内でエスケープシーケンスが展開されますが、シングルクォートで囲まれた文字列リテラル内では展開されません。
文字リテラル
Rubyにおける「文字リテラル」は、?
を前置した1文字で表します。
p ?a #==> "a" p ?字 #==> "字" p ?😈 #==> "😈" p ?😈.class #==> String
Rubyに文字型はなく、文字リテラルは長さ1文字の文字列です。
エンコーディング
Ruby では、文字列のエンコーディングを指定することができます。通常、UTF-8 エンコーディングが使われますが、別のエンコーディングを指定することもできます。
文字列のエンコーディングを指定するには、文字列の前に encoding: を付けて指定します。例えば、UTF-8 エンコーディングの文字列を扱う場合は以下のように書きます。
str = "こんにちは".encoding("UTF-8")
別のエンコーディングを指定する場合は、指定したいエンコーディング名を文字列で渡します。
str = "こんにちは".encoding("Shift_JIS")
上記のコードでは、文字列 "こんにちは" が Shift_JIS エンコーディングの文字列として扱われます。
文字列のエンコーディングを指定することで、文字列の変換時にエラーが発生することを防ぐことができます。
正規表現(Regexp)
Rubyにおける「正規表現クラス」と「正規表現リテラル」は、文字列をパターンとして表現するための機能です。
まず、「正規表現クラス」は Regexp
クラスを使用して表現されます。以下のように、Regexp.new
メソッドを使用して正規表現を作成することができます。
pattern = Regexp.new("Hello, world!")
また、正規表現オプションを指定する場合は、第2引数にオプションを渡すことができます。
pattern = Regexp.new("hello, world!", Regexp::IGNORECASE)
次に、「正規表現リテラル」はスラッシュ /
で囲まれた文字列で表現されます。以下のように、スラッシュで正規表現を作成することができます。
pattern = /Hello, world!/
また、正規表現オプションを指定する場合は、スラッシュの後ろにオプションを付けることができます。
pattern = /hello, world!/i
正規表現クラスと正規表現リテラルはどちらも同じ機能を持っており、どちらを使用しても正規表現を表現することができます。 ただし、リテラル表現の方が簡潔に表現することができ、可読性が高くなります。
Rubyで使用できる正規表現 正規表現 ユースケース /pattern/
文字列に一致するパターンを検索します。 /./
任意の一文字に一致します。 /^pattern/
文字列の先頭に一致するパターンを検索します。 /pattern$/
文字列の末尾に一致するパターンを検索します。 /[abc]/
a、b、cのいずれかに一致するものを検索します。 /[^abc]/
a、b、c以外のものに一致するものを検索します。 /pattern*/
0回以上の繰り返しに一致するパターンを検索します。 /pattern+/
1回以上の繰り返しに一致するパターンを検索します。 /pattern?/
0回または1回の繰り返しに一致するパターンを検索します。 /pattern{n}/
n回の繰り返しに一致するパターンを検索します。 /pattern{n,}/
n回以上の繰り返しに一致するパターンを検索します。 /pattern{n,m}/
n回以上、m回以下の繰り返しに一致するパターンを検索します。 /(pattern)/
一致する部分をグループ化します。 /(?<name>pattern)/
一致する部分を名前でグループ化します。 /(?#comment)/
正規表現内のコメントを書くことができます。 /\Apattern/
文字列の先頭に一致するパターンを検索します。 /pattern\z/
文字列の末尾に一致するパターンを検索します。 /\bpattern/
単語の先頭に一致するパターンを検索します。 /pattern\b/
単語の末尾 /pattern/i
大文字小文字を区別せずに文字列に一致するパターンを検索します。 /pattern/m
複数行の文字列に一致するパターンを検索します。 /pattern/x
正規表現内で空白を無視します。 /pattern/o
正規表現を一度だけコンパイルします。
シンボル(Symbol)
Rubyの「シンボル(Symbol)」は、一度定義されたら変更できないイミュータブルなオブジェクトで、文字列に似たデータ型です。シンボルは、コロン(:
)の後ろに名前を付けて表します。例えば、:apple
や:banana
などがシンボルの例です。
シンボルは、主にキーとして使われることが多く、文字列と比較して以下のようなメリットがあります。
- シンボルは一度生成されたら変更できないため、ハッシュのキーとして使うときに、キーの値が変更される心配がありません。
- 同じ名前のシンボルは必ず同じオブジェクトIDを持つため、検索が高速になります。
- 文字列よりもメモリ消費量が少ないため、大量のデータを扱う場合や、ハッシュのキーとして多用する場合に有効です。
シンボルを使う場合、通常はコロン(:
)をつけて定義しますが、:"apple"
のようにダブルクォーテーション("
)で囲って文字列から生成することもできます。また、文字列からシンボルを生成するには、to_sym
メソッドまたはintern
メソッドを使います。
例えば、以下のようにハッシュのキーとしてシンボルを使うことができます。
fruits = { apple: 100, banana: 200, orange: 300 } puts fruits[:banana] # => 200
配列(Array)
Rubyの配列(Array)は、複数の要素を順序付けて格納するためのデータ構造です。配列は、異なるデータ型の要素を混在させることができます。例えば、整数、文字列、オブジェクトなどを含めることができます。
Rubyの配列は、角かっこ [ ]
で囲まれた要素のコンマ区切りのリストで表されます。以下は、Rubyの配列の例です。
numbers = [1, 2, 3, 4, 5] fruits = ["apple", "banana", "orange"] mixed_array = [1, "hello", true, 3.14]
配列内の要素は、0から始まるインデックスを使用してアクセスできます。例えば、numbers
配列の最初の要素にアクセスするには、numbers[0]
とします。
Rubyの配列は動的で、サイズを事前に宣言する必要はありません。要素を追加、削除、置換することができます。また、Rubyには多くの配列関連のメソッドが組み込まれており、配列を操作するための便利な機能が提供されています。
Rubyの配列には、さまざまな操作が可能です。以下にいくつかの一般的な操作を示します。
- 要素の追加と削除:
<<
演算子やpush
メソッドを使用して要素を配列の末尾に追加できます。また、pop
メソッドを使用して末尾の要素を削除できます。他にも、unshift
メソッドを使って先頭に要素を追加したり、shift
メソッドを使って先頭の要素を削除したりすることもできます。numbers << 6 # numbersは[1, 2, 3, 4, 5, 6] fruits.push("grape") # fruitsは["apple", "banana", "orange", "grape"] numbers.pop # numbersは[1, 2, 3, 4, 5] fruits.unshift("kiwi") # fruitsは["kiwi", "apple", "banana", "orange", "grape"] fruits.shift # fruitsは["apple", "banana", "orange", "grape"]
- 要素の取得と変更: インデックスを使用して特定の要素にアクセスし、値を取得したり変更したりできます。
puts numbers[2] # 3を出力 fruits[1] = "pear" # fruitsは["apple", "pear", "orange", "grape"]
- 配列の結合と分割:
+
演算子を使用して配列を結合したり、concat
メソッドを使用したりして配列を結合できます。また、slice
メソッドを使用して配列を部分的に分割することもできます。combined_array = numbers + fruits # combined_arrayは[1, 2, 3, 4, 5, "apple", "pear", "orange", "grape"] part_of_fruits = fruits.slice(1, 2) # part_of_fruitsは["pear", "orange"]
- 配列と繰り返し:
- Rubyの配列は、繰り返し処理とともにしばしば使われるデータ構造です。配列の要素を1つずつ処理する場合には、以下のような繰り返し処理がよく用いられます。
fruits = ["apple", "banana", "orange"] fruits.each do |fruit| puts fruit end
- このコードでは、
each
メソッドを用いて、fruits
配列の要素を順番に取り出しています。そして、ブロック引数fruit
に格納された各要素をputs
メソッドで出力しています。 - また、配列の要素をインデックスで処理する場合には、以下のような繰り返し処理がよく用いられます。
fruits = ["apple", "banana", "orange"] fruits.each_with_index do |fruit, i| puts "#{i+1}: #{fruit}" end
- このコードでは、
each_with_index
メソッドを用いて、fruits
配列の要素とインデックスを順番に取り出しています。そして、ブロック引数fruit
に格納された各要素とブロック引数i
に格納された各インデックスを使って、puts
メソッドで出力しています。
- 要素の検索とフィルタリング:
include?
メソッドを使用して特定の要素が配列に含まれているかどうかを確認したり、select
メソッドを使用して条件に一致する要素のみを抽出したりすることができます。puts numbers.include?(3) # trueを出力 even_numbers = numbers.select { |num| num.even? } # even_numbersは[2, 4]
- 配列操作メソッド: Rubyには、配列を操作するための便利なメソッドが豊富に用意されています。その中でも、
map
、filter
、reduce
は特によく使われるメソッドです。numbers = [1, 2, 3, 4, 5] doubled_numbers = numbers.map do |n| n * 2 end p doubled_numbers #=> [2, 4, 6, 8, 10] even_numbers = numbers.filter do |n| n.even? end p even_numbers #=> [2, 4] sum = numbers.reduce do |result, n| result + n end puts sum #=> 15 # ブロックの代わりにシンボルを使って、次のように書くこともできます。 puts numbers.reduce(&:+) #=> 15
これらはRubyの配列を操作するための基本的な操作の一部です。Rubyの配列にはさらに多くの機能がありますが、これらの基本操作から始めることができます。
メソッドチェイン
Rubyには、複数のメソッドを連鎖して呼び出す「メソッドチェイン」という機能があります。メソッドチェインを使うことで、1つのオブジェクトに対して複数のメソッドを簡潔につなげて処理することができます。
例えば、以下のような配列があるとします。
fruits = ["apple", "banana", "orange"]
この配列に対して、map
メソッドで各要素を大文字にし、さらにjoin
メソッドでカンマ区切りの文字列に変換する場合、通常の記法では以下のように書くことができます。
fruits = ["apple", "banana", "orange"] upcase_fruits = fruits.map do |fruit| fruit.upcase end result = upcase_fruits.join(",") puts result #=> "APPLE,BANANA,ORANGE"
しかし、メソッドチェインを使うと以下のように簡潔に書くことができます。
fruits = ["apple", "banana", "orange"] result = fruits.map {|fruit| fruit.upcase}.join(",") puts result #=> "APPLE,BANANA,ORANGE"
さらにブロックにシンボルを使うと以下のように簡潔に書くことができます。
fruits = ["apple", "banana", "orange"] result = fruits.map(&:upcase).join(",") puts result #=> "APPLE,BANANA,ORANGE"
このように、複数のメソッドを簡潔につなげて処理することができます。ただし、メソッドチェインは読みやすさを損なうことがあるため、適切に使うようにしましょう。
- tapメソッド
tap
メソッドは、オブジェクト自体を受け取り、ブロックをそのオブジェクトのコンテキストで実行し、最終的にそのオブジェクトを返すメソッドです。主な目的は、オブジェクトの特定の状態をデバッグやログ出力などで調査することです。例えば、以下のように使用できます。
some_object.tap{|obj|p obj}.method1.method2.method3
このコードでは、some_object
に対して method1
から method3
までのメソッドを呼び出す前に、tap
ブロック内でその状態を出力しています。tap
メソッドは常にそのオブジェクトを返すため、メソッドチェインを中断することなく、そのまま次のメソッド呼び出しを行うことができます。
これらの機能を組み合わせることで、効果的にコードを記述し、デバッグやログ出力などの目的に役立てることができます。
ハッシュ(Hash)
Rubyの「ハッシュ(Hash)」は、キー(key)と値(value)のペアを保持するデータ構造であり、連想配列やマップとも呼ばれます。
ハッシュは波括弧({}
)で囲まれたキーと値のペアのリストで表され、キーと値の間には=>
を使って対応付けます。例えば、以下のように定義することができます。
my_hash = { "key1" => "value1", "key2" => "value2" }
この場合、my_hash
は、"key1"
というキーに対応する"value1"
、"key2"
というキーに対応する"value2"
という値を持ちます。
ハッシュは、キーを使って値を取得することができます。例えば、上記のmy_hash
から値を取得するには、以下のように書きます。
my_hash["key1"] # => "value1"
また、Hash
クラスのeach
メソッドを使うことで、ハッシュ内の全てのキーと値に対して繰り返し処理を行うことができます。以下は、my_hash
の全てのキーと値を出力する例です。
my_hash.each do |key, value| puts "#{key}: #{value}" end
出力結果は以下のようになります。
key1: value1 key2: value2
このように、ハッシュはRubyで非常に重要なデータ構造の一つであり、プログラムで様々な用途に利用されます。
シンボルをキーとした特別なハッシュリテラル
例えば、以下のようなハッシュを考えます。
my_hash = { :key1 => "value1", :key2 => "value2" }
このハッシュでは、キーとしてシンボルを使っています。ただし、この記法は少し冗長であり、Rubyでは以下のように短縮して書くことができます。
my_hash = { key1: "value1", key2: "value2" }
このように、キーをシンボルで表す場合は、コロン(:
)をキー名の前に付けることで簡潔に表現できます。これにより、より見やすく、読みやすいコードを書くことができます。
また、シンボルを使うことで、文字列を使う場合に比べて、ハッシュの検索速度が速くなることがあります。これは、シンボルはRuby内部で一度だけ生成され、それ以降は同じオブジェクトを使い回すため、検索時にオブジェクトの比較を高速に行えるためです。
なお、シンボルは、文字列と異なりイミュータブルなオブジェクトであり、文字列と同様に、to_sym
メソッドを使って文字列からシンボルを生成することもできます。
範囲(Range)
Rubyの「範囲(Range)」は、ある値から別の値までの連続する要素を表すオブジェクトです。Rubyでは、範囲は..
または...
の範囲演算子を使って作成することができます。以下に例を示します。
range1 = 1..5 # 1から5までの範囲を表すRangeオブジェクトを作成 range2 = 1...5 # 1から5未満までの範囲を表すRangeオブジェクトを作成
..
を使うと、範囲に終端の値が含まれますが、...
を使うと、範囲に終端の値は含まれません。
範囲は主に繰り返し処理に使われ、以下のように使うことができます。
range = 1..5 range.each { |i| puts i } # 1から5までの値を1つずつ表示する
このコードでは、each
メソッドを使って、範囲内の値を1つずつ取り出し、ブロック内で処理しています。
また、範囲を使うことで、配列の一部を切り出すこともできます。例えば、以下のように範囲を使って、配列から一部の要素を取り出すことができます。
array = [1, 2, 3, 4, 5] sub_array = array[1..3] # 2番目から4番目までの要素を取り出す puts sub_array # => [2, 3, 4]
このように、範囲はRubyで様々な場面で使われ、プログラミングをより効率的に行うための重要な要素の1つです。
ファイル (File)
Rubyにはファイル操作を行う標準ライブラリ File
があります。これを使用することで、ファイルの読み込み・書き込み、存在確認・削除、ディレクトリの作成・削除などが簡単に行えます。
以下は、ファイル操作の基本的な使い方の例です。
# ファイルの読み込み File.open('sample.txt', 'r') do |file| puts file.read end # ファイルの書き込み File.open('output.txt', 'w') do |file| file.write('Hello, world!') end # ファイルの存在確認 puts File.exist?('sample.txt') #=> true # ファイルの削除 File.delete('output.txt') # ディレクトリの作成 Dir.mkdir('mydir') # ディレクトリの削除 Dir.delete('mydir')
数学(Math)
RubyのMathモジュールは、数学関数を提供するライブラリです。このモジュールに含まれる関数を使用することで、三角関数、指数関数、対数関数などの計算を簡単に行うことができます。
例えば、sin関数を使用する場合は、以下のように記述します。
Math.sin(30 * Math::PI / 180) #=> 0.5
この例では、30度の正弦(sin)を計算しています。Math::PIは円周率を表しており、角度をラジアンに変換しています。
他にも、cos関数、tan関数、exp関数、log関数などがあります。詳細は公式ドキュメントを参照してください。
Rubyの標準ライブラリとMix-in
Rubyの標準ライブラリには、多数のクラスやモジュールが含まれています。これらのクラスやモジュールは、Rubyプログラムで頻繁に使用される共通機能を提供しています。例えば、文字列(String)クラス、配列(Array)クラス、ファイル操作(File)クラスなどがあります。
また、RubyではMix-inという概念もあります。Mix-inとは、あるクラスに他のモジュールを取り込んで機能を追加することです。これにより、クラスを継承することなく、複数のモジュールから機能を組み合わせて利用することができます。
例えば、Enumerableモジュールは、Rubyの多くのクラスにEnumerableという機能を提供するためのモジュールです。Enumerableモジュールには、eachメソッドやmapメソッド、selectメソッドなどが含まれており、これらのメソッドを利用することで、配列やハッシュなどのオブジェクトに対して繰り返し処理を簡単に行うことができます。
以下に、標準ライブラリからいくつかの代表的なクラスやMix-inを挙げます。
- Stringクラス:文字列を扱うためのクラス
- Arrayクラス:配列を扱うためのクラス
- Hashクラス:ハッシュを扱うためのクラス
- Fileクラス:ファイルを扱うためのクラス
- Enumerableモジュール:繰り返し処理を行うためのモジュール
- Comparableモジュール:比較演算を行うためのモジュール
- Kernelモジュール:基本的なメソッドを提供するためのモジュール
これらのクラスやMix-inは、Rubyプログラムの中で頻繁に使用されるため、覚えておくと便利です。詳細は公式ドキュメントを参照してください。
列挙(Enumerator)
RubyのEnumeratorクラスは、反復処理を行うためのクラスです。配列やハッシュなどのオブジェクトに対して繰り返し処理を行うことができます。
例えば、以下のようなコードを考えてみましょう。
array = [1, 2, 3, 4, 5] array.each do |num| puts num end
この場合、eachメソッドを使用して、配列の要素を順番に取り出していることがわかります。しかし、この方法では要素の途中で処理を中断したり、要素を複数回利用することができません。
ここでEnumeratorクラスを使用すると、以下のように書くことができます。
array = [1, 2, 3, 4, 5] enum = array.to_enum loop do num = enum.next puts num end
この場合、to_enumメソッドを使用して、Enumeratorオブジェクトを生成しています。そして、nextメソッドを使用して、順番に要素を取り出しています。この方法を使用することで、要素の途中で処理を中断することができますし、要素を複数回利用することもできます。
Enumeratorを使ったジェネレータ
def primegen n = 2 sieve = [] Enumerator.new do |y| loop do if sieve[n].nil? || sieve[n] == 0 y << n sieve[n * n] = n else factor = sieve[n] sieve[n + factor] = factor sieve[n] = 0 end n += 1 end end.lazy end p primegen.first(10) #=> [2, 3, 5, 7, 11, 13, 15, 17, 19, 21] primegen.each_with_index do |prime, index| break if index > 5 puts "#{index}: #{prime}" end # => 0: 2 # 1: 3 # 2: 5 # 3: 7 # 4: 11 # 5: 13
%記法
Rubyの%記法は、文字列や正規表現、配列、シンボルなどを作成するための簡潔な書き方のことを指します。%記法を使うことで、コードの可読性を高めることができます。
以下に、Rubyの%記法の使い方を表にまとめました。
%記法 記号 役割 例 %q シングルクォートで囲んだ文字列を作成する %q(Hello, world!)
=>"Hello, world!"
%Q ダブルクォートで囲んだ文字列を作成する %Q(Hello, #{name}!)
=>"Hello, Alice!"
%r 正規表現リテラルを作成する %r(\d+-\d+-\d+)
=>/\\d+-\\d+-\\d+/
%w 単語の配列を作成する %w(apple banana orange)
=>["apple", "banana", "orange"]
%W ダブルクォートで展開される単語の配列を作成する %W(#{fruit1} #{fruit2} #{fruit3})
=>["apple", "banana", "orange"]
%s シンボルを作成する %s(hello)
=>:hello
%i シンボルの配列を作成する %i(apple banana orange)
=>[:apple, :banana, :orange]
上の例ではパッターンを丸括弧()
で囲みましたが、波括弧{}
・角括弧[]
で囲むことも出来、括弧でない文字(例えば!
)で始めたら、同じ文字(この場合は!
)で閉じることが出来ます。
# {} を使う例 puts %q{Hello, #{name}!} # => Hello, Ruby! # [] を使う例 puts %w[apple banana orange] # => ["apple", "banana", "orange"] puts %i[foo bar baz] # => [:foo, :bar, :baz] # ! を使う例 puts %Q!It's raining outside! # => It's raining outside puts %w!apple banana orange! # => ["apple", "banana", "orange"] # | を使う例 puts %Q|Hello, #{name}!| # => Hello, Ruby! puts %i|foo bar baz| # => [:foo, :bar, :baz]
これらの例では、%記法の丸括弧 ()
の部分を、波括弧 {}
、角括弧 []
、感嘆符 !
、縦棒 |
に置き換えています。%記法では、括弧以外にも様々な文字を使うことができますが、適切な文字を選んで使用することが重要です。
標準クラス階層の一覧化
def print_subclasses(klass, indent = 0) subclasses = klass.subclasses return if subclasses.empty? subclasses.to_a.sort{|a,b| a.to_s <=> b.to_s}.each do |subclass| puts "#{"\t" * indent}#{subclass}" print_subclasses(subclass, indent + 1) end end # <code>Object</code>から再帰的にサブクラスを表示 print_subclasses(Object.superclass)
- このRubyのコードは、与えられたクラスのサブクラスを再帰的に表示するものです。以下はコードの解説です:
print_subclasses
メソッドは、与えられたクラス(klass
)のサブクラスを再帰的に表示します。indent
はインデントレベルを表し、デフォルト値は0です。subclasses
メソッドは、指定されたクラスの直接のサブクラスを返します。sort
メソッドは、サブクラスをアルファベット順に並べ替えます。each
メソッドは、サブクラスを1つずつ処理し、その名前を表示します。print_subclasses
メソッドを再帰的に呼び出すことで、サブクラスのサブクラスも同様に処理されます。Object.superclass
は、すべてのクラスの祖先であるObject
クラスから始まります。- 最後に、
print_subclasses(Object.superclass)
を呼び出すことで、すべてのクラスの階層構造を表示します。
次は、Rubyの標準クラス階層を再帰的に表示するものです。各クラスはその直接のサブクラスとしてインデントされて表示されます。以下に各部分の解説を示します:
Object
クラスはRubyのほとんどのクラスの祖先となる基本クラスです。ARGF.class
: コマンドライン引数やファイルからの入力を扱うクラスです。Array
: 配列を表すクラスです。Binding
: メソッド呼び出しのコンテキストを保持するオブジェクトを表すクラスです。Complex::compatible
: 複素数を表すクラスです。DidYouMean
以下のクラス群: ユーザーの入力ミスに対する修正候補を提案する機能を提供するモジュールです。Dir
: ディレクトリを扱うクラスです。Encoding
以下のクラス群: 文字エンコーディングを扱うクラスです。Enumerator
以下のクラス群: 列挙可能なオブジェクトを生成するためのクラスです。ErrorHighlight
以下のクラス群: エラーメッセージをハイライトする機能を提供するモジュールです。Exception
以下のクラス群: 例外を扱うためのクラスです。FalseClass
: 真でない値を表すクラスです。Fiber
: コルーチンを表すクラスです。File::Stat
: ファイルの状態を表すクラスです。Gem
以下のクラス群: RubyGemsを扱うためのクラスです。Hash
: ハッシュを表すクラスです。IO
以下のクラス群: 入出力を扱うクラスです。MatchData
: 正規表現のマッチング結果を表すクラスです。Method
: メソッドオブジェクトを表すクラスです。Module
以下のクラス群: モジュールを表すクラスです。Monitor
以下のクラス群: スレッドセーフな処理を実現するためのクラスです。NameError::message
: 名前エラーのメッセージを表すクラスです。NilClass
:nil
を表すクラスです。Numeric
以下のクラス群: 数値を表すクラスです。Integer
整数を表すクラスです。Complex
複素数を表すクラスです。Rational
有理数を表すクラスです。
ObjectSpace::WeakMap
: 弱参照を使ったハッシュマップを表すクラスです。Proc
: プロシージャやラムダを表すクラスです。Process::Status
: プロセスのステータスを表すクラスです。Ractor
以下のクラス群: Rubyの並行処理を実現するためのクラスです。Random::Base
以下のクラス群: 乱数生成を行うためのクラスです。Range
: 範囲を表すクラスです。Regexp
: 正規表現を表すクラスです。RubyVM
以下のクラス群: Rubyの仮想マシンを扱うクラスです。String
: 文字列を表すクラスです。Struct
: 構造体を表すクラスです。Symbol
: シンボルを表すクラスです。Thread
以下のクラス群: スレッドを扱うクラスです。ThreadGroup
: スレッドグループを表すクラスです。Time
以下のクラス群: 時刻を表すクラスです。TracePoint
: トレースポイントを表すクラスです。TrueClass
: 真の値を表すクラスです。UnboundMethod
: バインドされていないメソッドを表すクラスです。Ractor::MovedObject
: 移動されたオブジェクトを表すクラスです。
これはRubyの標準ライブラリで定義されたクラス階層を表しており、各クラスがどのような階層構造にあるかを示しています。
- 結果
Object ARGF.class Array Binding Complex::compatible DidYouMean::ClassNameChecker DidYouMean::DeprecatedMapping DidYouMean::Formatter DidYouMean::KeyErrorChecker DidYouMean::MethodNameChecker DidYouMean::NullChecker DidYouMean::PatternKeyNameChecker DidYouMean::RequirePathChecker DidYouMean::SpellChecker DidYouMean::TreeSpellChecker DidYouMean::VariableNameChecker Dir Encoding Encoding::Converter Enumerator Enumerator::ArithmeticSequence Enumerator::Chain Enumerator::Lazy Enumerator::Generator Enumerator::Producer Enumerator::Yielder ErrorHighlight::DefaultFormatter ErrorHighlight::Spotter Exception ErrorHighlight::Spotter::NonAscii NoMemoryError ScriptError LoadError Gem::LoadError Gem::ConflictError Gem::MissingSpecError Gem::MissingSpecVersionError NotImplementedError SyntaxError SecurityError SignalException Interrupt StandardError ArgumentError Gem::Requirement::BadRequirementError UncaughtThrowError EncodingError Encoding::CompatibilityError Encoding::ConverterNotFoundError Encoding::InvalidByteSequenceError Encoding::UndefinedConversionError FiberError IOError EOFError IndexError KeyError StopIteration ClosedQueueError Ractor::ClosedError LocalJumpError Math::DomainError NameError NoMethodError NoMatchingPatternError NoMatchingPatternKeyError RangeError FloatDomainError RegexpError RuntimeError FrozenError Gem::Exception Gem::CommandLineError Gem::DependencyError Gem::DependencyResolutionError Gem::UnsatisfiableDependencyError Gem::DependencyRemovalException Gem::DocumentError Gem::EndOfYAMLException Gem::FilePermissionError Gem::FormatException Gem::GemNotFoundException Gem::SpecificGemNotFoundException Gem::GemNotInHomeException Gem::ImpossibleDependenciesError Gem::InstallError Gem::RuntimeRequirementNotMetError Gem::InvalidSpecificationException Gem::OperationNotSupportedError Gem::RemoteError Gem::RemoteInstallationCancelled Gem::RemoteInstallationSkipped Gem::RemoteSourceException Gem::RubyVersionMismatch Gem::UninstallError Gem::UnknownCommandError Gem::VerificationError Gem::WebauthnVerificationError IO::Buffer::AccessError IO::Buffer::AllocationError IO::Buffer::InvalidatedError IO::Buffer::LockedError Ractor::Error Ractor::IsolationError Ractor::MovedError Ractor::RemoteError Ractor::UnsafeError SystemCallError Errno::E2BIG Errno::EACCES Errno::EADDRINUSE Errno::EADDRNOTAVAIL Errno::EADV Errno::EAFNOSUPPORT Errno::EAGAIN IO::EAGAINWaitReadable IO::EAGAINWaitWritable Errno::EALREADY Errno::EBADE Errno::EBADF Errno::EBADFD Errno::EBADMSG Errno::EBADR Errno::EBADRQC Errno::EBADSLT Errno::EBFONT Errno::EBUSY Errno::ECANCELED Errno::ECHILD Errno::ECHRNG Errno::ECOMM Errno::ECONNABORTED Errno::ECONNREFUSED Errno::ECONNRESET Errno::EDEADLK Errno::EDESTADDRREQ Errno::EDOM Errno::EDOTDOT Errno::EDQUOT Errno::EEXIST Errno::EFAULT Errno::EFBIG Errno::EHOSTDOWN Errno::EHOSTUNREACH Errno::EHWPOISON Errno::EIDRM Errno::EILSEQ Errno::EINPROGRESS IO::EINPROGRESSWaitReadable IO::EINPROGRESSWaitWritable Errno::EINTR Errno::EINVAL Errno::EIO Errno::EISCONN Errno::EISDIR Errno::EISNAM Errno::EKEYEXPIRED Errno::EKEYREJECTED Errno::EKEYREVOKED Errno::EL2HLT Errno::EL2NSYNC Errno::EL3HLT Errno::EL3RST Errno::ELIBACC Errno::ELIBBAD Errno::ELIBEXEC Errno::ELIBMAX Errno::ELIBSCN Errno::ELNRNG Errno::ELOOP Errno::EMEDIUMTYPE Errno::EMFILE Errno::EMLINK Errno::EMSGSIZE Errno::EMULTIHOP Errno::ENAMETOOLONG Errno::ENAVAIL Errno::ENETDOWN Errno::ENETRESET Errno::ENETUNREACH Errno::ENFILE Errno::ENOANO Errno::ENOBUFS Errno::ENOCSI Errno::ENODATA Errno::ENODEV Errno::ENOENT Errno::ENOEXEC Errno::ENOKEY Errno::ENOLCK Errno::ENOLINK Errno::ENOMEDIUM Errno::ENOMEM Errno::ENOMSG Errno::ENONET Errno::ENOPKG Errno::ENOPROTOOPT Errno::ENOSPC Errno::ENOSR Errno::ENOSTR Errno::ENOSYS Errno::ENOTBLK Errno::ENOTCONN Errno::ENOTDIR Errno::ENOTEMPTY Errno::ENOTNAM Errno::ENOTRECOVERABLE Errno::ENOTSOCK Errno::ENOTSUP Errno::ENOTTY Errno::ENOTUNIQ Errno::ENXIO Errno::EOVERFLOW Errno::EOWNERDEAD Errno::EPERM Errno::EPFNOSUPPORT Errno::EPIPE Errno::EPROTO Errno::EPROTONOSUPPORT Errno::EPROTOTYPE Errno::ERANGE Errno::EREMCHG Errno::EREMOTE Errno::EREMOTEIO Errno::ERESTART Errno::ERFKILL Errno::EROFS Errno::ESHUTDOWN Errno::ESOCKTNOSUPPORT Errno::ESPIPE Errno::ESRCH Errno::ESRMNT Errno::ESTALE Errno::ESTRPIPE Errno::ETIME Errno::ETIMEDOUT Errno::ETOOMANYREFS Errno::ETXTBSY Errno::EUCLEAN Errno::EUNATCH Errno::EUSERS Errno::EXDEV Errno::EXFULL Errno::NOERROR ThreadError TypeError ZeroDivisionError SystemExit Gem::SystemExitException SystemStackError fatal FalseClass Fiber File::Stat Gem::BasicSpecification Gem::Specification Gem::StubSpecification Gem::Dependency Gem::ErrorReason Gem::PlatformMismatch Gem::SourceFetchProblem Gem::List Gem::PathSupport Gem::Platform Gem::Requirement Gem::StubSpecification::StubLine Gem::UnknownCommandSpellChecker Gem::Version Hash IO File IO::Buffer MatchData Method Module Class Refinement Monitor MonitorMixin::ConditionVariable NameError::message NilClass Numeric Complex Float Integer Rational ObjectSpace::WeakMap Proc Process::Status Ractor Random::Base Random Range Rational::compatible Regexp RubyVM RubyVM::AbstractSyntaxTree::Node RubyVM::InstructionSequence String DidYouMean::ClassNameChecker::ClassName Warning::buffer Struct #<Class:0x0000798f28922be8> #<Class:0x0000798f2902c5b0> Process::Tms Symbol Thread Process::Waiter Thread::Backtrace Thread::Backtrace::Location Thread::ConditionVariable Thread::Mutex Thread::Queue Thread::SizedQueue ThreadGroup Time Time::tm TracePoint TrueClass UnboundMethod Ractor::MovedObject
RDoc
RDocとは、Rubyドキュメンテーションの生成ツールであり、Rubyのプログラムに対する文書化を支援するためのツールです。RDocを使用すると、Rubyのプログラムに関する情報をドキュメントとして出力することができます。これにより、ユーザーは作成されたコードを理解するための情報が提供され、開発者は自分たちのコードを文書化することができます。RDocは、Rubyの標準ライブラリに含まれています。
構文
RDocは、Rubyのための文書作成システムで、コード内で注釈コメントを書くことで、自動的にドキュメントを生成します。以下はRDocの構文についての解説です。
RDocの注釈コメントは、通常のRubyコメントとして記述されますが、一つ上に「#」の代わりに「=begin」と「=end」を使ってマークアップを開始し、終了します。
以下は、RDocのマークアップの例です。
=begin This class does some cool things. == Example An example of how to use this class: MyClass.new.do_cool_thing == Copyright Copyright (c) 2022 John Doe. All rights reserved. =end
RDocは、以下の形式のマークアップタグをサポートしています。
#
: コメント=begin
/=end
: ドキュメントの開始/終了==
: セクションの開始*
: 箇条書きの開始+
: リストの開始-
: リストの終了!>
: コードブロックの開始<pre>
/</pre>
: コードブロックの開始/終了+methodName+(args)+
: メソッドの定義(arg)
,(arg=default)
: 引数の指定returns (type)
: 戻り値の指定\
: ラインコンティニュー
RDocの実行
RDocの実行には、次のコマンドを使用します。
$ rdoc [options] [names...]
オプションには、ドキュメンテーションを生成するFormattersや、ドキュメント化するファイルの指定などが含まれます。names
には、ドキュメンテーションを生成するファイルまたはディレクトリを指定します。
以上が、RDocの構文解説です。
附録
Rubyの変更の概要
Ruby-3.3.0
Ruby 3.3.0では、多くの重要な変更が導入されました。その中には、言語機能の改善、コアクラスの更新、標準ライブラリの更新、C APIの更新、実装の改善などが含まれます。
- 言語機能の変更:
-
- 匿名の可変長引数とキーワード可変長引数が引数として渡せるようになりました。
- Procオブジェクトのdupとcloneメソッドが#initialize_dupおよび#initialize_cloneフックを呼び出すようになりました。
- "Find pattern" が実験的ではなくなりました。
- ブロック内での匿名パラメータの転送が制限されました。
# Ruby 3.3.0での変更点の例 # 匿名の可変長引数とキーワード可変長引数が引数として渡せるようになりました。 def example(*args, **kwargs) p args p kwargs end example(1, 2, 3, key1: 'value1', key2: 'value2') # Procオブジェクトのdupとcloneメソッドが#initialize_dupおよび#initialize_cloneフックを呼び出すようになりました。 proc_object = proc { |x| x * 2 } cloned_proc = proc_object.clone p cloned_proc.call(5) # => 10 # Range#overlap?メソッドが追加されました。 range1 = 1..5 range2 = 4..8 p range1.overlap?(range2) # => true
- Core classesの更新:
-
- Array#packとString#unpackが未知のディレクティブに対してArgumentErrorを発生させるようになりました。
- メソッドRange#overlap?が追加され、2つの範囲が重なっているかどうかを確認できるようになりました。
- 標準ライブラリの更新:
-
- RubyGemsとBundlerが、必要なgemをGemfileやgemspecに追加せずにrequireした場合に警告を発生させるようになりました。
- 多くの標準ライブラリのバージョンが更新されました。
- C APIの更新:
-
- 新しいAPIが導入されいくつかのAPUが廃止されました。
- Postponed job APIが変更され、新しいAPIが追加されました。
- 実装の改善:
-
- パーサーがBisonからLrama LALRパーサージェネレーターに置き換えられました。
- GCとメモリ管理のパフォーマンスが大幅に向上しました。
- YJITのパフォーマンスとメモリ使用量が改善されました。
- MJITが削除され、代わりにRJITが導入されました。
これらの変更により、Ruby 3.3.0はより高速で効率的なバージョンになりました。
Ruby-3.2.0
Ruby 3.2.0では、いくつかの重要な変更と新機能が導入されています。以下に概要を示します:
- 言語の変更:
- 匿名の可変長引数とキーワード可変長引数が引数として渡せるようになりました。
- 単一の位置引数とキーワードを受け入れるProcは、自動的にスプラットしなくなりました。
- 明示的なオブジェクトに対する定数の割り当て評価順序が一貫性を持つようになりました。
- "Find pattern" が実験的ではなくなりました。
- restパラメータを持つメソッドが
*args
を通じてキーワード引数を委譲する場合、ruby2_keywords
でマークする必要があります。
# Ruby 3.2.0の言語の変更 # 匿名の可変長引数とキーワード可変長引数が引数として渡せるようになりました。 # 例: def example(*args) p args end example(1, 2, 3) # => [1, 2, 3] # 単一の位置引数とキーワードを受け入れるProcは、自動的にスプラットしなくなりました。 # 例: proc_example = proc { |a, **kw| [a, kw] } p proc_example.call(1, x: 2, y: 3) # => [1, {:x=>2, :y=>3}] # 明示的なオブジェクトに対する定数の割り当て評価順序が一貫性を持つようになりました。 # 例: class Foo; end BAR = :baz Foo::BAR = BAR p Foo::BAR # => :baz # "Find pattern" が実験的ではなくなりました。 # restパラメータを持つメソッドが*argsを通じてキーワード引数を委譲する場合、ruby2_keywordsでマークする必要があります。 # 例: def target(**kw) p kw end ruby2_keywords def delegate(*args) target(*args) end delegate(a: 1, b: 2) # => {:a=>1, :b=>2}
- コアクラスの更新:
- 継承可能なファイバーストレージのための
Fiber.[]
とFiber.[]=
が導入されました。 - Thread、IO、Class、Data、Encoding、Enumerator、Exception、Hash、Integer、Kernel、MatchData、Module、Proc、Process、Regexp、Refinement、RubyVM::AbstractSyntaxTree、Set、String、Struct、Timeなどの新しいメソッドや機能が追加されました。
- 継承可能なファイバーストレージのための
- 標準ライブラリの更新:
- Bundler、CGI、Coverage、Date、ERB、FileUtils、IRB、Net::Protocol、Pathname、Socket、URIなどの更新と機能強化が行われました。
- C APIの更新:
- 更新されたAPIや非推奨APIの除去、PRNGやスレッドの計装などのための新しいAPIが導入されました。
- 実装の改善:
Kernel#autoload
における競合状態が解消されました。- Regexpマッチングのキャッシュベースの最適化が導入されました。
- 変数幅割り当てがデフォルトで有効化されました。
- オブジェクトの形状を考慮した新しいインスタンス変数のキャッシングメカニズムが導入され、JITコードの生成が改善されました。
- 命令シーケンスのマーキングやメジャーコレクションの高速化が実装されました。
- JITの改善:
- YJITが実験的な段階を抜け、複数のプラットフォーム(x86-64やarm64/aarch64など)をサポートするようになりました。
- YJITはオブジェクトの形状を最適化するように改善され、物理メモリの遅延割り当てが導入されました。
- MJITコンパイラがRubyで再実装され、フォークされたRubyプロセスで実行されるようになりました。
これらの変更と改善により、Rubyプログラミングのさまざまな側面でパフォーマンス、安定性、機能性が向上しました。ユーザーは、より良いエクスペリエンスと改善されたパフォーマンスのために、Ruby 3.2.0へのアップグレードを検討することが推奨されます。
Ruby-3.1.0
Ruby 3.1.0の変更点は以下の通りです:
言語の変更点:
- ブロック引数が、ブロックが別のメソッドに渡される場合は匿名であってもよくなりました。
- ピン演算子は、式を受け取れるようになりました。
- ピン演算子は、インスタンス、クラス、グローバル変数をサポートするようになりました。
- 1行のパターンマッチングが実験的ではなくなりました。
- 複数の代入の評価順序が単一の代入の評価順序と一貫性が持たされるようになりました。
- ハッシュリテラルとキーワード引数では値を省略できるようになりました。
- main-Ractor以外のRactorも、共有可能なオブジェクトを参照する場合はクラス/モジュールのインスタンス変数を取得できるようになりました。
- 無限メソッド定義でもコマンド構文が許可されるようになりました。
# ブロック引数が、ブロックが別のメソッドに渡される場合は匿名であってもよくなりました。 def foo(&) bar(&) end # ピン演算子は、式を受け取れるようになりました。 result = Prime.each_cons(2).lazy.find_all{_1 in [n, ^(n + 2)]}.take(3).to_a #=> [[3, 5], [5, 7], [11, 13]] # ピン演算子は、インスタンス、クラス、グローバル変数をサポートするようになりました。 @n = 5 result = Prime.each_cons(2).lazy.find{_1 in [n, ^@n]} #=> [3, 5] # 1行のパターンマッチングが実験的ではなくなりました。 # Parenthesesを省略できるようになりました。 [0, 1] => _, x {y: 2} => y: x #=> 1 y #=> 2 # 複数の代入の評価順序が単一の代入の評価順序と一貫性が持たされるようになりました。 # Ruby 3.1.0以前 foo[0], bar.baz = a, b # 評価順序: a, b, foo, []= on foo, bar, []= on bar, baz= on bar # Ruby 3.1.0以降 # 評価順序: foo, bar, a, b, []= on foo, baz= on bar foo[0], bar.baz = a, b # ハッシュリテラルとキーワード引数では値を省略できるようになりました。 # 例: {x: x, y: y} を {x:, y:} と書けます。
- コアクラスの更新:
- Array、Class、Enumerable、Enumerator::Lazy、File、GC、Integer、Kernel、Marshal、MatchData、Method / UnboundMethod、Module、Process、Struct、String、Thread、Thread::Backtrace、Thread::Queue、Time、TracePointなどのクラスに変更が加えられました。
- 標準ライブラリの更新:
- RubyGems、base64、benchmark、bigdecimal、bundler、cgi、csv、date、did_you_mean、digest、drb、erb、error_highlight、etc、fcntl、fiddle、fileutils、find、io-console、io-wait、ipaddr、irb、json、logger、net-http、net-protocol、nkf、open-uri、openssl、optparse、ostruct、pathname、pp、prettyprint、psych、racc、rdoc、readline、readline-ext、reline、resolv、rinda、ruby2_keywords、securerandom、set、stringio、strscan、tempfile、time、timeout、tmpdir、un、uri、yaml、zlibなどの更新があります。
C APIの更新、実装の改善、JIT(MJIT、YJIT)、静的解析、デバッガ、エラーハイライト、IRBの自動補完、その他の変更が含まれます。
Ruby-3.0.0
Ruby 3.0.0には、多くの言語の変更点が含まれています。主な変更点を以下に示します:
- キーワード引数と位置引数の分離: キーワード引数は位置引数と区別されるようになりました。これにより、Ruby 2.7で警告が発生していたコードは、ArgumentErrorが発生するか、異なる動作をするようになります。
- Procの動作変更: 単一のrest引数とキーワードを受け入れるProcは、autosplattingの対象外となりました。これにより、単一のrest引数とキーワードを受け入れるProcの動作が統一されました。
- Arguments forwardingの改善: Arguments forwarding(...)が前方の引数をサポートするようになりました。
- パターンマッチングの改善: パターンマッチング(case/in)が実験的な段階から正式な機能に移行しました。また、一行のパターンマッチングも改良され、=>演算子が導入されました。
- Endless method definitionの導入: Endless method definitionが導入され、メソッドをより簡潔に定義することができるようになりました。
- 文字列補間リテラルの変更: # frozen-string-literal: trueを使用している場合、文字列補間リテラルが凍結されなくなりました。
- 静的解析の導入: 静的解析の基盤が導入され、RBSというRubyプログラムのための型定義言語が導入されました。また、TypeProfというRubyプログラムのための型解析ツールも実験的な形で提供されます。
- 非推奨の警告の変更: 非推奨の警告はデフォルトで表示されなくなりました。
- $SAFEと$KCODEの変更: $SAFEと$KCODEは通常のグローバル変数として振る舞い、関連するC-APIメソッドが削除されました。
- yieldの変更: メソッド内のシングルトンクラス定義でのyieldは、警告ではなくSyntaxErrorとなりました。
- クラス変数のアクセスの変更: 同じ定義でクラス変数が上書きされた場合、RuntimeErrorが発生するようになりました。
- 番号付きパラメータへの代入の変更: 番号付きパラメータへの代入は、警告ではなくSyntaxErrorとなりました。
# キーワード引数と位置引数の分離 def example(arg, kwarg:) puts arg puts kwarg end # ArgumentErrorが発生します # example(123, kwarg: 456) # Procの動作変更 # autosplattingの対象外となりました proc = ->(*rest, **kwargs) { [rest, kwargs] } # [rest, kwargs]がそのまま返されます p proc.call(1, 2, a: 3, b: 4) # Arguments forwardingの改善 def forwarder(*args, **kwargs) receiver(*args, **kwargs) end def receiver(a, b:, c: nil) puts "a: #{a}, b: #{b}, c: #{c}" end forwarder(1, b: 2, c: 3) # => a: 1, b: 2, c: 3 # パターンマッチングの改善 case [1, 2] in [1, x] puts x end # => 2 # Endless method definitionの導入 def foo = puts "Hello" foo # => Hello # 文字列補間リテラルの変更 # # frozen-string-literal: trueを使用している場合、文字列補間リテラルが凍結されなくなりました foo = "foo #{Time.now}" # 静的解析の導入 # 型定義言語RBSが導入され、型解析ツールTypeProfが提供されます # 非推奨の警告の変更 # デフォルトで表示されなくなりました # $SAFEと$KCODEの変更 # 通常のグローバル変数として振る舞い、関連するC-APIメソッドが削除されました # yieldの変更 # メソッド内のシングルトンクラス定義でのyieldは、警告ではなくSyntaxErrorとなりました # クラス変数のアクセスの変更 class MyClass @@var = 1 end # RuntimeErrorが発生します # MyClass.class_eval { @@var = 2 } # 番号付きパラメータへの代入の変更 # SyntaxErrorが発生します # _1 = 10
これらの変更点は、Ruby 3.0.0で導入された主な機能です。これにより、より安全で効率的なコーディングが可能になり、言語の進化が促進されました。
改廃された技術
Rubyの改廃された技術や利用が推奨されない技術は、言語の進化、パフォーマンスの向上、シンプルさの追求、新しいプログラミングパラダイムへの対応により置き換えられます。以下に、代表的な技術を示します。
taint
メソッド
- サポート開始年: 1997年(Ruby 1.0)
- サポート終了年: 2020年(Ruby 3.0で削除)
- 廃止または衰退の理由
- 信頼性を向上させるため、
taint
とtrust
メカニズムは非推奨となり削除されました。セキュリティモデルが変更されたことに伴います。 - 代替技術
- 特定のセキュリティ要件に応じて、コードレビューや他の安全な実装方法を採用してください。
= $==
グローバル変数
- サポート開始年: 1997年(Ruby 1.0)
- サポート終了年: 2007年(Ruby 1.9で削除)
- 廃止または衰退の理由
- 可読性の向上と、グローバル変数の乱用を避けるため削除されました。
- 代替技術
- 特定の機能に応じて、
Thread
またはFiber
ローカル変数を使用してください。
= Object#==~
メソッド
- サポート開始年: 1997年(Ruby 1.0)
- サポート終了年: 2018年(Ruby 2.5で削除)
- 廃止または衰退の理由
- 不要とされ、特定の意味を持たない
Object#=~
は削除されました。 - 代替技術
- カスタムクラスでマッチングを実装する場合は、
#===
や明示的なメソッドを実装してください。
Fixnum
と Bignum
クラス
- サポート開始年: 1997年(Ruby 1.0)
- サポート終了年: 2017年(Ruby 2.4で削除)
- 廃止または衰退の理由
- 一貫性を持たせるために、整数型が
Integer
に統一されました。 - 代替技術
Integer
クラスを使用してください。
Flip-flop
演算子
- サポート開始年: 1997年(Ruby 1.0)
- サポート終了年: 2020年(Ruby 3.0で削除)
- 廃止または衰退の理由
- コードの可読性が低く、多くのケースで誤解を招くため非推奨化されました。
- 代替技術
- 明示的な条件分岐や状態管理を用いて実装してください。
DL
ライブラリ
- サポート開始年: 2000年(Ruby 1.6)
- サポート終了年: 2014年(Ruby 2.2で削除)
- 廃止または衰退の理由
- より安全で使いやすい
Fiddle
ライブラリに置き換えられました。 - 代替技術
Fiddle
ライブラリを使用してください。
sprintf
における %
フォーマット指定子
- サポート開始年: 1997年(Ruby 1.0)
- サポート終了年: 2018年(Ruby 2.5で非推奨、3.0で削除)
- 廃止または衰退の理由
%
の意味があいまいで、誤用される可能性があるため削除されました。- 代替技術
- 明示的なフォーマット指定子(例:
%d
や%s
)を使用してください。
String#byteslice
の旧引数形式
- サポート開始年: 2001年(Ruby 1.6)
- サポート終了年: 2017年(Ruby 2.4で非推奨、2.6で削除)
- 廃止または衰退の理由
- 一貫性を保つため引数形式が変更されました。
- 代替技術
- 新しい引数形式(
String#byteslice(start, length)
)を使用してください。
旧式のプロック引数
- サポート開始年: 1997年(Ruby 1.0)
- サポート終了年: 2011年(Ruby 1.9で削除)
- 廃止または衰退の理由
- 一貫した引数形式を推進し、動作を明確化するため削除されました。
- 代替技術
- ブロック引数の新しい形式を使用してください(例:
proc { |x| ... }
)。
YAML における Symbol のデフォルトロード
- サポート開始年: 2005年(Ruby 1.8)
- サポート終了年: 2019年(Ruby 2.6で非推奨、3.0で削除)
- 廃止または衰退の理由
- セキュリティリスクを軽減するため、Symbolのデフォルトロードが廃止されました。
- 代替技術
safe_load
メソッドを使用し、Symbolのロードを手動で有効化してください。
コードギャラリー
エラトステネスの篩
エラトステネスの篩を、若干 Ruby らしく書いてみました。
- エラトステネスの篩
def eratosthenes(n) is_prime = [true] * (n + 1) is_prime[0] = false is_prime[1] = false (2..Math.sqrt(n)).each do |i| if is_prime[i] (i*i..n).step(i) do |j| is_prime[j] = false end end end primes = (2..n).select { |i| is_prime[i] } return primes end p eratosthenes(100)
- 実行結果
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
最大公約数と最小公倍数
最大公約数と最小公倍数を、若干 Ruby らしく書いてみました。
- 最大公約数と最小公倍数
def gcd2(m, n) = n == 0 ? m : gcd2(n, m % n) def gcd(*ints) = ints.reduce(&method(:gcd2)) def lcm2(m, n) = m * n / gcd2(m, n) def lcm(*ints) = ints.reduce(&method(:lcm2)) puts "gcd2(30, 45) => #{gcd2(30, 45)}" puts "gcd(30, 72, 12) => #{gcd(30, 72, 12)}" puts "lcm2(30, 72) => #{lcm2(30, 72)}" puts "lcm(30, 42, 72) => #{lcm(30, 42, 72)}"
- 実行結果
gcd2(30, 45) => 15 gcd(30, 72, 12) => 6 lcm2(30, 72) => 360 lcm(30, 42, 72) => 2520
二分法
二分法を、若干 Ruby らしく書いてみました。
- 二分法
def bisection(low, high, &f) x = (low + high) / 2.0 fx = f[x] return x if fx.abs < 1.0e-10 case fx <=> 0 when -1 then low = x when 1 then high = x end return bisection(low, high, &f) end puts bisection(0, 3) { |x| x - 1 } puts bisection(0, 3) { |x| x * x - 1 }
- 実行結果
0.9999999999417923 1.0000000000291038
- 旧課程(-2012年度)高等学校数学B/数値計算とコンピューター#2分法の例を Ruby に移植しました。