関数型を値・引数・返り値として扱う
すべての関数は型の情報を持っています。そして、関数は参照型のインスタンスとして扱うことができ、関数型引数を持つ関数も宣言可能です。
例として、2つのInt型の引数から、大きい方を取り出す関数と小さい方を取り出す関数を宣言し、それらを使ってInt型配列の境界値(最大値あるいは最小値)を求める関数を作成してみます。
2つのInt型の引数から、大きい方を取り出す関数maximumと、小さい方を取り出す関数minimumは、次のように宣言できます。
func maximum(a: Int, b: Int) -> Int { return a > b ? a : b } func (a: Int, b: Int) -> Int { return a > b ? b : a }
次は、Int型配列の境界値(最大値あるいは最小値)を求める関数の宣言です。1つ目の引数は、maximum関数やminimum関数のような、2つのIntをタプルとして受け取ってIntを返す関数を受け取ります。2つ目の引数は、境界値を求めるInt型配列を受け取ります。
func boundary(rule: (Int, Int) -> Int, nums: [Int]) -> Int { var bound = nums[0] for num in nums { bound = rule(num, bound) } return bound }
1つ目の引数の記述どおり、関数型の引数は (引数すべてを表すタプル) -> 返り値型で指定されます。また、返り値に関しても (引数すべてを表すタプル) -> 返り値型で関数が指定できます。
なお、2つ目の引数で使われている[Int]は、Array<Int>のシンタックスシュガーです。
実行すると、ルールとして指定した関数(maximumあるいはminimum)に応じた境界値が返ってきます。
var rule = maximum boundary(maximum, [1,4,5,6]) // 6 rule = minimum boundary(minimum, [-1, 4, 10, 11]) // -1
この例から、Swiftでは関数もローカル変数に代入して扱えることがわかりました。さらに、関数型の変数・定数にも型推論が適用されます。ここでrule関数の引数に異なる型の関数を与えると、今までと同じようなことが起こります。試してみてください。
さらに、Swiftでは返り値として関数を返す関数を宣言することも可能です。例として、先ほどのmaximum関数、minimum関数のどちらを使用するかをBool値で指定できる関数を作成してみましょう。
func rule(#isMaximum: Bool) -> (Int, Int) -> Int { return isMaximum ? maximum : minimum }
rule関数の引数isMaximumにtrueを与えるとmaximum関数が呼ばれ、falseを与えるとminimum関数が呼ばれます。次のようにrule関数を使うと、minimum関数をルールとしてInt型配列から最小値が取り出されます。
boundary(rule(isMaximum: false), [-2, 3, 5, 6, 11, 5]) // -2
引数として関数型を取ったり、関数型の返り値を持ったりする関数は高階関数と呼ばれます。高階関数は、Arrayのmapメソッドやreduceメソッドなど、Swiftの至る所に現れる重要な概念です。
ネスト関数
関数内で関数を定義することもできます(ネスト関数)。maximum、minimum関数をrule関数内で定義してみましょう。
func rule(#isMaximum: Bool) -> (Int, Int) -> Int { func maximum(a: Int, b: Int) -> Int {return a > b ? a : b} func minimum(a: Int, b: Int) -> Int {return a < b ? a : b} return isMaximum ? maximum : minimum } boundary(rule(isMaximum: false), [-2, 3, 5, 6, 11, 5]) // -2
このように記述すると、グローバルに定義された関数のスコープをrule関数内にとどめることができます。
なお、ネストされた関数では、それを定義している関数内のローカル変数を用いると、ローカル変数のキャプチャが起こるため、注意が必要です。キャプチャの解説も含めた詳細は、次ページ「クロージャ式」の項で解説します。
カリー化関数
複数の引数を持つ関数に関しては、カリー化関数(複数引数を持つ関数を1引数関数のチェーンとして呼び出せるようにしたもの)を簡単に宣言する方法も用意されています。先ほどの境界値を求めるboundary関数を、カリー化した形で宣言してみましょう。
func boundary(rule: (Int, Int) -> Int)(nums: [Int]) -> Int { var bound = nums[0] for num in nums { bound = rule(num, bound) } return bound } boundary(rule(isMaximum: false))(nums:[-2, 3, 5, 6, 11, 5]) // -2
この流儀の宣言方法は、次のネストした関数を使った宣言方法と同じとみなせます。
func boundary(rule: (Int, Int) -> Int) -> [Int] -> Int { func boundaryForNumbers(nums: [Int]) -> Int { var bound = nums[0] for num in nums { bound = rule(num, bound) } return bound } return boundaryForNumbers } boundary(rule(isMaximum: false))([-2, 3, 5, 6, 11, 5]) // -2
カリー化された関数表現の何がうれしいかというと、引数を同時に適用するのではなく部分的に適用することで、新たな意味を持った関数型の変数・定数を生成できる点にあります。次のコードを見てください。
var maximumboundary = boundary(rule(isMaximum: true)) maximumboundary([-2, 3, 5, 6, 11, 5]) // 11
この例では、最大値を計算する関数が、部分的な引数の適用で作成されています。引数の部分適用によって、Int型配列から最大値を返す関数を新しく定義する必要がなくなっていることがわかります。