Ruby
オブジェクト指向プログラミング言語
From Wikipedia, the free encyclopedia
Ruby(ルビー)は、まつもとゆきひろ(通称: Matz)により開発された、簡潔な文法が特徴的なオブジェクト指向スクリプト言語[3]。
| パラダイム |
関数型プログラミング、命令型プログラミング、オブジェクト指向プログラミング、リフレクション |
|---|---|
| 登場時期 | 1995年 |
| 開発者 |
まつもとゆきひろ |
| 最新リリース |
4.0[1] |
| 型付け | 強い動的型付け, ダック・タイピング |
| 主な処理系 | MRI, YARV, JRuby, IronRuby, MacRuby |
| 影響を受けた言語 |
Ada、Dylan、Perl、Python、Smalltalk、C++、CLU、Eiffel、LISP、BASIC、Lua、Emacs |
| 影響を与えた言語 | D言語[2]、Groovy、Swift、Crystal、Scala、Elixir |
| プラットフォーム |
Microsoft Windows、Linux、*BSD、macOS |
| ライセンス |
Rubyライセンス、GPL 2.0、2条項BSDライセンス |
| ウェブサイト |
www |
| 拡張子 |
rb、rbw |
概要
Rubyは、1993年2月24日に生まれ、1995年12月にfjで発表された。
6月の誕生石Pearl(真珠)と同じ発音をするPerlに続くという意味で、7月の誕生石Ruby(ルビー)に由来する。
2011年3月22日、「JIS X 3017」に制定。
2012年4月1日、「ISO/IEC 30170」に承認、日本人が開発したプログラミング言語では初めて、国際電気標準会議(IEC)に認証された[4][5]。
2025年9月現在、言語仕様は明文化されていない[注釈 1]。
文字列・数値等すべてのデータをオブジェクトとして扱う。
クラス、ガベージコレクション、例外処理、Mixin、正規表現等の基本機能に、別途機能をRubyGemsで追加可能。
バージョン1.9.3以降、BSDとのデュアルライセンスで頒布されている。
設計思想
なぜ、Rubyを開発したのか[6]。
そのように問われるときに、もっとも適切な答えは、Linux開発者であるリーナス・トーバルズの言葉と同じではないかと思います。
Rubyの言語仕様策定において、最も重視しているのは、ストレスなくプログラミングを楽しむことである/enjoy programming[7]。
Ruby には Perl や Python とは決定的に違う点があり、それこそが Ruby の存在価値なのです。それは「楽しさ」です。私の知る限り、Ruby ほど「楽しさ」について焦点を当てている言語は他にありません。Ruby は純粋に楽しみのために設計され、言語を作る人、使う人、学ぶ人すべてが楽しめることを目的としています。しかし、ただ単に楽しいだけではありません。Ruby は実用性も十分です。実用性がなければ楽しめないではありませんか。
クラスと定数の先頭をアルファベット大文字に限定した仕様(基本設計思想)について、Matzは次のとおり説明している[8]。
2文字目以降は自由なので、もしどうしても日本語が使いたいのであれば、少々不自然にも見えますが、先頭だけ大文字の接頭辞をつけるのはどうでしょうか。しかし、私個人としては、日本語の変数名などを使うことは、そのプログラムを読む人の範囲を日本語が読める人に限定してしまうことになるので、ひどくもったいないのではないかと感じています。そこで、この点を積極的に改善する気にはなれないのです(#日本語による表記例も参照のこと)。
静的型について、Matzは(基本設計思想である動的型付けを維持し)『静的型付けを導入しない』意思を改めて示した[9]。
その代わり、静的解析を導入[10]し、型チェックを行えるようにすることを明らかにしました。(中略)静的解析に期待してほしい、とまつもとさんは述べました。
MINASWAN「Matzがniceだから俺らもniceでいよう」[11]
そしてRuby(中略)コミュニティを表現した標語に"MINASWAN"というものがあると紹介しました。これは"Matz is nice so we are nice"の略で、もめごとがあっても「matzがniceだから俺らもniceでいよう」と、なだめることが海外のメーリングリストではよく見られるそうです。(中略)今ではスローガンにまで昇華しています。(中略)日本のRubyコミュニティにこの特質を逆輸入したいと考え、講演の最初に取り上げたと述べました。
実装
コード例
ここでは、実用途に即したコード例を中心に記載する。クロスプラットフォームでの使用を考慮しているが、Unix系コマンドを利用できない環境では正常に動作しないことがある。なお、ウィキブックスには、より多くの基本的なコード例が記載されている。
はじめに
すべてのデータがオブジェクト
I1 = 123
p f1 = I1.to_f #=> 123.0 浮動小数点数に変換
p i1 = I1.to_i #=> 123 整数に変換
p s1 = I1.to_s #=> "123" 文字列に変換
p -f1.abs #=> 123.0
p -i1.abs #=> 123
p s1.length #=> 3
p s1.index("1") #=> 0
メソッドチェーン
メソッドを.で連結し順に処理[12]
p S1 = "Rubyは楽しい?" #=> "Rubyは楽しい?"
p S1.split(//) #=> ["R", "u", "b", "y", "は", "楽", "し", "い", "?"]
p S1.split(//).map{|v| v.gsub("?", "!!")} #=> ["R", "u", "b", "y", "は", "楽", "し", "い", "!!"]
p S1.split(//).map{|v| v.gsub("?", "!!")}.join("") #=> "Rubyは楽しい!!"
以下のように書くこともできる。
p "Rubyは楽しい?"
.split(//)
.map{|v| v.gsub("?", "!!")}
.join("")
#=> "Rubyは楽しい!!"
コメントアウト
#で始まる行は、1行コメント。
# コメント1
# コメント2
=begin ... =end行は、複数行コメント。
=begin
コメント1
コメント2
=end
おまじない
ヘッダ情報の付与
#!/usr/bin/env ruby
#coding:utf-8
puts "本コードは、これ以降に書く。"
変数と定数
ローカル変数
小文字 または '_' で始まる識別子は ローカル変数
var = 123
p var = 456 #=> 456
_v = 123
p _v = 456 #=> 456
グローバル変数
'$' で始まる識別子は グローバル変数
$var = 123
p $var = 456 #=> 456
定数
アルファベット大文字 [A-Z]で始まる識別子は 定数
Var = 123
p Var = 456 #=> 警告: 定数 Var はすでに初期化されています
日本語による表記例
# 変数
p 変数 = "これは変数です"
p 変数 = "変数なので代入可能です"
# グローバル変数
p $変数 = "これはグローバル変数です"
p $変数 = "グローバル変数なので代入可能です"
# 定数
p S定数 = "これは定数です"
p S定数 = "定数なのでエラーが発生します"
コマンドライン引数
ARGV
# 本コードを「aaa.rb」として保存し、
# $ ruby aaa.rb -help -title="Hello, Ruby!" "Ruby was born in 1993."
# を実行したとき。
p ARGV #=> ["-help", "-title=Hello, Ruby!", "Ruby was born in 1993."]
ARGV.each do |_s1|
# オプションあり
if _s1 =~ /^-[a-z]/
_k, _v = _s1.split("=")
# 引数あり
if _v
puts "オプションあり/引数あり:#{_k} = #{_v}"
# 引数なし
else
puts "オプションあり/引数なし:#{_k}"
end
# オプションなし
else
puts "オプションなし:#{_s1}"
end
end
#=>
# オプションあり/引数なし:-help
# オプションあり/引数あり:-title = Hello, Ruby!
# オプションなし:Ruby was born in 1993.
配列
配列の作成と使用法
# 数値と文字列が混在する配列
A1 = [1, "UPCASE", 3.14, 1, 2, [2.0, 4], "downcase"]
# 要素を取得
p A1[2] #=> 3.14
p A1[0..2] #=> [1, "UPCASE", 3.14]
p A1[0, 2] #=> [1, "UPCASE"]
# 大文字・小文字を区別しないソート
p A1.sort_by{|v| v.to_s.downcase} #=> [1, 1, 2, 3.14, [2.0, 4], "downcase", "UPCASE"]
p A1.flatten.sort_by{|v| v.to_s.downcase} #=> [1, 1, 2, 2.0, 3.14, 4, "downcase", "UPCASE"]
p A1.flatten.uniq.sort_by{|v| v.to_s.downcase} #=> [1, 2, 2.0, 3.14, 4, "downcase", "UPCASE"]
# いずれも以下を出力
# ["A", "B C", "D"]
# 文字列を配列に変換
S1 = "A \n B C\nD\n"
p S1.split("\n").map{|v| v.strip}
# ヒアドキュメントを配列に変換
S2 = <<EOD
A
B C
D
EOD
p S2.each_line.map{|v| v.strip}
# %w記法で配列に変換
p %w(A B\ C D)
p %w(
A
B\ C
D
)
%w記法で都道府県コードの配列を作成する例
# [1..47] = ["北海道".."沖縄県"](一部抜粋)
p Apref = [nil] + %w(北海道 青森県 岩手県 宮城県) #=> [nil, "北海道", "青森県", "岩手県", "宮城県"]
printf("%02d: %s\n", 4, Apref[4]) #=> "04: 宮城県"
上記コードをクラス化する例(#現実世界の情報も参照のこと)
class Pref
def initialize()
@aPref = [nil] + %w(北海道 青森県 岩手県 宮城県)
end
def explain(id = 0)
sprintf("%02d: %s", id, @aPref[id])
end
end
pref = Pref.new
puts pref.explain(4) #=> "04: 宮城県"
ハッシュ
ハッシュの作成と使用法
h10 = {}
h10["water"] = "wet"
h10["fire"] = "hot"
h20 = {"water" => "wet", "fire" => "hot"}
h31 = {:water => "wet", :fire => "hot"}
h32 = {:"water" => "wet", :"fire" => "hot"}
h41 = {water: "wet", fire: "hot"}
h42 = {"water": "wet", "fire": "hot"}
# いずれも以下を出力
# "hot"
p h10["fire"]
p h20["fire"]
p h31[:fire]
p h32[:"fire"]
p h41[:fire]
p h42[:"fire"]
# いずれも以下を出力
# water: wet
# fire: hot
h10.each { |_k, _v| print _k, ": ", _v, "\n" }
h20.each { |_k, _v| print _k, ": ", _v, "\n" }
h31.each { |_k, _v| print _k, ": ", _v, "\n" }
h32.each { |_k, _v| print _k, ": ", _v, "\n" }
h41.each { |_k, _v| print _k, ": ", _v, "\n" }
h42.each { |_k, _v| print _k, ": ", _v, "\n" }
四則演算など
数値
p 1 + 2 #=> 3
p 1 - 2 #=> -1
p 1 * 2 #=> 2
p 1 / 2 #=> 0 期待値は 0.5 だが?
p 1.0 / 2 #=> 0.5 期待どおり。
p 1 ** 2 #=> 1
文字列
p "Ru" + "by" #=> "Ruby"
配列
p ["Ru"] + ["by"] #=> ["Ru", "by"]
p ["Ru", "by"] - ["Ru"] #=> ["by"]
p [1, 2, 3] * ":" #=> "1:2:3"/[1, 2, 3].join(":") と同義
制御構造
ほかの言語でもよくみられるような制御構造を用いることができる。
条件分岐
if, case
# [Ctrl]+[C]
Signal.trap(:INT) do
puts
exit
end
# キー入力
print "[Y/n] ? "
p s1 = STDIN.gets.strip.upcase
print "例1-1: "
if s1 == "Y"
puts true
elsif s1 == "N"
puts false
else
puts nil
end
print "例1-2: "
puts(
if s1 == "Y"
true
elsif s1 == "N"
false
else
nil
end
)
print "例2: "
puts s1 == "Y" ? true : nil
print "例3: "
print true if s1 == "Y"
puts
print "例4: "
puts(
case s1
when "Y"
true
when "N"
false
else
nil
end
)
ブロック
ブロックは { ... } または do ... end によって囲まれたコード列である。
一行で収まるときは { ... }、複数行にまたがるときは do ... end が使用される。
# { ... }
"Hello, Ruby!".split { |_s1| puts _s1 }
# do ... end
"Hello, Ruby!".split do |_s1|
puts _s1
end
gsub() の罠
S1 = "ABC-ABC"
# ok
# 期待どおり => "AびC-AびC"
p S1.gsub("B", "び")
# NG
# 期待 => "AびBC-AびBC"
# 結果 => "AびC-AびC"
p S1.gsub(/(B)/, "び#{$1}")
# ok
# 期待どおり => "AびBC-AびBC"
p S1.gsub(/(B)/){ "び#{$1}" }
繰り返し処理
for
a1 = [1, 2, 5, 13, 21]
a2 = []
for i1 in a1
a2 << i1 * 2
end
p a2 #=> [2, 4, 10, 26, 42]
while
a1 = [1, 2, 5, 13, 21]
i1 = 0
while i1 < a1.length
a1[i1] *= 2
i1 += 1
end
p a1 #=> [2, 4, 10, 26, 42]
each メソッド
a1 = [1, 2, 5, 13, 21]
a2 = []
a1.each { |i1| a2 << i1 * 2 }
p a1 #=> [1, 2, 5, 13, 21]
p a2 #=> [2, 4, 10, 26, 42]
a1.each.with_index(0) { |i1, i2| a1[i2] = i1 * 2 }
p a1 #=> [2, 4, 10, 26, 42]
map, map! メソッド
a1 = [1, 2, 5, 13, 21]
# map
a2 = a1.map { |i1| i1 * 2 }
p a1 #=> [1, 2, 5, 13, 21]
p a2 #=> [2, 4, 10, 26, 42]
# map!
a1.map! { |i1| i1 * 2 }
p a1 #=> [2, 4, 10, 26, 42]
times メソッド
a1 = [1, 2, 5, 13, 21]
a1.length.times { |i1| a1[i1] *= 2 }
p a1 #=> [2, 4, 10, 26, 42]
指定した回数の繰り返し処理
# 以下、いずれも "foofoofoo" を出力。
s1 = "foo"
3.times { print s1 }
puts
print (s1 * 3), "\n"
連続する数字の繰り返し処理
# 連続する数字の配列を作成
p (1..5).to_a #=> [1, 2, 3, 4, 5]
# 断続する数字をキーにした空のハッシュを作成
hi1 = {}
[0..5, 10, 20..21, 30].each do |e1|
if e1.class == Range
e1.each do |i1|
hi1[i1] = ""
end
else
hi1[e1] = ""
end
end
p hi1 #=> {0=>"", 1=>"", 2=>"", 3=>"", 4=>"", 5=>"", 10=>"", 20=>"", 21=>"", 30=>""}
p hi1[0] #=> ""
p hi1[99] #=> nil
# 上記コードをワンオフ向けに特化した例
hi1 = {}
[(0..5).to_a, 10, (20..21).to_a, 30].flatten.each do |i1|
hi1[i1] = ""
end
p hi1 #=> {0=>"", 1=>"", 2=>"", 3=>"", 4=>"", 5=>"", 10=>"", 20=>"", 21=>"", 30=>""}
ファイルの読み書き
テキストファイル
# 入力ファイル
IFn = __FILE__
# 出力ファイル
OFn = "output.txt"
s1 = ""
s2 = ""
# メモリに余裕があるなら、全行読み込んで処理。
File.read(IFn).each_line do |_s1|
if _s1.match("全行")
s1 << _s1
end
end
# メモリに余裕がないときは、一行ずつ読み込んで処理。
File.open(IFn, "r") do |_fs|
_fs.each_line do |_s2|
if _s2.match("一行ずつ")
s2 << _s2
end
end
end
# 書き込み
File.write(OFn, s1)
# 追加書き込み
File.open(OFn, "a") do |_fs|
_fs.write s2
end
# OS判定
# Cドライブが存在すれば Windows
# 上記以外は Unix系
Cmd = Dir.exist?("c:/") ? "type" : "cat"
puts %x(#{Cmd} #{OFn})
バイナリファイル
テキストファイルと異なるメソッド等
File.read(... => File.binread(...
File.open(..., "r") => File.open(..., "rb")
File.open(..., "w") => File.open(..., "wb")
File.open(..., "a") => File.open(..., "ab")
その他、マルチバイト文字を扱うときは、エンコーディングが必要になる。
URL
http経由でテキストデータを読み込む例(#例外処理、#gsub() の罠も参照のこと)
require 'open-uri'
# URL
Url = "https://ja.wikipedia.org/w/index.php?title=Ruby&action=history&offset=&limit=5"
# 検索文字列
Search = "「Ruby」の変更履歴"
begin
# 全行をメモリに先読みした後、一行ずつ処理する。
URI.open(Url).read.each_line.with_index(1) do |_s1, _i1|
# 文字列検索
if _s1.match(Search)
_s1.strip!
print(
# 行番号
"\033[94mL#{_i1}\033[0m\t",
# 一致した文字列を色付け表示
_s1.gsub(Search, "\033[95m#{Search}\033[0m"),
"\n"
)
end
end
rescue => e
p e
end
正規表現
正規表現による検索例
# 検索対象文字列
p S1 = "12345 ABCdef あいうえおかきくけこ"
# 検索文字列(正規表現)
p Rgx1 = /(\d{3}).*?([A-z]+).*?(あ.+お)/
# 検索実行
if S1 =~ Rgx1
# $& で結果取得
print "全体: ", $&, "\n" #=> "12345 ABCdef あいうえお"
# $~[1..] で部分一致の配列取得
print "部分: ", $~[1..], "\n" #=> ["123", "ABCdef", "あいうえお"]
end
クラス
既存クラス
既存のStringクラスのメソッドの「書き換え」と「新規追加」を行う例
class String
# 既存メソッドの書き換え/危険!!
def to_s
self.swapcase
end
# 新規メソッド追加
def foo
self.split(//).join(" ")
end
end
s1 = "Hello, Ruby!"
puts s1 #=> "Hello, Ruby!"
puts s1.to_s #=> "hELLO, rUBY!"/本来は "Hello, Ruby!" を表示
puts s1.foo #=> "H e l l o , R u b y !"
現実世界の情報
「動物(Animal)」の「種類(breed)」と「鳴き声(bark)」をクラス化する例
class Animal
def initialize(breed, bark)
@breed, @bark = breed, bark
end
def explain
"#{@breed}は「#{@bark}」と鳴く。"
end
end
#-------------------------------------------------
#(例1)定数に格納
# 静的データ限定。
#-------------------------------------------------
Dog = Animal.new("犬", "わんわん")
Cat = Animal.new("猫", "にゃー")
puts Dog.explain #=> "犬は「わんわん」と鳴く。"
puts Cat.explain #=> "猫は「にゃー」と鳴く。"
#-------------------------------------------------
#(例2)ハッシュに格納
# 動的データを扱うとき、使い勝手がよい。
#-------------------------------------------------
List1 = <<EOD
Dog1 犬1 わんわん
Cat1 猫1 にゃー
EOD
hAnimal = {}
List1.each_line do |_s1|
_key, _breed, _bark = _s1.strip.split("\t")
hAnimal[_key] = Animal.new(_breed, _bark)
end
puts hAnimal["Dog1"].explain #=> "犬1は「わんわん」と鳴く。"
puts hAnimal["Cat1"].explain #=> "猫1は「にゃー」と鳴く。"
#-------------------------------------------------
#(例3)evalで動的に生成した定数に格納
# 参考程度。
#-------------------------------------------------
List2 = <<EOD
Dog2 犬2 わんわん
Cat2 猫2 にゃー
EOD
List2.each_line do |_s1|
_key, _breed, _bark = _s1.strip.split("\t")
eval "#{_key} = Animal.new('#{_breed}', '#{_bark}')"
end
puts Dog2.explain #=> "犬2は「わんわん」と鳴く。"
puts Cat2.explain #=> "猫2は「にゃー」と鳴く。"
仮想データ
コマンドラインインタプリタ上のカーソル操作をクラス化する例
class Cursor
def up(n)
(n - 1).times { print "*\033[1D\033[1A" }
print "*\033[1D"
sleep 1
end
def down(n)
(n - 1).times { print "*\033[1D\033[1B" }
print "*\033[1D"
sleep 1
end
def right(n)
n.times { print "*\033[1C" }
print "\033[2D"
sleep 1
end
def left(n)
n.times { print "*\033[3D" }
print "\033[2C"
sleep 1
end
end
cursor = Cursor.new
cursor.down(6)
cursor.right(6)
cursor.up(4)
cursor.left(4)
cursor.down(4)
puts
インスタンス変数へアクセス
クラス内のインスタンス変数 @foo にアクセスするための定義例
attr_reader読込メソッドattr_writer書込メソッドattr_accessor読込/書込メソッド
class Gemstone
def initialize(id = 0, name = "")
@id, @name = id, name
end
# 読込メソッド生成 @id, @name
attr_reader :id, :name
# def id
# @id
# end
# def name
# @name
# end
# 書込メソッド生成 @name
attr_writer :name
# def name=(name)
# @name = name
# end
# 上記は、
# attr_reader :id
# attr_accessor :name
# とも書ける。
end
# 初期化 @id, @name
gemstone = Gemstone.new(123, "?")
p gemstone.id #=> 123
p gemstone.name #=> "?"
# データ書き換え @name
gemstone.name = "Ruby"
p gemstone.name #=> "Ruby"
例外処理
想定外のエラーが発生したとき、エラーを分別して実行を継続する。
begin
# 処理
rescue error_type => e
# error_type の例外あり
p e
rescue => e
# 例外あり
p e
else
# 例外なしのとき実行
ensure
# 必ず実行
end
例外なし
# 下記を出力
# Hello Ruby!
# else
# ensure
begin
puts "Hello Ruby!"
rescue => e
puts "rescue", e
else
puts "else"
ensure
puts "ensure"
end
例外は raise ... でも発火できる。
例外あり raise "エラー"
# 下記を出力
# rescue
# エラー
# ensure
begin
raise "エラー"
rescue => e
puts "rescue", e
else
puts "else"
ensure
puts "ensure"
end
例外あり raise ArgumentError, "Argument エラー"
# 下記を出力
# ArgumentError rescue
# Argument エラー
# ensure
begin
raise ArgumentError, "Argument エラー"
rescue ArgumentError => e
puts "ArgumentError rescue", e
rescue => e
puts "rescue", e
else
puts "else"
ensure
puts "ensure"
end
外部コマンド等の利用
Ruby on Railsが有名になったため、Rubyを書いたことがない人は「Rubyは敷居が高い」と敬遠するかもしれないが、それは誤解である。Rubyは「小さなことを少しの努力/Doing small things with little effort」で実装できる言語の一つであり、ちょっとしたプロトタイピング開発やシェルスクリプトの代替に向いている。
「少ない労力でより多くの成果を/Do more with less」
以下、外部コマンド等(コマンド、実行ファイル、スクリプト言語)のうち、Unix系コマンドを利用したコード例を記す。
(例1)実行/成功可否(true, false)を取得。
bool = system("...")
falseコマンドを実行
p b1 = system("false") #=> false
if ! b1
STDERR.puts "FALSE" #=> "FALSE"
end
(例2)実行/結果を取得。
string = %x(...)
echoコマンドを実行
p s1 = %x(echo) #=> "\n"
if s1.strip.length == 0
STDERR.puts "EMPTY" #=> "EMPTY"
end
実行中のRubyスクリプトのソースコード各行に連番を付与
print %x(cat #{__FILE__} | nl -ba -w1)
(参考)Rubyのみで実装
File.read(__FILE__).each_line.with_index(1) do |_s1, _i1|
print _i1, "\t", _s1
end
ホームディレクトリの隠しファイルを再帰抽出し、ディレクトリとファイルを配列にする例
a1 = []
# "~/" を絶対パス "/home/foo" に変換
sAbsPath = File.expand_path("~/")
%x(find #{sAbsPath} -type f -name ".*").each_line do |_s1|
_s1.chomp!
# ファイル名直前の "/" 位置
i1 = _s1.rindex("/", -1)
a1 << [_s1[..(i1 - 1)], _s1[(i1 + 1)..]] #=>(例)["/home/foo", ".bashrc"]
end
# ディレクトリ順にソート
a1.sort.each { |_a1| print(_a1, "\n") }
(参考)Rubyのみで実装
# 直感的な理解を期待し、敢えて File.basename(), File.dirname() を使用した。
a1 = []
# "~/" を絶対パス "/home/foo" に変換
sAbsPath = File.expand_path("~/")
Dir.glob("**/*", File::FNM_DOTMATCH, base: sAbsPath).each do |_fn|
sPath = File.join(sAbsPath, _fn)
# ファイルか?
if FileTest.file?(sPath)
sFn = File.basename(sPath)
# "." で始まるファイル名か?
if sFn[0] == "."
a1 << [File.dirname(sPath), sFn] #=>(例)["/home/foo", ".bashrc"]
end
end
end
# ディレクトリ順にソート
a1.sort.each { |_a1| print(_a1, "\n") }
並列処理
Parallel gem[13] で並列処理する例
出力結果は「処理が完了する順序で異なる」ことに注意。
配列による実装例
require 'parallel'
CmdList = ["sleep 2", "ls -la", "sleep 8"]
# Windows版Ruby3.4現在、オプション in_processes: 未対応のようなので in_threads: を使用した。
Parallel.each(CmdList, in_threads: 4) do |_cmd|
system(_cmd.strip)
end
ヒアドキュメントによる実装例
require 'parallel'
CmdList = <<EOD
# Ruby
ruby -e 'print "Hello, Ruby!\\n"'
# Perl
perl -e 'print "Hello, Perl!\\n";'
# Python
python3 -c "print('Hello, Python!')"
EOD
Parallel.each(CmdList.each_line, in_threads: 4) do |_cmd|
system(_cmd.strip)
end
eval
Rubyスクリプトを動的に評価/実行するときは eval で実装(#例外処理も参照のこと)
require 'parallel'
def SubEval(_sec = 0)
sleep _sec # _sec 秒停止
puts _sec # _sec を表示
end
# 便宜上、ヒアドキュメントで記述します。
# 実用途では、標準入力/外部ファイルから読み込んだテキストデータを想定しています。
# 以下、意図的にエラーを混入しているので、実行して確認してみてください。
CmdList = <<EOD
SubNoExist()
SubEval("Ruby")
SubEval(0)
SubEval(10)
SubEval(5)
SubEval(1)
SubEval(2)
EOD
# 入力データに100%の信用がないときは、例外処理を実装。
Parallel.each(CmdList.each_line, in_threads: 4) do |_cmd|
_cmd.strip!
begin
eval(_cmd)
rescue => e
puts "#{_cmd} => #{e}"
end
end
フィボナッチ数
フィボナッチ数を求める例[14]。適正なアルゴリズムを使用することで処理速度が改善される事例は多い。
# 例えば、以下のコードが、"fib.rb"に保存されているとき、
# $ ruby ./fib.rb 10
# と実行。
def RtnFibIntr(num = 0)
if num == 0
return [1, 2]
end
if (num & 1) == 0
numHalf = (num / 2).to_i
iPm = ((numHalf & 1) == 0 ? 1 : -1)
f1, l1 = RtnFibIntr(numHalf)
l2 = (l1 * l1) - (2 * iPm)
f2 = (f1 * l1) - iPm
elsif (num % 8) == 7
f1, l1 = RtnFibIntr(num + 1)
f2 = (2 * f1) - l1
l2 = (3 * f2) - f1
else
f1, l1 = RtnFibIntr(num - 1)
f2 = (3 * f1) - l1
l2 = (2 * f2) - f1
end
return [f2, l2]
end
def SubFib(num = 0)
if num > 0
print num, "\t", RtnFibIntr(num - 1)[0], "\n"
end
end
def main()
if ARGV.length > 0
i1 = ARGV[0].to_i
SubFib(i1)
end
end
main()
不向きな処理
ベンチマークテストで使用される以下のようなコードを実行したとき、処理速度が著しく低下することがある。
i1 = 1000000
while i1 <= 1010000
i2 = i1 - 1
i3 = 2
while i3 <= i1
if (i1 % i3) == 0
break
elsif i3 == i2
puts i1.to_s
break
end
i3 += 1
end
i1 += 1
end
(参考)上記コードをC言語で記述した例(#外部コマンド等の利用も参照のこと)。
// 本コードを「aaa.c」として保存したとき、
// コンパイル例
// $ gcc ./aaa.c -o ./aaa.exe
// 実行例
// $ ./aaa.exe
#include <stdio.h>
int main()
{
unsigned int i1 = 1000000;
while(i1 <= 1010000)
{
unsigned int i2 = i1 - 1;
unsigned int i3 = 2;
while(i3 <= i1)
{
if((i1 % i3) == 0)
{
break;
}
else if(i3 == i2)
{
printf("%u\n", i1);
break;
}
i3 += 1;
}
i1 += 1;
}
return 0;
}
Rubyの周辺技術
- ActiveScriptRuby - WindowsのActiveX環境でRubyインタプリタを呼び出す(Internet Explorer限定だがHTMLに埋め込んでクライアント上で動かすスクリプト言語としてRubyを指定できるようになる)
- Apollo - Delphiを扱えるようにする(初稿: 2004-10-16)
- dRuby - 分散オブジェクト
- DXRuby - DirectXを使用するための拡張ライブラリ
- eRuby - HTMLにRubyスクリプトの埋め込みを可能にする
- Exerb - RubyスクリプトをWindowsの実行ファイルに変換
- homebrew - macOSパッケージ管理システム
- HotRuby - JavaScriptやAdobe Flash上で動くRuby処理系
- mod_ruby - CGIより高速なApache HTTP Serverのモジュール
- Opal - RubyスクリプトをJavaScriptへ変換するコンパイラ
- Rake - ビルドツール
- RD - Rubyスクリプトに埋め込む文書形式
- RDE - 統合開発環境(最終版: 2002-10-14)
- RSpec - ビヘイビア駆動開発のためのフレームワーク
- RubyGems - パッケージ管理システム
- Ruby on Rails - Webアプリケーションフレームワーク
- Ruby/SDL - SDLライブラリを扱えるようにする
- RWiki - RDを採用したウィキ
- Smalruby - ScratchのRuby対応版(Rubyソースを生成するScratch)とでも言うべきGUI型IDE
- YARV - 正式な処理系として採用(Ruby1.9.0以降)
- WIN32OLE - Win32APIやCOMコンポーネントを呼び出すためのライブラリ
Rubyで開発されたアプリケーション
- Chef - 構成管理ツール
- Hiki - ウィキソフトウェア
- mikutter - Twitterクライアント
- Phusion Passenger - Apache HTTP Server/nginx用モジュール
- Puppet - 構成管理ツール
- qwikWeb - メーリングリストとWikiの長所を合わせたようなシステム
- Ruby on Rails - Webアプリケーションフレームワーク
- Basecamp - Webベースのプロジェクト管理ツール
- GitHub - ソフトウェア開発プラットフォーム
- Metasploit - コンピュータセキュリティに関するプロジェクト
- Redmine - Webベースのプロジェクト管理ソフトウェア
- Serverspec - サーバの実状をテストするツール
- tDiary - Web日記
- Vagrant - 仮想マシンを構築するためのソフトウェア
- WEBrick - Webサーバの機能を提供するライブラリ
- 影舞 - Webベースのバグ管理システム
Rubyを組み込んだアプリケーション
エピソード
ブロック構造構文の選択理由
Ruby ではブロック構造を end で終える構文が採用されているが、Matzは他の構文が採用される可能性があったことを述べている。当時、Emacs 上で end で終える構文をオートインデントさせた例はあまりなく、Ruby 言語用の編集モードにオートインデント機能を持たせられるかどうかが問題になっていたためである[注釈 3]。実際には数日の試行でオートインデント可能であることがわかり、現在の構文になった。C言語のような{ ... }を使った構文も検討されていたが、結局これは採用されなかった[17]。
Matzが書いたコードの割合
「Quora (2020年9月8日)」より抜粋し補足[18]。
Rubyにおける標準的な実装であるCRubyでは、私はもう10年近く前(2010年頃)から主要な開発者としては脱落しています。言語デザイナーとしての活動がメインということですね。では、現在(2020年9月8日)のCRubyのソースコードに(往年の)私のコードがどのくらい残っているのか確認してみましょう。(中略)結果、現状のCコード509,802行(2020–09–08現在)のうち、私のコミットした行は、36,437行残ってました。1割も残っていませんが、正直なところ意外と多いなという印象です。一方、最近私がプログラマーとして関わっているmrubyで同様の方法で測定したところ、67,068行中、25,049行でした。mrubyでは他に代理コミットしてもらったぶんがありますから、それを加えると32,653行で、約半分ってところですかね。
Matzとゆかりのある地域
言語間競争とRuby
- Python に満足していれば Ruby は生まれなかった
- Ruby on Rails が Python ではなく Ruby で作られた理由 - 開発者のハンソンがRubyに恋をしたから
デイヴィッド・ハイネマイヤー・ハンソンが(2004年頃)Ruby on Railsを構築するのにPython(2.x?)を選ばなかった理由として「私の場合は、恋に落ちたのがRubyなのです。私はRubyに恋をしていますし、もう14年間もそうなのです。(中略)『最適なツール』などというものは存在しないのです。あなたの脳をちょうどいい具合に刺激するパズルがあるだけなのです。今日では、ほぼなんでも作ることができます。そして、それを使って、さらに何でも作れてしまうのです。これは素晴らしいことです。表現や言語、そして思考の多様性に乾杯しましょう!」と質問サイトのQuoraで本人が回答している[18]。
- 「Rubyは死んだ」のか? 検索ワード頻度では分からない Ruby の生産性やビジネス上の価値
「Ruby Business Users Conference 2018 Winter」(2018年12月14日)より抜粋[23]。
RubyとかRuby on Railsだと、簡単なWebアプリケーションをすぐ作ったり、あるいは、さまざまなジャンルで実際の適用例があるので、なにか困ったとき同じ問題に直面した人を探せたり、あるいはその問題を解決するRubyGemsを見つけられる。そういう点でいうと、トータルの生産性はかなり高いことがあるんですね。 なので、テクノロジーとしては、2010年代にどんどん新しく出てきた言語が持ってるあの機能がないとかこの機能がないとか、そういうカタログスペック上の欠点があるように思えても、トータルの生産性あるいは効率のよさを考えると、Ruby on Railsのビジネス上の価値は、実はそんなに下がっていないと思うんです。 先ほどのTIOBE Indexみたいなランキングは、技術者が新しいことを学ぶときに探すところで順位がついているので、ホットなトピックというんですか、新しく出てきて話題になっているものが上に来がちなんですよね。 だから、実際に仕事として、あるいは自分のプロダクトを作るときに、どんな言語を選択してどういうふうに開発したらいいのかを考えると、Rubyの持っているビジネス上の価値はそんなに下がっていないと思います。たとえ順位が下がって、表面上Rubyの人気が凋落したように見えても、ある意味「まだまだ大丈夫」が1つの見識だと思います。
注釈
- 「ISO/IEC 30170」による厳密な言語仕様はない。2010年6月以降、言語仕様をテストするRubySpecという外部ライブラリの開発が行われていたが、Ruby 2.2.0リリース直後の2014年12月31日、開発が突然終了された。Ruby 2.2.0が「RubySpecのテストをパスしない」等、MRI側によるRubySpec軽視が一因とされている。“【悲報】Ruby開発者が使わないので「RubySpec」開発終了”. 2025年9月16日閲覧。
- DRYは「Don't Repeat Yourself/同じことを繰り返さない」の意。Rubyのデータ型(オブジェクト)は、動的に型変換し再利用される(同じことを繰り返す)存在であって、静的に固定型で利用される(同じことを繰り返さない)存在ではない。
- 原文のママ。リファレンスマニュアル2.1.0ではmatz、3.4ではMatz(2025年8月現在)。
- TIOBE社はオランダにある会社で、同じオランダ出身のグイド・ヴァンロッサムが生みの親であるPythonは上位にランキングしている(2025年6月現在)。