💎
【Ruby 34日目】基本文法 - splat演算子
はじめに
Rubyのsplat演算子(*と**)について、Ruby 3.4の仕様に基づいて詳しく解説します。
この記事では、基本的な概念から実践的な使い方まで、具体的なコード例を交えて説明します。
基本概念
splat演算子は、配列やハッシュを展開・結合するための演算子です:
-
*(splat演算子) - 配列の展開や可変長引数の処理 -
**(double splat演算子) - ハッシュの展開やキーワード引数の処理 - 引数での使用 - 可変長引数を受け取る
- 呼び出しでの使用 - 配列やハッシュを展開して渡す
これらを使うことで、柔軟な引数処理やデータ構造の操作が可能になります。
基本的な使い方
配列の展開(*演算子)
# 配列を個別の引数として展開
def sum(a, b, c)
a + b + c
end
numbers = [1, 2, 3]
puts sum(*numbers) #=> 6
配列の結合
# 複数の配列を結合
arr1 = [1, 2, 3]
arr2 = [4, 5, 6]
arr3 = [7, 8, 9]
combined = [*arr1, *arr2, *arr3]
puts combined.inspect #=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 配列の途中に要素を挿入
result = [0, *arr1, 10]
puts result.inspect #=> [0, 1, 2, 3, 10]
可変長引数の受け取り
def greet(greeting, *names)
names.each do |name|
puts "#{greeting}, #{name}!"
end
end
greet("Hello", "Alice", "Bob", "Charlie")
#=> Hello, Alice!
# Hello, Bob!
# Hello, Charlie!
配列の分割代入
# 配列の最初と残りを分ける
first, *rest = [1, 2, 3, 4, 5]
puts first #=> 1
puts rest.inspect #=> [2, 3, 4, 5]
# 配列の最後と残りを分ける
*beginning, last = [1, 2, 3, 4, 5]
puts beginning.inspect #=> [1, 2, 3, 4]
puts last #=> 5
# 配列の中間を取り出す
first, *middle, last = [1, 2, 3, 4, 5]
puts first #=> 1
puts middle.inspect #=> [2, 3, 4]
puts last #=> 5
ハッシュの展開(**演算子)
# ハッシュをキーワード引数として展開
def create_user(name:, age:, country:)
"#{name} (#{age}) from #{country}"
end
user_data = { name: "Alice", age: 25, country: "Japan" }
puts create_user(**user_data) #=> Alice (25) from Japan
ハッシュのマージ
# 複数のハッシュをマージ
defaults = { host: "localhost", port: 3000, ssl: true }
custom = { port: 8080, timeout: 30 }
config = { **defaults, **custom }
puts config.inspect
#=> {:host=>"localhost", :port=>8080, :ssl=>true, :timeout=>30}
# ハッシュリテラル内で展開
user = { name: "Alice", age: 25 }
profile = { **user, country: "Japan", verified: true }
puts profile.inspect
#=> {:name=>"Alice", :age=>25, :country=>"Japan", :verified=>true}
可変長キーワード引数
def build_query(table:, **conditions)
query = "SELECT * FROM #{table}"
unless conditions.empty?
where = conditions.map { |k, v| "#{k} = '#{v}'" }.join(" AND ")
query += " WHERE #{where}"
end
query
end
puts build_query(table: "users", age: 25, country: "Japan")
#=> SELECT * FROM users WHERE age = '25' AND country = 'Japan'
よくあるユースケース
ケース1: メソッド引数の転送
引数をそのまま別のメソッドに転送します。
class Logger
def log(level, *args, **kwargs)
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
message = format_message(*args)
metadata = format_metadata(**kwargs)
puts "[#{timestamp}] [#{level.upcase}] #{message} #{metadata}"
end
private
def format_message(*parts)
parts.join(" ")
end
def format_metadata(**data)
return "" if data.empty?
"| " + data.map { |k, v| "#{k}=#{v}" }.join(" ")
end
end
logger = Logger.new
logger.log(:info, "User", "logged", "in", user_id: 123, ip: "192.168.1.1")
#=> [2025-11-10 10:00:00] [INFO] User logged in | user_id=123 ip=192.168.1.1
ケース2: 設定のマージ
デフォルト設定とカスタム設定をマージします。
class Application
DEFAULT_CONFIG = {
host: "localhost",
port: 3000,
ssl: false,
timeout: 30,
retry_count: 3
}
def initialize(**custom_config)
@config = { **DEFAULT_CONFIG, **custom_config }
end
def start
puts "Starting application with config:"
@config.each { |key, value| puts " #{key}: #{value}" }
end
end
app = Application.new(port: 8080, ssl: true, debug: true)
app.start
#=> Starting application with config:
# host: localhost
# port: 8080
# ssl: true
# timeout: 30
# retry_count: 3
# debug: true
ケース3: 配列の動的な結合
複数のソースから配列を構築します。
class ReportBuilder
def build_report(title, *sections)
header = ["=" * 50, title, "=" * 50]
footer = ["=" * 50, "End of Report", "=" * 50]
report = [*header, "", *sections.flatten, "", *footer]
report.join("\n")
end
end
builder = ReportBuilder.new
section1 = ["Section 1:", "- Item A", "- Item B"]
section2 = ["Section 2:", "- Item C", "- Item D"]
puts builder.build_report("Monthly Report", section1, section2)
#=> ==================================================
# Monthly Report
# ==================================================
#
# Section 1:
# - Item A
# - Item B
# Section 2:
# - Item C
# - Item D
#
# ==================================================
# End of Report
# ==================================================
ケース4: 関数の部分適用
引数の一部を固定した新しい関数を作成します。
class PartialFunction
def initialize(method, *fixed_args, **fixed_kwargs)
@method = method
@fixed_args = fixed_args
@fixed_kwargs = fixed_kwargs
end
def call(*args, **kwargs)
all_args = [*@fixed_args, *args]
all_kwargs = { **@fixed_kwargs, **kwargs }
@method.call(*all_args, **all_kwargs)
end
end
# 元の関数
multiply_and_add = ->(a, b, c:) { a * b + c }
# 部分適用:aを2に固定
double_and_add = PartialFunction.new(multiply_and_add, 2)
puts double_and_add.call(5, c: 10) #=> 20 (2 * 5 + 10)
puts double_and_add.call(3, c: 7) #=> 13 (2 * 3 + 7)
ケース5: REST APIのパラメータ処理
可変長のフィルター条件を処理します。
class APIClient
def search(endpoint, required_param, *optional_filters, **query_params)
url = "https://api.example.com/#{endpoint}"
params = { q: required_param }
# オプションフィルターを追加
optional_filters.each_with_index do |filter, index|
params["filter#{index + 1}"] = filter
end
# クエリパラメータをマージ
params.merge!(**query_params)
# URLを構築
query_string = params.map { |k, v| "#{k}=#{v}" }.join("&")
"#{url}?#{query_string}"
end
end
client = APIClient.new
url = client.search(
"products",
"laptop",
"electronics",
"in-stock",
limit: 10,
sort: "price",
order: "asc"
)
puts url
#=> https://api.example.com/products?q=laptop&filter1=electronics&filter2=in-stock&limit=10&sort=price&order=asc
注意点とベストプラクティス
注意点
- 配列とハッシュの展開は異なる演算子
# BAD: *を使ってハッシュを展開しようとする
def create_user(name:, age:)
"#{name} (#{age})"
end
user_data = { name: "Alice", age: 25 }
# create_user(*user_data) # ArgumentError
# GOOD: **を使う
puts create_user(**user_data) #=> Alice (25)
- splat演算子の位置
# 可変長引数は最後の位置引数の前に配置できる
def method1(a, *b, c)
"a=#{a}, b=#{b.inspect}, c=#{c}"
end
puts method1(1, 2, 3, 4, 5) #=> a=1, b=[2, 3, 4], c=5
# キーワード引数と組み合わせる場合
def method2(a, *args, key: "default", **kwargs)
"a=#{a}, args=#{args.inspect}, key=#{key}, kwargs=#{kwargs.inspect}"
end
puts method2(1, 2, 3, key: "value", extra: "data")
#=> a=1, args=[2, 3], key=value, kwargs={:extra=>"data"}
- nilの扱い
# nilを展開すると空配列として扱われる
arr = nil
result = [1, 2, *arr, 3]
puts result.inspect #=> [1, 2, 3]
# ハッシュも同様
hash = nil
merged = { a: 1, **hash, b: 2 }
puts merged.inspect #=> {:a=>1, :b=>2}
ベストプラクティス
- 引数転送には
...を使用(Ruby 2.7+)
# GOOD: Ruby 2.7以降では引数転送演算子を使う
def wrapper(...)
actual_method(...)
end
def actual_method(a, b, c:)
"a=#{a}, b=#{b}, c=#{c}"
end
puts wrapper(1, 2, c: 3) #=> a=1, b=2, c=3
- 配列の結合は明示的に
# GOOD: 意図が明確
arrays = [[1, 2], [3, 4], [5, 6]]
combined = arrays.flatten
puts combined.inspect #=> [1, 2, 3, 4, 5, 6]
# ALSO GOOD: splatを使った方法
combined = [*arrays[0], *arrays[1], *arrays[2]]
puts combined.inspect #=> [1, 2, 3, 4, 5, 6]
- ハッシュのマージは優先順位を意識
# 後に指定したハッシュの値が優先される
defaults = { a: 1, b: 2, c: 3 }
custom = { b: 20, d: 4 }
# customの値が優先
config = { **defaults, **custom }
puts config.inspect #=> {:a=>1, :b=>20, :c=>3, :d=>4}
# defaultsの値が優先
config = { **custom, **defaults }
puts config.inspect #=> {:b=>2, :d=>4, :a=>1, :c=>3}
Ruby 3.4での改善点
- Prismパーサーによる最適化 - splat演算子の解析が高速化
- YJITの最適化 - splat演算子を使った配列・ハッシュ操作のパフォーマンス向上
- 引数転送(...)の安定性向上 - Ruby 2.7で導入された引数転送がより安定的に
- エラーメッセージの改善 - splat演算子の誤用時のエラーメッセージがより詳細に
まとめ
この記事では、splat演算子について以下の内容を学びました:
- 基本概念と重要性 -
*と**の違い、配列とハッシュの展開 - 基本的な使い方と構文 - 引数の展開、分割代入、マージ操作
- 実践的なユースケース - 引数転送、設定マージ、配列結合、部分適用、APIパラメータ処理
- 注意点とベストプラクティス - 演算子の使い分け、引数転送演算子、マージの優先順位
splat演算子を適切に使用することで、柔軟で簡潔なコードを書くことができます。
Discussion