Swiftの関数 | 引数(Parameters)

Swiftの関数 | 引数(Parameters)

引数です。読み方は「ひきすう」です。引数は、基本的には関数へのインプットを与えます。Swiftではパラメータ(parameters)と呼ぶので、今後はこの2つの呼び名を使います。

ここでは、Swiftにおける引数とは何か?から始めて、引数のある関数、引数の名前とその付け方、関数呼出し用パラメータ名(argument labels)と内部使用パラメータ名(parameter names)の違い、In-Out Parametersを使って関数内部の結果を外に持ち出す方法、などを詳しく説明していきます。

関数の引数(Parameters)

引数(Parameters)って何?

関数の引数(ひきすう)は、基本的には関数へのインプットです。関数の名前の後にカッコ()を付けますが、その中に入る定数(または変数)のことを、引数と呼びます。基本的にSwiftではパラメータ(parameter)と呼ぶようなので、ここでは「引数」や「パラメータ」を使います。

引数なしの関数

このページでは引数の話がメインなんですが、実は引数は関数を定義する場合に必須ではありません。

// 引数なしの関数
func helloWorld() -> String {
    return "ハローワールド!"
}
print(helloWorld())
//"ハローワールド!"と表示

上のサンプルコードでは関数helloWorld()を定義しています。見て分かる通り引数は無く、戻り値はStringです。

ただし注意点が2つあって

  • 関数を定義する時、関数名の後のカッコ()は省略できない
  • 関数を使用する時、関数名の後のカッコ()は省略できない

です。つまり、かっこを略すことはできません。かっこを略すと定数や変数と区別ができませんしね。

複数の引数を持つ関数

パラメータ(引数)は複数あっても問題ありません。関数の定義を説明するときに使ったサンプルコードを拡張して、朝と夜で違う挨拶をする関数を実装してみます。

// 複数の引数を持つ関数
func hello(name: String, isMorning: Bool) -> String {
    let greeting = isMorning ? "おはよう、" : "こんばんわ、"
    return greeting + name
}
print(hello(name:"太郎", isMorning:true))
//"おはよう、太郎"

パラメータを複数定義(使用)する場合は、それぞれのパラメータをコンマ,で区切ります。

helloという名前を持った関数を2つ作りましたが、改めて定義部分だけ抜粋すると

// 引数は1つ
func hello(name: String) -> String {
    ....
}

// 引数は2つ
func hello(name: String, isMorning: Bool) -> String {
    ....
}

このようになっています。名前は同じですが、それぞれの関数が取るパラメータの数が違っているので、2つは独立した別の関数です。また、パラメータの数は同じでもその型が違う場合は関数としては別物扱いになります。

本筋からそれますが、関数内部の定数greeting条件分岐には三項演算子を使っています。If文でも同様の処理ができますが、三項演算子で書くと同じ処理を短く書けますし、greetingを定数にできるので便利です。

引数の名前 | Function Argument Labels and Parameter Names

Swiftでは、引数は2つの名前を持つことが可能で、1つはargument label、もう1つはparameter nameと呼ばれています。それぞれの用途は

  • Argument label = 関数呼び出しの時に指定する引数の名前
  • Parameter name = 関数定義(実装)の時に使う引数の名前

です。関数は定義しないと使えないので、基本となる名前はparameter nameの方です。したがって、これまでのサンプルコードのように、引数の名前を1つだけ付けた場合、その名前はparameter nameです。でも、名前が1つしか無いので、その名前をargument labelとしても使う、という仕様になっています。

以下で定義構文と具体的な使用例を見ていきます。簡単のため今後は、argument labelを「(関数呼び出し用)ラベル」、parameter nameを「(関数定義用)パラメータ名」と呼ぶことにします。

Argument labelやparameter nameという名前はSwift 3から導入された言葉です。Swift 2.xとの対応を書くと、

  • argument label = external parameter name、
  • parameter name = local parameter name

です。

引数の名前の付け方 | Argument labelを付ける場合

ラベル(argument label)を明示的に付ける場合の定義構文は

