Swiftのユーザ定義関数の活用

2018年9月13日(木)
大津 真
連載7回目となる今回は、Swiftのユーザ定義関数のさまざまな活用方法を紹介していく。

はじめに

前回は、Swiftにおけるユーザ定義関数の基本について説明しました。今回から数回に分けて関数の活用法を紹介していきましょう。

関数はファーストクラスオブジェクト

Swiftでは、関数は「ファーストクラスオブジェクト(第一級オブジェクト)」です。ファーストクラスオブジェクトの詳細な定義はプログラミング言語によって異なりますが、Swiftでは次のような特徴を備えたオブジェクトと考えてください。

  • 変数に代入できる
  • 関数の引数にできる
  • 関数の戻り値にできる

当然、文字列や数値をはじめとするデータ型はファーストクラスオブジェクトですが、関数も同じように扱えるわけです。

関数の型について

本連載の第4回で説明したように、Swiftはデータの型(Type)に厳格な言語です。文字列はString型、整数はInt型やInt8型、配列はArrayといった型があるのと同じように、関数にも型があります。ただし、通常のデータ型と異なり、関数の型は引数や戻り値によって決定されます。次に、関数の型の書式を示します。

(引数1の型, 引数2の型, ...) -> 戻り値の型

関数の型の例

例として、前回の連載で取り上げた、height(身長)とbmi(BMIの値)を引数に、標準体重(kg)を戻すstdWeight()関数を示します。

リスト1:身長とBMIをもとに標準体重を返すstdWeight()関数

func stdWeight(height: Double, bmi: Double = 22.0) -> Double {
        return pow(height / 100, 2) * bmi
}

この関数は、heightとbmiというDouble型の数値2つを引数として受け取り、Double型の値を戻り値として戻します。stdWeight()関数の型は、次のように表されます。

(Double, Double) -> Double

stdWeight()関数では、2番目の引数bmiにデフォルト値として「22.0」が設定されていますが、その情報は型には含めません。

引数や戻り値のない関数の型

戻り値のない関数の場合、型で戻り値を省略できません。戻り値部分に「()」を記述します。引数がない場合も、引数部分に「()」のみを記述します。次の例は、今日の日付を日本語で表示するshowToday()関数です。

リスト2:showToday()関数

func show_today() {
        let f = DateFormatter()
        f.dateStyle = .full
        f.locale = Locale(identifier: "ja_JP")
        let now = Date()
        print(f.string(from:now))
}

show_today() // -> 2018年8月22日 水曜日

この関数には引数と戻り値はありません。そこで、型は次のようになります。

() -> ()

関数を変数に代入する

それではファーストクラスオブジェクトとしての関数の使用例を見ていくことにしましょう。まず変数に、定義済みの関数を代入するには次のようにします。

var 変数名: 関数の型 = 関数名

前述のstdWeight()を変数myFunc1に代入するには、次のようにします。

リスト3:関数を変数に代入

var myFunc1:(Double, Double) -> Double = stdWeight

こうすることで、myFunc1を2つの値を引数に呼び出せます。

リスト4:関数を代入した変数の呼び出し方

var w1 = myFunc1(180.0, 21.0) // -> 68.04

ただし、型には引数ラベルやデフォルト値などの情報は含まれていないため、それらを指定して呼び出すことはできません。

リスト5:引数ラベルやデフォルト値は使用できない

var w2 = myFunc1(height: 180.0, bmi: 21.0) // -> 引数ラベルを指定するとエラー
var w3 = myFunc1(180.0)  // -> デフォルト値を使用するとエラー

型推論を利用する

通常のデータを変数に代入する場合と同様に、関数を変数に代入する場合も型推論機能が働きます。宣言と同時に変数に代入する場合には、型アノテーションなしで代入できます。

リスト6:関数の代入時に型推論を利用

var myuFunc1 = stdWeight

関数を引数にする

続いて、関数を引数にするシンプルな関数の例を示しましょう。この場合、引数には次の形式で関数の型を指定します。

引数名: (引数1の型, 引数2の型, ...) -> 戻り値の型

次の関数は、指定した言語で曜日の名前を戻すgetWeekday()関数です。最初の引数で曜日を、日曜日を0、月曜日を1、....土曜日を6といった整数値で指定します。2番目の引数wfuncでは整数値を引数に曜日を戻す関数を指定します。

リスト7:曜日の名前を戻すgetWeekday()関数

func getWeekday(wday: Int, wfunc: (Int) -> String) -> String {
        return wfunc(wday)
}

引数として指定した関数の型は、「(Int) -> String)」に設定されていることに注目してください。関数の中身では、単に引数で指定したwfunc()関数をwdayを引数に呼び出し、その値を戻しているだけです。この関数の引数として使用できる関数の定義例を示します。

リスト8:getWeekday()関数の引数として用いる関数の例

// 曜日を日本語で戻す
func jDay(num: Int) -> String{
        let weekdays = ["日", "月", "火", "水", "木", "金", "土"]
        return weekdays[num]
}

// 曜日を英語で戻す
func eDay(num: Int) -> String{
        let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
        return weekdays[num]
}

最初の関数jDay()は曜日を日本語で戻し、2番目の関数eDay()は曜日を英語で戻します。これらの関数を引数に指定して、getWeekday()関数を呼び出す例を示します。

リスト9:getWeekday()関数の呼び出し方

var tue1 = getWeekday(wday:2, wfunc:jDay) // -> 火
var tue2 = getWeekday(wday:2, wfunc:eDay) // -> Tue

