Swift: Swiftの戻り値と関数型:複数戻り値、ネスト関数、型エイリアス

関数は基本型以上のものを返せます——複数の値を返したり、他の関数を返したりすることもできます。このレッスンではSwiftの関数戻り値の全範囲と、関数が第一級市民であるという中核概念を探求します。

1. 学習目標


2. EC開発者の実話

(1) 課題:割引ランクごとに新しい関数を書く

Charlieはプロモーションエンジンを構築しています。異なるユーザーランクに異なる割引が適用されます:

SWIFT
func vipDiscount(_ price: Double) -> Double {
    return price * 0.8
}
func memberDiscount(_ price: Double) -> Double {
    return price * 0.9
}
func normalDiscount(_ price: Double) -> Double {
    return price
}

新しい割引タイプが追加されるたびに、新しい関数を書きます。プロモーションルールが変わると、大量のコードを更新する必要があります。戦略の切り替えは手書きのif-elseに依存しており、動的に設定する方法がありません。

(2) 型としての関数の解決策

Swiftでは関数は「第一級市民」です——IntやStringと同じように渡したり保存したりできます:

SWIFT
// Define a discount strategy type
typealias DiscountStrategy = (Double) -> Double
// Use a function dictionary instead of if-else
let strategies: [String: DiscountStrategy] = [
    "vip": { $0 * 0.8 },
    "member": { $0 * 0.9 },
    "normal": { $0 }
]
// Switch strategy in one line
func applyDiscount(_ price: Double, level: String) -> Double {
    return strategies[level]?(price) ?? price
}

(3) 結果:設定可能な戦略、拡張可能なコード

観点 Before After
割引の追加 関数作成 + if-else修正 辞書エントリ追加
戦略切替 ハードコードif-else 動的キーベース選択
再利用性 各戦略が独立、合成不可 関数の合成と受け渡し可能
テストカバレッジ 戦略ごとに1テスト 統一テストフレームワーク

3. 複数戻り値と関数オーバーロード

(1) タプルによる複数値の返却

Swiftのタプルにより、関数は一度に複数の値を返せます:

100%
graph LR
    A[関数がタプルを返す] --> B[要素 1]
    A --> C[要素 2]
    A --> D[要素 3]
    B --> E["名前付きアクセス: result.name"]
    C --> F["名前付きアクセス: result.age"]
    D --> G["インデックスアクセス: result.0"]
アプローチ 構文 使用場面
匿名タプル -> (Int, String) 単純なグルーピング、名前は重要でない
名前付きタプル -> (sum: Int, avg: Double) 意味的なフィールドアクセスが必要
オプショナルタプル -> (Int, String)? 関数が失敗してnilを返す可能性がある

▶ サンプル: 配列の統計分析

SWIFT
// ============================================
// Return multiple statistics using a named tuple
// ============================================
func analyze(_ numbers: [Int]) -> (count: Int, sum: Int, average: Double) {
    let count = numbers.count
    let sum = numbers.reduce(0, +)
    let average = count > 0 ? Double(sum) / Double(count) : 0
    return (count, sum, average)
}
let result = analyze([85, 90, 78, 92, 88])
print("Count: \(result.count)")
print("Sum: \(result.sum)")
print("Average: \(result.average)")

出力:

TEXT 📖 参照専用
Count: 5
Sum: 433
Average: 86.6

(2) 関数オーバーロード

Swiftではパラメータの型や個数が異なれば、同じ名前の複数の関数を定義できます:

SWIFT
func area(width: Double, height: Double) -> Double {
    return width * height
}
func area(radius: Double) -> Double {
    return Double.pi * radius * radius
}
print("Rectangle area: \(area(width: 10, height: 5))")
print("Circle area: \(area(radius: 3))")

出力:

TEXT 📖 参照専用
Rectangle area: 50.0
Circle area: 28.274333882308138

4. 型としての関数

(1) 関数型の構文

すべての関数は特定の型を持ち、パラメータ型と戻り値型によって決まります:

関数シグネチャ 関数型
func add(a: Int, b: Int) -> Int (Int, Int) -> Int
func log() -> Void () -> Void または () -> ()
func greet(_ name: String) (String) -> Void
func double(_ n: Int) -> Int (Int) -> Int
💡 注意: 関数型にはパラメータ型と戻り値型のみが含まれ、引数ラベルは含まれません

▶ サンプル: 変数やパラメータとしての関数

SWIFT
// ============================================
// Functions passed as variables and parameters
// ============================================
// 1. Define two simple functions
func add(_ a: Int, _ b: Int) -> Int { return a + b }
func multiply(_ a: Int, _ b: Int) -> Int { return a * b }
// 2. Function type variable
var operation: (Int, Int) -> Int = add
print("add: \(operation(3, 4))")
// 3. Swap the function
operation = multiply
print("multiply: \(operation(3, 4))")
// 4. Function as a parameter
func execute(_ a: Int, _ b: Int, using op: (Int, Int) -> Int) -> Int {
    return op(a, b)
}
print("execute add: \(execute(5, 6, using: add))")
print("execute multiply: \(execute(5, 6, using: multiply))")

出力:

TEXT 📖 参照専用
add: 7
multiply: 12
execute add: 11
execute multiply: 30

(2) 関数型命名のためのtypealias

関数型が複雑だったり多くの場所で使われる場合、typealiasで名前を付けます:

SWIFT
// Define a function type alias
typealias IntOperation = (Int, Int) -> Int
// Use the alias for variables and parameters
func calculate(_ a: Int, _ b: Int, using op: IntOperation) -> Int {
    return op(a, b)
}
let op: IntOperation = { $0 * $0 + $1 * $1 }
print("Result: \(calculate(3, 4, using: op))")