// Argument labelとparameter name
func functionName(argument label parameter name:type, ....){
    statements
}

のようになります。最初に指定する名前がラベル(関数を呼び出す時に使う名前)、2番目がパラメータ名(関数定義の時に使う名前)になります。2つの名前の間には半角スペースが必要です。

関数呼び出し用ラベルの指定 | Specifying Argument Labels

早速具体例で見てみます。

// ラベルを明示的に指定
func antiCommutator(a firstInteger: Int, b secondInteger: Int) -> Int {
    return firstInteger*secondInteger + secondInteger*firstInteger
}
print("a=1, b=2, {a,b}=\(antiCommutator(a:1,b:2))")
//"a=1, b=2, {a,b}=4"と表示

2つの数字の掛け算を、順番を入れ替えて足すという演算{a,b}=a*b+b*aを関数にしました。最初のパラメータはaがラベル(関数呼び出し用)、firstIntegerがパラメータ名(関数定義の内部で使用)です。2番目のパラメータも同様です。

外部パラメータを指定した場合、関数呼び出しの時に必ずそのパラメータ名を使用しなければいけません。したがって、次のような呼び出しはコンパイルエラーになります。

// firstIntegerとsecondIntegerは関数内部でのみ使用可能
antiCommutator(firstInteger:1, secondInteger:2)
//error: incorrect argument labels in call (have 'firstInteger:secondInteger:', expected 'a:b:')

関数呼び出し用ラベルの省略 | Omitting Argument Labels

関数を呼び出す度にパラメータのラベルを指定するのが面倒くさい、という場合は、ラベルにアンダースコア_を指定します。先ほど作ったantiCommutator()関数を改造してみましょう。

// 関数呼び出し用ラベルの省略
func antiCommutator(_ firstInteger: Int, _ secondInteger: Int) -> Int {
    return firstInteger*secondInteger + secondInteger*firstInteger
}

// 呼び出しの際にラベルを省略出来る
print("a=2, b=3, {a,b}=\(antiCommutator(2,3))")

最後の行の使用例から分かるように、ラベルにアンダースコアを指定すると、呼び出す際に名前を省略して値だけをセットすれば良くなります。

上記例のように、関数の名前からパラメータが容易に推測出来る場合は便利な仕様です。ただ、余りむやみに利用すると、パラメータが何を指しているのか分からなくなる可能性があるので注意が必要です。

補足1: Swift 2.xではデフォルトで第一引数のラベルが省略可能

Swift 2.xまではパラメータの第一引数だけは特別扱いで、デフォルトでラベル名を省略可能な仕様になっていました。Swift 3から全てのパラメータが同等の扱いになり、第一引数でもラベル名を省略することが出来ません。もしSwift 2.x系の仕様が良い!という場合は、関数(またはメソッド)の第一引数のラベルに明示的にアンダースコアをセットしないといけません。

補足2: print関数について

print関数を使用すると分かりますが、インプットする文字列にラベルを付ける必要がありません。

print("hello")
//"hello"と表示

これはprint関数の定義を見ると分かりますが、第一引数のラベルにアンダースコアが指定されているためです。

参考: Swift Standard LibraryのAPI Reference、print関数の仕様

引数のデフォルト値

関数のパラメータを設定した場合は、関数呼び出しの場合にそれを指定しないといけません。しかし、関数を定義する際にデフォルトの値をセットしておけば、呼び出しの時に値を指定するのを省略することができます。

print関数について少し補足したので、ほぼ同じような関数を自作してデフォルトパラメータの振る舞いを見てみます。少し長いです。

// String限定の自作print関数のようなもの
func showArray(_ array: [String], separator: String = " ", terminator: String = "\n") -> String {
    // 空白文字ならリターン
    guard !array.isEmpty else {
        return terminator
    }

    // 1文字だけならそのまま返す
    let count = array.count
    if count == 1 {
        return array[0] + terminator
    }
    
    // セパレータで連結
    var output = ""
    for index in 0..<count {
        // 最後はセパレータ不用
        let s = index==count-1 ? "" : separator
        output += array[index] + s
    }
    
    // ターミネータを付けてリターン
    return output + terminator
}

実はもっと短く出来るのですが、敢えて全部自力で実装して中身を理解しやすいようにしています。長いので順番に説明します。

パラメータは3つ

関数showArrayはパラメータを3つ持っています。

func showArray(_ array: [String], separator: String = " ", terminator: String = "\n") -> String {
    ....
}

1つ目はStringの配列です。パラメータ名はarrayですがラベルにアンダースコアが指定されているので、この関数には名前無しで値を指定出来るということが分かります。

2つ目と3つ目のパラメータの役割は後で詳しく説明しますが、両方ともデフォルト値がセットされていることが分かります。したがって、関数showArrayを実行する場合、最低限必要なのは最初のパラメータであるStringの配列だけです。

例外の処理 | 配列が空の場合と要素が1つの場合

配列要素が空、または1つだけの場合は例外として特別な処理を実行します。

guardを使って配列が空の場合を排除しています。

// 空白文字ならリターン
guard !array.isEmpty else {
    return terminator
}

出力する場合文字列の最後に終端子(terminator)を付けます。デフォルトの終端子は\nになっていますが、これは改行を表す特殊文字です。したがって、パラメータterminatorを変更しない限りは、常に改行するような文字列を出力することになります。

次は配列要素が1つの場合です。

// 1文字だけならそのまま返す
let count = array.count
if count == 1 {
    return array[0] + terminator
}

最終的には配列要素を並べて出力するんですが、要素間にセパレータ(2番目のパラメータseparator)を挟みます。配列要素が1つだけの場合はセパレータを挟む必要がないので、上記サンプルコードのように終端子を付けてそのままリターンしています。

配列要素を連結する

最後の処理は配列要素が2つ以上ある場合です。

// セパレータで連結
var output = ""
for index in 0..<count {
    // 最後はセパレータ不用
    let s = index==count-1 ? "" : separator
    output += array[index] + s
}

// ターミネータを付けてリターン
return output + terminator

for-inループ内部で配列のインデックスが必要なので、ちょっと面倒くさいループの回し方をしています。また、最後の要素の後ろにはセパレータを付けたくないので、三項演算子を使って条件分岐しています。連結した文字列は終端子を付けて戻しています。

三項演算子をカッコで囲むと

output += array[index] + (index==count-1 ? "" : separator)

このように1行で表現することも可能です。

実際に使ってみる

先ず最初のパラメータだけを指定した場合ですが、

showArray([]) // "\n"
showArray(["hello"]) // "hello\n"
showArray(["hello", "world", "2016"]) // "hello world 2016\n"

このように出力されます。今最初のパラメータにデフォルト値を指定していないので、実行する時は空の配列[]を入れておかないとエラーになります。

2番目と3番目のパラメータを変えることも可能で、

showArray(["hello", "world", "2016"], separator:", ") // "hello, world, 2016\n"
showArray(["hello", "world", "2016"], separator:" | ", terminator: "") // "hello | world | 2016"
showArray(["hello", "world", "2016"], terminator: ".") // "hello world 2016."

という感じになります。面白いのは3番目の実行例で、デフォルト値が入っているパラメータが複数ある場合、変更したいパラメータだけを指定することも出来ます。ただし、関数呼び出しの時にパラメータの順番を変えるとコンパイルエラーになるので注意が必要です。

可変パラメータ(Variadic Parameters)

あらかじめパラメータの数が分からない場合などに使えるのが可変パラメータ(Variadic Parameters)です。「Variadic」は難しい単語ですが、変数variableと前半は共通なので、「変わる」という感じは分かると思います。

可変パラメータは型の名前の後に3つのピリオド...を入れることで指定できます。先程作ったshow関数は、配列を入れないと出力出来ない仕様だったので、可変パラメータを使って改良してみます。

// 可変パラメータ
func show(_ array: String... , separator: String = " ", terminator: String = "\n") -> String {
    ....
}

変更点は最初のパラメータの型指定を[String]からString...にしただけなので、残りは省略します。可変パラメータで指定したString...は、関数内部では配列リテラル[String]になります。

関数呼び出しは

show("hello", "world", "2016")
//"hello world 2016\n"と表示

という形になります。可変パラメータでは、複数の値をコンマで切って指定することが出来るので便利です。実はprint関数の最初のパラメータは可変パラメータになっています。

ただし、可変パラメータは1つの関数に1つしか使用できませんので、2つの可変パラメータを定義しようとするとコンパイルエラーになります。

引数は定数?それとも変数?

関数の引数(パラメータ)はデフォルトでは定数です。したがって変更を加えようとするとコンパイルエラーになります。

// パラメータはデフォルトでは定数
func hello(name: String, isMorning: Bool) -> String {
    // パラメータnameは定数なので変更出来ない
    name = ""
    let greeting = isMorning ? "おはよう、" : "こんばんわ、"
    return greeting + name
}
//error: cannot assign to value: 'name' is a 'let' constant

これは、間違えてパラメータを変更するケースを防いでくれるので、非常に良い仕様になってます。では、パラメータを変数として取り扱いたい場合はどうすれば良いでしょうか?その場合は、次のセクションで紹介するin-out parametersという特別なパラメータを使用します。

Swift 2.2から関数のパラメータを変数宣言することは非推奨になりました。コンパイルすると警告(warning)が出ます。Swift 3で完全に削除されました。

// パラメータの変数宣言(Swift 2.x)
func hello(var to: String) {
    to += ", hello"
    print(to)
}
// コンパイルエラー
// warning: 'var' parameters are deprecated and will be removed in Swift 3

In-Out Parameters

一つ前の例で「変数のパラメータは関数の外に持ち出すことができない」と書きました。しかし、状況によってはパラメータの結果を関数の外に出したい場合もあるかもしれません。これを実装するにはIn-Out Parametersを使います。幾つかルールがありますので、箇条書きにします。うまい訳が思いつかないので、ここではin-out parameterをそのまま使います。

  • In-out parameterは変数でなければならない(定数やリテラルはダメ)
  • In-out parameterはデフォルト値を持てない
  • 可変パラメータ(Double...など)はin-out parameterにできない

実際の例で見てみます。公式マニュアルにあるswap関数が最も分かりやすいので、ここではそれをDoubleで実装してみます。

// In-out parameterの例
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someDouble = 3.14
var anotherDouble = -3.14

print("前: someDouble = \(someDouble), anotherDouble = \(anotherDouble)")
swapTwoDoubles(&someDouble, &anotherDouble)
print("後: someDouble = \(someDouble), anotherDouble = \(anotherDouble)")
//前: someDouble = 3.14, anotherDouble = -3.14
//後: someDouble = -3.14, anotherDouble = 3.14

このように、inoutをパラメータに指定することにより、関数内部の演算結果を関数の外に持ち出すことができます。

Swift 2.xではパラメータ名(厳密に言うとラベル)の前にinoutを付ける構文でしたが、Swift 3以降では型名の前に変わっています。

// Swift 2.xでのinout指定
func swapTwoDoubles(inout _ a: Double, ....

// Swift 3以降
func swapTwoDoubles(_ a: inout Double, ....

ここで1つ注意があって、変数を関数パラメータに渡す際には、変数の直前にアンドマーク&を付けないといけません。

var someDouble = 3.14
var anotherDouble = -3.14
swapTwoDoubles(&someDouble, &anotherDouble)

この指定によって「これらの変数は関数内部で変更可能で、かつ関数の外に持ち出せる」ということを伝えています。

まとめ

  • 関数の引数(パラメータ)は必須ではない
  • 関数の引数は、関数呼び出し専用の名前(argument label)と、関数定義用の名前(parameter name)がある
  • 引数にデフォルト値を入れることができる。その場合、パラメータを省略して関数を呼び出すことが可能
  • 引数を関数の外に取り出すには、inoutを引数に指定する。関数呼び出しの際には、パラメータ名の直前にアンドマーク&を付ける必要がある。