関数をネストする

関数の内部で、別の関数を定義することができます。関数の内部で定義した関数は、関数の内部だけで参照できます。この後に説明する、関数を戻す関数を定義したり、次回説明するクロージャとして値を保持したりすることができます。次に、前述のgetWeekday()関数をベースにした例を示しましょう。内部で、jDay()関数とeDay()関数の2つを定義して、if文でどちらを使用するかを切り分けています。2番目の引数isJaがtrueの場合には日本語の曜日を、そうでなければ英語の曜日を戻します。

リスト10:関数のネスト

func getWeekday2(wday: Int, isJa:Bool = false) -> String {
        // 曜日を日本語で戻す内部関数
        func jDay(num: Int) -> String{
                let weekdays = ["日", "月", "火", "水", "木", "金", "土"]
                return weekdays[num]
        }

        // 曜日を英語で戻す内部関数
        func eDay(num: Int) -> String{
                let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
                return weekdays[num]
        }

    // 引数isJaの値に応じて使用する関数を切り分ける
        if isJa {
                return jDay(num: wday)
        } else {
                return eDay(num: wday)
        }
}

var wed1 = getWeekday2(wday: 3) // -> "Wed"
var wed2 = getWeekday2(wday: 3, isJa: true) // -> "水"

関数を戻す関数

続いて、関数を戻り値とする関数の定義例を示しましょう。この場合、関数の戻り値は次の形式で指定します。

(引数1の型, 引数2の型, ...) -> 戻り値の型

前述のgetWeekday2()関数を変更して、関数を戻すようにしてみましょう。引数isJaがtrueの場合には内部で定義したjDay()関数を、falseの場合にはeDay()関数を戻すようにしています。

リスト11:関数を戻り値とする関数の例

func getWeekday2Func(isJa:Bool = false) -> (Int) -> String {
        // 曜日を日本語で戻す
        func jDay(num: Int) -> String{
                let weekdays = ["日", "月", "火", "水", "木", "金", "土"]
                return weekdays[num]
        }

        // 曜日を英語で戻す
        func eDay(num: Int) -> String{
                let weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
                return weekdays[num]
        }

        if isJa {
                return jDay
        } else {
                return eDay
        }
}

次のようにして呼び出します。

リスト12:関数を戻り値とする関数の使用例

var wday1 = getWeekday2Func(isJa: true)
var sat1 = wday1(6) // -> "土"
var wday2 = getWeekday2Func()
var sat2 = wday2(6) // -> "Sat"

ちょっと強引な例でしたが、関数を別の関数の引数にしたり、戻り値にできることが理解できたのではないでしょうか?

内部の関数と変数のスコープについて

最後に、関数をネストさせた場合の、変数のスコープについて説明しておきましょう。関数の内部で変数を参照した場合、次のようなルールで変数が参照されます。

  1. 関数の内部で宣言したローカル変数があれば、それが参照される
  2. ない場合には、その外側の関数のローカル変数が順次参照される
  3. それでもない場合には、グローバル変数が参照される

次に例を見てみましょう。

リスト13:関数のネストと変数のスコープ

var v1 = "global1"  // (1)
var v2 = "global2"  // (2)
var v3 = "global3"  // (3)

// 外側の関数
func outerFunc() { // (4)
        var v2 = "outerFunc1"  // (5)
        var v3 = "outerFunc3"  // (6)

        // 内側の関数
        func innerFunc(){ // (7)
                let v3 = "innerfunc1" // (8)
                print("v1: \(v1)")    // (9)
                print("v2: \(v2)")    // (10)
                print("v3: \(v3)")    // (11)
        }

        // 内側の関数を呼び出す
        innerFunc()  // (12)
}

// 外側の関数を呼び出す
outerFunc() // (13)

(1)(2)(3)で変数v1、v2、v3をグローバル変数として宣言し値を代入しています。(4)で外側の関数outerFunc()を宣言し、(5)(6)でそのローカル変数としてv2、v2を宣言して値を代入しています。(7)で内側の関数innerFunc()を定義し、そのローカル変数としてv3を宣言して値を代入しています。(9)(10)(11)でv1、v2、v3の値を表示しています。(12)でouterFunc()の内部でinnerFunc()を呼び出しています。(13)でouterFunc()を呼び出しています。

結果は次の通りになります。

リスト14:上記の関数の実行結果

v1: global1
v2: outerFunc1
v3: innerfunc1

変数が関数の内側から外側に順に参照されていることがわかると思います。

東京都生まれ。早稲田大学理工学部卒業後、外資系コンピューターメーカーにSEとして8年間勤務。現在はフリーランスのテクニカルライター、プログラマー。

<主な著書>
きちんとわかる! JavaScript とことん入門(技術評論社)
基礎Python(インプレス)
他多数

連載バックナンバー

開発言語技術解説
第9回

Swiftでの文字列操作の基礎(その1)

2019/9/27
連載9回目となる今回は、Swfitによる文字列操作の基本を学びます。あわせてSwift 5で導入された新機能にもついても紹介します。
開発言語
第8回

Swiftでクロージャを理解する

2018/12/21
連載8回目となる今回は、モダンな言語に欠かせないクロージャについて解説します。
開発言語技術解説
第7回

Swiftのユーザ定義関数の活用

2018/9/13
連載7回目となる今回は、Swiftのユーザ定義関数のさまざまな活用方法を紹介していく。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています