Swiftのユーザ定義関数の活用
はじめに
前回は、Swiftにおけるユーザ定義関数の基本について説明しました。今回から数回に分けて関数の活用法を紹介していきましょう。
関数はファーストクラスオブジェクト
Swiftでは、関数は「ファーストクラスオブジェクト(第一級オブジェクト)」です。ファーストクラスオブジェクトの詳細な定義はプログラミング言語によって異なりますが、Swiftでは次のような特徴を備えたオブジェクトと考えてください。
- 変数に代入できる
- 関数の引数にできる
- 関数の戻り値にできる
当然、文字列や数値をはじめとするデータ型はファーストクラスオブジェクトですが、関数も同じように扱えるわけです。
関数の型について
本連載の第4回で説明したように、Swiftはデータの型(Type)に厳格な言語です。文字列はString型、整数はInt型やInt8型、配列はArrayといった型があるのと同じように、関数にも型があります。ただし、通常のデータ型と異なり、関数の型は引数や戻り値によって決定されます。次に、関数の型の書式を示します。
(引数1の型, 引数2の型, ...) -> 戻り値の型
関数の型の例
例として、前回の連載で取り上げた、height(身長)とbmi(BMIの値)を引数に、標準体重(kg)を戻す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()関数です。
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に代入するには、次のようにします。
var myFunc1:(Double, Double) -> Double = stdWeight
こうすることで、myFunc1を2つの値を引数に呼び出せます。
var w1 = myFunc1(180.0, 21.0) // -> 68.04
ただし、型には引数ラベルやデフォルト値などの情報は含まれていないため、それらを指定して呼び出すことはできません。
var w2 = myFunc1(height: 180.0, bmi: 21.0) // -> 引数ラベルを指定するとエラー var w3 = myFunc1(180.0) // -> デフォルト値を使用するとエラー
型推論を利用する
通常のデータを変数に代入する場合と同様に、関数を変数に代入する場合も型推論機能が働きます。宣言と同時に変数に代入する場合には、型アノテーションなしで代入できます。
var myuFunc1 = stdWeight
関数を引数にする
続いて、関数を引数にするシンプルな関数の例を示しましょう。この場合、引数には次の形式で関数の型を指定します。
引数名: (引数1の型, 引数2の型, ...) -> 戻り値の型
次の関数は、指定した言語で曜日の名前を戻すgetWeekday()関数です。最初の引数で曜日を、日曜日を0、月曜日を1、....土曜日を6といった整数値で指定します。2番目の引数wfuncでは整数値を引数に曜日を戻す関数を指定します。
func getWeekday(wday: Int, wfunc: (Int) -> String) -> String { return wfunc(wday) }
引数として指定した関数の型は、「(Int) -> String)」に設定されていることに注目してください。関数の中身では、単に引数で指定したwfunc()関数をwdayを引数に呼び出し、その値を戻しているだけです。この関数の引数として使用できる関数の定義例を示します。
// 曜日を日本語で戻す 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()関数を呼び出す例を示します。
var tue1 = getWeekday(wday:2, wfunc:jDay) // -> 火 var tue2 = getWeekday(wday:2, wfunc:eDay) // -> Tue
関数をネストする
関数の内部で、別の関数を定義することができます。関数の内部で定義した関数は、関数の内部だけで参照できます。この後に説明する、関数を戻す関数を定義したり、次回説明するクロージャとして値を保持したりすることができます。次に、前述のgetWeekday()関数をベースにした例を示しましょう。内部で、jDay()関数とeDay()関数の2つを定義して、if文でどちらを使用するかを切り分けています。2番目の引数isJaがtrueの場合には日本語の曜日を、そうでなければ英語の曜日を戻します。
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()関数を戻すようにしています。
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 } }
次のようにして呼び出します。
var wday1 = getWeekday2Func(isJa: true) var sat1 = wday1(6) // -> "土" var wday2 = getWeekday2Func() var sat2 = wday2(6) // -> "Sat"
ちょっと強引な例でしたが、関数を別の関数の引数にしたり、戻り値にできることが理解できたのではないでしょうか?
内部の関数と変数のスコープについて
最後に、関数をネストさせた場合の、変数のスコープについて説明しておきましょう。関数の内部で変数を参照した場合、次のようなルールで変数が参照されます。
- 関数の内部で宣言したローカル変数があれば、それが参照される
- ない場合には、その外側の関数のローカル変数が順次参照される
- それでもない場合には、グローバル変数が参照される
次に例を見てみましょう。
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()を呼び出しています。
結果は次の通りになります。
v1: global1 v2: outerFunc1 v3: innerfunc1
変数が関数の内側から外側に順に参照されていることがわかると思います。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- 「PHP 7.2.12/7.1.24」リリース
- 「PHP 7.2.3/7.1.15/7.0.28/5.6.34」リリース
- 「PHP 7.0.9/5.6.24/5.5.38」リリース、PHP 5.5系列はサポート打ち切りへ
- 「PHP 7.1.22/7.0.32/5.6.38」リリース
- 「PHP 7.1.22/7.0.32/5.6.38」リリース
- 「PHP 7.1.5/7.0.19」リリース
- 「PHP 7.0.14/5.6.29」リリース
- 「PHP 7.2.1/7.1.13/7.0.27/5.6.33」リリース
- 「PHP 7.1.1/7.0.15/5.6.30」リリース
- 「PHP 7.1.1/7.0.15/5.6.30」リリース