The Swift Programming Language
のLanguage Guide -> Functions
を読んで、内容をまとめて見ました。
概要
Swiftの関数
はCスタイルのシンプルな関数から、複雑なObjective-Cスタイルのメソッドまでを表す事ができる。
In/Outパラメータには関数の呼び出しを単純化するためにデフォルト値を与える事ができる。
関数のパラメータ、戻り値には型を指定する必要がある。
関数のパラメータには関数を渡す事ができ、関数を戻り値とすることができる。(第一級)
関数のネストも可能。
関数の定義と呼び出し
関数は一意になるようにネーミングしなければならない。被っているとコンパイルエラー。(当たり前ですね)
関数に渡すパラメータは、常に関数定義のパラメータリストと同じ順序で指定しなければならない。
以下は、String
型のパラメータを受け取り、String
型の戻り値を返す関数。
通常の変数定義と同様、:
でパラメータの型を指定する。
戻り値はアロー演算子で戻り値の型を指定する。Tupleも可。
func sayHello(personName: String) -> String {
let greeting = "Hello, \(personName)!"
return greeting
}
sayHello("Jones") // -> "Jones"
関数のパラメータと戻り値
パラメータを単一または複数持つ関数を定義可能。
複数の入力パラメータ
パラメータを複数指定する場合はカンマで区切る。数に制限はない。
func halfOpenRange(start: Int, end: Int) -> Int {
return end - start;
}
halfOpenRange(1, 10) // -> 9
タプル型をパラメータに持つことも可能。
func sayManyHello(persion: (name: String, age: Int)) -> String {
return "name: (persion.name), age: (persion.age)"
}
sayManyHello(("enmanji", 26)) // -> "name: enmanji, age: 26"
### パラメータを持たない関数
```swift
func sayHelloWorld() -> String {
return "Hello World!";
}
sayHelloWorld() // -> "Hello World!"
戻り値を持たない関数
戻り値も持たない場合は、アロー演算子(->)と戻り値の型を指定しない。
func sayGoodbye(personName: String) {
println("Goodbye \(personName)");
}
sayGoodbye("Dave") // -> print "Goodbye Dave"
内部的にはVoid型(空のタプル型)を戻り値として返している。
戻り値を持つ関数を実行しても戻り値を受け取る必要はない。(無視できる)
複数の戻り値
戻り値の型にタプル型を指定可能。
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
minMax([1, 9, 5, 2]) // -> (min: 1, max: 9)
Optionalなタプル型を返す
Optionalなタプル型を返すことも可能。
func minMaxOptional(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty {
return nil
}
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
minMaxOptional([1, 9, 5, 2]) // -> (min: 1, max: 9)
minMaxOptional([]) // -> nil
パラメータ名
関数のパラメータ名は関数内で使用するために関数内のローカル変数として定義される。
呼び出し元では当然使用できない。
func someFunction(paramName: Int) {
// この中でのみ'paramName'が使用できる
}
外部パラメータ名
呼び出し元で指定するパラメータ名を指定できる。(ラベリング)
パラメータ名の前にスペースを開けて指定する。
func join(string s1: String, toString s2: String, withJoiner joiner: String) -> String {
return s1 + joiner + s2
}
join(string: "Hello", toString: "World", withJoiner: ", ") // -> "Hello, World"
関数内部では略名を使用したいけど、外から指定する名前は完全名にしたい時などで使えそう。
しかし、ソースコードの可読性の観点から、必要な箇所のみで使用するべきとのこと。
外部パラメータ名を指定したら、呼び出し元では必ずパラメータ名を指定しなければならない様子。
個人的には指定を強制されたくない。
簡易的な外部パラメータ名
パラメータ名の前に#
を付与することで、外部と内部のパラメータ名に同じものを使用できる。
パラメータごとに指定。
func shorthandJoin(#s1: String, #s2: String, withJoiner joiner: String) -> String {
return s1 + joiner + s2
}
shorthandJoin(s1: "Hello", s2: "World", withJoiner: ", ") // -> "Hello, World"
パラメータのデフォルト値
パラメータにはデフォルト値を設定することが可能。
デフォルト値が指定されているパラメータは関数呼び出し時に省略可。
func DefaultJoinerJoin(string s1: String, toString s2: String, withJoiner joiner: String = ", ") -> String {
return s1 + joiner + s2
}
DefaultJoinerJoin(string: "Hello", toString: "World", withJoiner:"_") // -> "Hello_World"
DefaultJoinerJoin(string: "Hello", toString: "World") // -> "Hello, World"
デフォルト値を持たないパラメータの呼び出し順序を保証するために、デフォルト値を持つパラメータはパラメータリストの最後になるように配置する。
最後でなくてもコンパイルエラーにはならず、期待通り動作する。
func DefaultTestJoin(string s1: String = "Hello", toString s2: String, withJoiner joiner: String) -> String {
return s1 + joiner + s2
}
DefaultTestJoin(string: "Goodbye", toString: "World", withJoiner:", ") // -> "Goodbye, World"
DefaultTestJoin(toString: "World", withJoiner:", ") // -> "Hello, World"
外部パラメータ名とデフォルト値の併用
併用可。上記例の通り。
可変長パラメータ
可変長パラメータは0個から複数個のパラメータを受けられる。
パラメータの型指定の後にピリオドを...
と3つ記述すると可変長パラメータになる。
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 4, 8) // -> 3.75
パラメータの曖昧さの回避のため、可変長パラメータはパラメータリストの最後になるように配置する。
デフォルト値を持つパラメータは可変長パラメータの直前に配置する。
可変長パラメータの後にパラメータを配置すると、さすがにコンパイルエラーになった。
定数パラメータと変数パラメータ
関数のパラメータは関数内で誤って書き換えてしまわないように、デフォルトでは定数として定義されているが、変数に変更できる。
任意のパラメータの先頭にvar
を指定することで、パラメータを複製して変数として使用できる。
func alignRight(var msg: String, count: Int, pad: Character) -> String {
let amountToPad = count - countElements(msg)
if amountToPad < 1 {
return msg
}
for _ in 1...amountToPad {
msg = pad + msg
}
return msg
}
let originalString = "hello"
let paddedString = alignRight("hello", 10, "-") // -> "-----hello"
letも明示的に指定できる。外部パラメータ名が指定されてる場合は外部パラメータ名の前。
可変長パラメータに指定するとXcodeが必ず落ちる。現状無理そう。
In-Outパラメータ
変数パラメータは関数呼び出し元が渡した変数の値は変更されない。
変更したい場合は、In-Outパラメータを使用する。
任意のパラメータの先頭にinout
と指定することでIn-Outパラメータになる。変数のアドレス(変数の前に&
)を渡す。
ただし、定数やリテラルを渡すことはできない。
func swapTwoInts(inout a: Int, inout b: Int) {
let temp = a
a = b
b = temp
}
var num1: Int = 1
var num2: Int = 2
swapTwoInts(&num1, &num2)
println("num1: \(num1), num2: \(num2)") // -> print "num1: 2, num2: 1"
In-Outパラメータにはvar
またはlet
を指定できない。
In-Outパラメータは関数呼び出し先に結果を返すための一つの手段であり、戻り値とは異なる。
関数型
全ての関数は、関数のパラメータと戻り値の型によって作られる関数型。
func addTwoInts(a: Int, b: Int) -> Int {
return a + b
}
func multiplyTwoInts(a: Int, b: Int) -> Int {
return a * b
}
上記2つの関数はInt型のパラメータ2つとInt型の戻り値を持つため、両者の関数型は(Int, Int) -> Int
となる。
以下の関数の場合、関数型は() -> ()
となる。(戻り値の()
はVoid
を表す空のタプル)
func printHelloWorld() {
println("Hello World")
}
関数型の使用
関数型は第一級オブジェクトであるため、他の型と同様に定数や変数として扱うことができる。
var mathFunc: (Int, Int) -> Int = addTwoInts
let ConstMathFunc: (Int, Int) -> Int = addTwoInts
関数型に代入した関数を実行。
mathFunc(2, 3) // -> 5
関数型が一致(パラメータの型・数、戻り値の型が一致)している関数を再代入することも可能。
mathFunc = multiplyTwoInts
mathFunc(2, 3) // -> 6
関数型をパラメータ型に指定
関数型を関数のパラメータの型に指定できる。
func printMathResult(mathFunc: (Int, Int) -> Int, a: Int, b: Int) {
println("Result: \(mathFunc(a, b))")
}
printMathResult(addTwoInts, 3, 5) // -> print "8"
関数型を戻り値に指定
関数型を関数の戻り値の型に指定できる。
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
func chooseStepFunc(backwords: Bool) -> (Int) -> Int {
return backwords ? stepBackward : stepForward
}
let stepBackFunc = chooseStepFunc(true)
stepBackFunc(2) // -> 1
let stepForwardFunc = chooseStepFunc(false)
stepForwardFunc(2) // -> 3
関数のネスト
関数のボディの中に関数をネストして定義できる。
これまでのサンプルは全てグローバル関数として定義してきたが、ネストした関数として定義することで、局所的なプライベート関数を定義できる。
func chooseNestStepFunc(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
let stepBackNest = chooseNestStepFunc(true)
stepBackNest(2) // -> 1
let stepForwardNest = chooseNestStepFunc(false)
stepForwardNest(2) // -> 3
Experiment
個人的に気になった事を実験的に試したもの。
パラメータ名の省略
最初のパラメータはパラメータ名を省略可能。(用途不明)
関数内からアクセスする方法が不明。
func unnameParamFunc(String, name: String) {
println("Hello \(name)")
}
unnameParamFunc("noName", "Jon!") // -> print "Hello Jon!"
クロージャ関数のネスト
クロージャもネスト関数として使える。コード量はそれほど減らないが上級者な感じに。
クロージャにクロージャのネストもいける。
func NestClosureFuncTest() -> (Int) -> Int {
var closure = {(plusNum: Int) -> Int in
let num = 100
let result = num + plusNum
return result
}
return closure
}
let nestClosureFunc = NestClosureFuncTest()
nestClosureFunc(99) // -> 199
関数にクラスをネスト
関数にはクラスをネストすることもできたが、意図していないかも。(Xcodeがよく落ちる)
func wrapperFunc() {
class NestClass {
func method1(str: String) {
println(str)
}
}
let nestClass = NestClass()
nestClass.method1("Test!!")
}
wrapperFunc() // -> print "Test!!"
即時関数
あまり用途はないかもしれないが、クロージャを使ってJavaScriptの即時関数的な書き方もできる。
グローバルに関数を定義したくないけど、コード実行時に即時に何か処理したいときとか。
({(num1: Int, num2: Int) in
println(num1 + num2)
})(1,2) // -> print "3"