出力:

TEXT 📖 参照専用
Result: 25

5. ネスト関数と関数を返す関数

(1) ネスト関数

関数の内部に関数を定義できます。内部関数は外部関数のスコープ内でのみアクセス可能です:

SWIFT
// ============================================
// Nested functions: define helper functions inside
// ============================================
func formatFullName(first: String, last: String) -> String {
    // Nested helper
    func capitalize(_ text: String) -> String {
        return text.prefix(1).uppercased() + text.dropFirst().lowercased()
    }
    return "\(capitalize(first)) \(capitalize(last.uppercased()))"
}
print(formatFullName(first: "alice", last: "JOHNSON"))

出力:

TEXT 📖 参照専用
Alice Johnson

(2) 関数を返す関数

高階関数の別の形——関数を返す:

SWIFT
// ============================================
// Function factory: generate different strategy functions by parameter
// ============================================
func makeMultiplier(factor: Double) -> (Double) -> Double {
    func multiplier(_ value: Double) -> Double {
        return value * factor
    }
    return multiplier
}
let double = makeMultiplier(factor: 2)
let triple = makeMultiplier(factor: 3)
print("double(5) = \(double(5))")
print("triple(5) = \(triple(5))")

出力:

TEXT 📖 参照専用
double(5) = 10.0
triple(5) = 15.0

▶ サンプル: 割引戦略ファクトリ

SWIFT
// ============================================
// Discount factory with nested functions and function return values
// ============================================
typealias DiscountRule = (Double) -> Double
func discountFor(level: String) -> DiscountRule? {
    // Nested discount functions
    func vip(_ price: Double) -> Double { return price * 0.8 }
    func member(_ price: Double) -> Double { return price * 0.9 }
    func normal(_ price: Double) -> Double { return price }
    switch level {
    case "vip": return vip
    case "member": return member
    case "normal": return normal
    default: return nil
    }
}
if let rule = discountFor(level: "vip") {
    print("VIP discount price: $\(rule(100))")
}

出力:

TEXT 📖 参照専用
VIP discount price: $80.0

6. 完全な例:プロモーションエンジン

SWIFT
// ============================================
// Full example: Promotions engine
// Combines: multiple returns + function types + typealias + nested functions
// ============================================
import Foundation
// 1. Define function type aliases
typealias Discount = (Double) -> Double
typealias DiscountResult = (original: Double, final: Double, saved: Double)
// 2. Discount factory — returns a nested function
func createDiscount(minSpend: Double, rate: Double) -> Discount {
    func discount(_ price: Double) -> Double {
        return price >= minSpend ? price * (1 - rate) : price
    }
    return discount
}
// 3. Multiple return values — return discount result details
func applyDiscount(price: Double, rule: Discount) -> DiscountResult {
    let final = rule(price)
    let saved = price - final
    return (price, final, saved)
}
// 4. Function as parameter — batch process orders
func processOrders(_ prices: [Double], using rule: Discount) -> [DiscountResult] {
    return prices.map { applyDiscount(price: $0, rule: rule) }
}
// 5. Use the promotions engine
let memberRule = createDiscount(minSpend: 50, rate: 0.15)
let orderResults = processOrders([120, 45, 200], using: memberRule)
for result in orderResults {
    print("$\(result.original) → $\(result.final) (saved $\(result.saved))")
}

出力:

TEXT 📖 参照専用
$120.0 → $102.0 (saved $18.0)
$45.0 → $45.0 (saved $0.0)
$200.0 → $170.0 (saved $30.0)

❓ よくある質問

Q 関数型が引数ラベルを無視するのはなぜですか?
A 関数型は型のマッチングに焦点を当てています——パラメータの型と戻り値。ラベルは外部呼び出し規約の一部であり、型の互換性には影響しません。したがって(a: Int) -> Void(b: Int) -> Voidは同じ型です。
Q ネスト関数とクロージャの違いは?
A ネスト関数は名前付きの内部関数です。クロージャは通常匿名のコードブロックです。ネスト関数は外部変数をキャプチャでき、クロージャと同様に動作しますが、名前があり繰り返し呼び出せます。
Q typealiasは複雑なジェネリック関数型にも使えますか?
A はい。例えばtypealias Transformer<T> = (T) -> Tはジェネリック関数型エイリアスを定義します。ただし、Swiftではtypealiasはジェネリック制約を直接表現できないため、別のメカニズムが必要です。
Q タプルを返すのとカスタム構造体を返すのではどちらが良いですか?
A 単純な組み合わせ(例:2つのIntを返す)にはタプルを使います。意味的に複雑なシナリオ(ドキュメント付きの名前付きフィールド)には構造体を使います��タプルはアドホックなグルーピングに、構造体は公開APIに適します。
Q 関数オーバーロードは実行時パフォーマンスに影響しますか?
A いいえ。オーバーロード解決はコンパイル時に行われ(静的ディスパッチ)、実行時オーバーヘッドはありません。Swiftコンパイラはコンパイル時にパラメータの型と数に基づいて正しいバージョンを選択します。

📖 まとめ


📝 練習問題

  1. 初級: 商と余りの両方をタプルで返すdivide(_:_:)関数を作成してください。divide(17, 5)の結果を呼び出して出力してください。
  2. 中級: [Int]配列用のtransform関数を作成し、(Int) -> Int型のパラメータを受け取って新しい配列を返すようにしてください。その関数型のtypealiasも定義してください。
  3. 上級: 「操作チェーン」を実装してください——初期のInt値と[(Int) -> Int]関数の配列を受け取り、各関数を順に値に適用して最終結果を返すchain(_:_:)関数を作成してください。3つの変換関数を作成し、チェーンを構成してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%