メインコンテンツまでスキップ
Examples Verified (100%)

Union型

Union型を使用すると、値が複数の異なる型のいずれかになることができます。正当に複数の形式を持つことができるデータをモデル化するために不可欠です。この章では、T-RubyでUnion型を効果的に使用する方法を学びます。

Union型とは?

Union型は、指定された複数の型のいずれかになりうる値を表します。T-Rubyでは、パイプ(|)演算子を使用してUnion型を作成します:

union_basics.trb
# この変数はStringまたはnilになりうる
name: String | nil = "Alice"

# これはStringまたはIntegerになりうる
id: String | Integer = "user-123"

# これは3つの型のいずれかになりうる
value: String | Integer | Boolean = true

Union型を使用する理由

Union型はいくつかのシナリオで役立ちます:

1. オプショナル値

最も一般的な使用法は、型とnilを組み合わせてオプショナル値を表すことです:

optional_values.trb
def find_user(id: Integer): String | nil
return nil if id < 0
"User #{id}"
end

# 結果はnilの可能性がある
user: String | nil = find_user(1) # "User 1"
no_user: String | nil = find_user(-1) # nil

2. 複数の有効な入力型

関数が異なる型の入力を受け入れる場合:

multiple_inputs.trb
def format_id(id: String | Integer): String
if id.is_a?(Integer)
"ID-#{id}"
else
id.upcase
end
end

formatted1: String = format_id(123) # "ID-123"
formatted2: String = format_id("abc") # "ABC"

3. 異なる戻り値型

関数が条件に基づいて異なる型を返す可能性がある場合:

different_returns.trb
def parse_value(input: String): String | Integer | Boolean
if input == "true" || input == "false"
input == "true"
elsif input.to_i.to_s == input
input.to_i
else
input
end
end

result1 = parse_value("42") # 42 (Integer)
result2 = parse_value("true") # true (Boolean)
result3 = parse_value("hello") # "hello" (String)

Union型の操作

is_a?による型チェック

Union型を持つ値を安全に使用するには、実際の型を確認する必要があります:

type_checking.trb
def process_value(value: String | Integer): String
if value.is_a?(String)
# このブロック内では、T-RubyはvalueがStringであることを知っている
value.upcase
else
# ここでは、T-RubyはvalueがIntegerであることを知っている
value.to_s
end
end

result1: String = process_value("hello") # "HELLO"
result2: String = process_value(42) # "42"

nilのチェック

オプショナル値を扱う場合、常にnilをチェックしてください:

nil_checking.trb
def get_length(text: String | nil): Integer
if text.nil?
0
else
# ここでは、T-RubyはtextがStringであることを知っている(nilではない)
text.length
end
end

len1: Integer = get_length("hello") # 5
len2: Integer = get_length(nil) # 0

# 安全ナビゲーション演算子を使用した代替
def get_length_safe(text: String | nil): Integer | nil
text&.length
end

複数の型チェック

Unionに2つ以上の型がある場合:

multiple_checks.trb
def describe_value(value: String | Integer | Boolean): String
if value.is_a?(String)
"テキスト: #{value}"
elsif value.is_a?(Integer)
"数値: #{value}"
elsif value.is_a?(Boolean)
"ブール: #{value}"
else
"不明"
end
end

desc1: String = describe_value("hello") # "テキスト: hello"
desc2: String = describe_value(42) # "数値: 42"
desc3: String = describe_value(true) # "ブール: true"

コレクションでのUnion型

Union型は配列やハッシュとよく一緒に使用されます:

Union要素型を持つ配列

union_arrays.trb
# 文字列または整数を含むことができる配列
def create_mixed_list(): Array<String | Integer>
["Alice", 1, "Bob", 2, "Charlie", 3]
end

def sum_numbers(items: Array<String | Integer>): Integer
total = 0

items.each do |item|
if item.is_a?(Integer)
total += item
end
end

total
end

def get_strings(items: Array<String | Integer>): Array<String>
result: Array<String> = []

items.each do |item|
if item.is_a?(String)
result << item
end
end

result
end

mixed: Array<String | Integer> = create_mixed_list()
sum: Integer = sum_numbers(mixed) # 6
strings: Array<String> = get_strings(mixed) # ["Alice", "Bob", "Charlie"]

Union値型を持つハッシュ

union_hashes.trb
# 異なる値型を持つハッシュ
def create_config(): Hash<Symbol, String | Integer | Boolean>
{
host: "localhost",
port: 3000,
debug: true,
timeout: 30,
environment: "development"
}
end

def get_string_value(
config: Hash<Symbol, String | Integer | Boolean>,
key: Symbol
): String | nil
value = config[key]

if value.is_a?(String)
value
else
nil
end
end

def get_integer_value(
config: Hash<Symbol, String | Integer | Boolean>,
key: Symbol
): Integer | nil
value = config[key]

if value.is_a?(Integer)
value
else
nil
end
end

config = create_config()
host: String | nil = get_string_value(config, :host) # "localhost"
port: Integer | nil = get_integer_value(config, :port) # 3000

一般的なUnion型パターン

パターン1:成功またはエラー

result_pattern.trb
def divide_safe(a: Float, b: Float): Float | String
if b == 0.0
"エラー: ゼロで除算できません"
else
a / b
end
end

def process_result(result: Float | String): String
if result.is_a?(Float)
"結果: #{result}"
else
# エラーメッセージ
result
end
end

result1 = divide_safe(10.0, 2.0) # 5.0
result2 = divide_safe(10.0, 0.0) # "エラー: ゼロで除算できません"

message1: String = process_result(result1) # "結果: 5.0"
message2: String = process_result(result2) # "エラー: ゼロで除算できません"

パターン2:デフォルト値

default_pattern.trb
def get_value_or_default(
value: String | nil,
default: String
): String
if value.nil?
default
else
value
end
end

# 単純なケースでは||を使用
def get_or_default_short(value: String | nil, default: String): String
value || default
end

result1: String = get_value_or_default("hello", "default") # "hello"
result2: String = get_value_or_default(nil, "default") # "default"

パターン3:型強制変換

coercion_pattern.trb
def to_integer(value: String | Integer): Integer
if value.is_a?(Integer)
value
else
value.to_i
end
end

def to_string(value: String | Integer | Boolean): String
if value.is_a?(String)
value
else
value.to_s
end
end

num1: Integer = to_integer(42) # 42
num2: Integer = to_integer("42") # 42

str1: String = to_string("hello") # "hello"
str2: String = to_string(42) # "42"
str3: String = to_string(true) # "true"

パターン4:多態関数

polymorphic_pattern.trb
def repeat(value: String | Integer, times: Integer): String
if value.is_a?(String)
value * times
else
# 数値表現を繰り返す
(value.to_s + " ") * times
end
end

result1: String = repeat("Ha", 3) # "HaHaHa"
result2: String = repeat(42, 3) # "42 42 42 "

ネストしたUnion型

Union型は複雑な方法で組み合わせることができます:

Union内のUnion

nested_unions.trb
# 数値(IntegerまたはFloat)またはテキスト(StringまたはSymbol)になりうる値
def process_input(value: Integer | Float | String | Symbol): String
if value.is_a?(Integer) || value.is_a?(Float)
"数値: #{value}"
elsif value.is_a?(String)
"文字列: #{value}"
else
"シンボル: #{value}"
end
end

result1: String = process_input(42) # "数値: 42"
result2: String = process_input(3.14) # "数値: 3.14"
result3: String = process_input("hello") # "文字列: hello"
result4: String = process_input(:active) # "シンボル: active"

複雑な型とのUnion

complex_unions.trb
# 単一の値または値の配列になりうる
def normalize_input(
value: String | Array<String>
): Array<String>
if value.is_a?(Array)
value
else
[value]
end
end

result1: Array<String> = normalize_input("hello") # ["hello"]
result2: Array<String> = normalize_input(["a", "b"]) # ["a", "b"]

# 単一の整数または範囲になりうる
def expand_range(value: Integer | Range): Array<Integer>
if value.is_a?(Range)
value.to_a
else
[value]
end
end

nums1: Array<Integer> = expand_range(5) # [5]
nums2: Array<Integer> = expand_range(1..5) # [1, 2, 3, 4, 5]

実用的な例:設定システム

Union型を使用した包括的な例です:

config_system.trb
class ConfigManager
def initialize()
@config: Hash<String, String | Integer | Boolean | nil> = {}
end

def set(key: String, value: String | Integer | Boolean | nil)
@config[key] = value
end

def get_string(key: String): String | nil
value = @config[key]

if value.is_a?(String)
value
else
nil
end
end

def get_integer(key: String): Integer | nil
value = @config[key]

if value.is_a?(Integer)
value
else
nil
end
end

def get_bool(key: String): Boolean | nil
value = @config[key]

if value.is_a?(Boolean)
value
else
nil
end
end

def get_string_or_default(key: String, default: String): String
value = get_string(key)
value || default
end

def get_integer_or_default(key: String, default: Integer): Integer
value = get_integer(key)
value || default
end

def get_bool_or_default(key: String, default: Boolean): Boolean
value = get_bool(key)
if value.nil?
default
else
value
end
end

def to_hash(): Hash<String, String | Integer | Boolean | nil>
@config.dup
end

def parse_and_set(key: String, raw_value: String)
# booleanとしてパース試行
if raw_value == "true"
set(key, true)
return
elsif raw_value == "false"
set(key, false)
return
end

# 整数としてパース試行
int_value = raw_value.to_i
if int_value.to_s == raw_value
set(key, int_value)
return
end

# それ以外は文字列として保存
set(key, raw_value)
end
end

# 使用法
config = ConfigManager.new()

config.set("host", "localhost")
config.set("port", 3000)
config.set("debug", true)
config.set("optional_feature", nil)

host: String = config.get_string_or_default("host", "0.0.0.0")
# "localhost"

port: Integer = config.get_integer_or_default("port", 8080)
# 3000

debug: Boolean = config.get_bool_or_default("debug", false)
# true

timeout: Integer = config.get_integer_or_default("timeout", 30)
# 30(キーが存在しないのでデフォルトを使用)

# 文字列からパース
config.parse_and_set("max_connections", "100") # Integerとして保存
config.parse_and_set("enable_ssl", "true") # Booleanとして保存
config.parse_and_set("environment", "production") # Stringとして保存

ベストプラクティス

1. Unionをシンプルに保つ

多すぎる型を持つUnionを避けてください:

simple_unions.trb
# 良い - 明確でシンプル
def process(value: String | Integer): String
# ...
end

# 避ける - 処理する型が多すぎる
def process_complex(
value: String | Integer | Float | Boolean | Symbol | nil
): String
# 分岐が多すぎる
end

2. オプショナル値にnil Unionを使用

optional_best_practice.trb
# 良い - 明確にオプショナル
def find_item(id: Integer): String | nil
# ...
end

# 避ける - 空文字列で「見つからない」を意味
def find_item_bad(id: Integer): String
# 見つからないと""を返す - 不明確!
end

3. 一貫した順序で型をチェック

consistent_checks.trb
# 良い - 一貫したパターン
def process(value: String | Integer): String
if value.is_a?(String)
value.upcase
else
value.to_s
end
end

# 良い - 同じパターン
def format(value: String | Integer): String
if value.is_a?(String)
"テキスト: #{value}"
else
"数値: #{value}"
end
end

4. Union型の意味を文書化

documentation.trb
# 良い - 各型が何を意味するか明確
def get_status(id: Integer): String | Symbol | nil
# 戻り値:
# - String: エラーメッセージ
# - Symbol: ステータスコード(:active, :pendingなど)
# - nil: アイテムが見つからない

return nil if id < 0
return :active if id == 1
"エラー: 無効な状態"
end

一般的な落とし穴

型チェックを忘れる

missing_checks.trb
# 間違い - 型をチェックしていない
def bad_example(value: String | Integer): Integer
value.length # エラー!Integerにはlengthがない
end

# 正しい - 最初に型をチェック
def good_example(value: String | Integer): Integer
if value.is_a?(String)
value.length
else
value
end
end

変更後の型の仮定

type_mutation.trb
def risky_example(value: String | Integer)
if value.is_a?(String)
value = value.to_i # これでIntegerに!
# valueは今Integer、Stringではない
end

# ここでvalueがまだStringであると仮定できない
end

まとめ

T-RubyのUnion型は、値が複数の型のいずれかになることを可能にします:

  • 構文: パイプ演算子(|)を使用して型を結合
  • 一般的な用途: | nilで値をオプショナルにする
  • 型チェック: is_a?を使用して実際の型を判定
  • コレクション: ArrayやHash型と組み合わせて使用可能
  • ベストプラクティス: Unionをシンプルに保ち、一貫して型をチェック

Union型は、単一の型に収まらない現実のデータをモデル化するために不可欠です。型ナローイング(次の章で説明)と組み合わせることで、多様なデータを処理する強力で安全な方法を提供します。