関数という名の(定|変)数
連載第1回目(もちろん0から数えています)の今回は、前回予告どおり関数型言語としてのSwiftを見ていきます。ところで「関数型言語」とはいったいなんでしょう。実のところあいまいな用語ではあるのですが、「最低限文化的な関数型」の関数は第一級オブジェクトであることに異議のある読者はあまりいらっしゃらないと思います。「第一級オブジェクトってなんぞや」おさらいしてみるとこういう感じでしょうか。
- 変数に代入できる
- 関数の引数にできる
- 関数を返す関数が書ける
JavaScript、Lua、Perl、Python、PHP、Rubyといった今日日のスクリプト言語はその意味において「最低限文化的な関数型言語」の定義を満たしています。一例としてJavaScriptを見てみましょうか。前回のFizzBuzzを普通に書くとこんな感じでしょうか(リスト1)。
見てのとおり、fizzbuzz
は変数名で、そこに関数を代入しています。Swiftではどうでしょうか? こうかな?
いいえ、ちょっと違います。正しくはこうです。
あるいはこうです。
まとめるとこう。
- funcは不要
- 型指定は必要
- →前者は
{型定義付き引数 in 定義}
- →後者は
変数名:型 = { 引数名 in 定義 }
変数指定を{}
の外ではなく中でやるあたり、JavaScriptよりむしろRubyっぽいですね。実はもっとRubyっぽいことをこれから見ていきます。FizzBuzzをちょっと変えて、「1から100に対応した結果を出力」するのではなく、「1から100まで入った配列をFizzBuzz変換」することを考えてみましょう。JavaScriptではこうかな(リスト2)。
ここで、fizzbuzz()
を無名化するとこうなります。
動くことは動きますが、()
の中に入ったfunction
はなんとも不格好です。Rubyならどうでしょうか?
「メソッドの最後の引数がブロックの場合、()の外に書いてよい」というルールのおかげでずいぶんとエレガントです。それではSwiftでは?
見てのとおり、map
の後の無名関数を()
でくくらなくても動きました。無名関数の引数と戻り値のほうも指定していません。このあたりは、Swiftの父の1人であるChris Lattnerもホームページで「アイデアを拝借した」と率直に答えているとおりです。
しかし、Lattnerが言っていない、拝借されたアイデアがもう1つあります。コードで見てみましょう(リスト3)。
なんと、in
が消えてしまいました。その代わりn
があった位置に$0
という未宣言の変数が存在します。この変数のことをプレイスホルダー(placeholder)変数というのですが、$0
が最初の引数、$1
が次の引数といった具合です。これ、どう見てもPerl 6のアイデアなんですが……。
{}は全部関数!
ここで今まで見てきたSwiftのコードの{}
をじーっとよく見てみてください。何か見えてきませんか? ヒントを1つ。if
の後ろの{}
は、Swiftでは省略不可能です。if
の後ろの条件には()
がないのに……。
そう。Swiftでは、意味論的(semantically)には{}
はブロックで、つまり関数なのです!
本当かどうか、確かめてみましょう。どうやって?――if
を再発明して!(リスト4)
動いてます(図1)。動いちゃってます。IF
は関数で、しかも中に条件分岐がいっさいないのに。それにしても、関数IF
はずいぶん奇妙きてれつな形をしています。ちょっと冗長に書き直してみましょう(リスト5)。
きてれつではありますが、筋は通ってるのがおわかりいただけたでしょうか。
Swiftが「筋を通しやすい」のは、辞書(dictionary)のリテラルに、Perlによって一般化しJSONによって不朽の地位を得た{}
を使っていないこともあります。Swiftでは辞書リテラルは[key:value]
の形式をとるので{}
が辞書リテラルなのかブロックなのかあいまいになることはありません。{}
を見たら例外なくブロック、つまり関数リテラルだと言い切ってしまってよいのです。
ではfuncは不要かというと……
ここまで見てきたとおり、Swiftにおける関数の本質は{}
にあり、func(){}
という構文糖衣は無用の長物に思えます。しかしこれが必要になるケースが2つほど存在します。
1つは、再帰関数。先ほどの例のfact
も再帰的に定義されています。ここでもう一度「ふつう」に書き直してみましょう。
これを、こう書き直してみましょう。
一見動きそうですが、こんな感じに怒られてしまいます。
これを防ぐためには、先にvar
、つまり変数として宣言だけしておいたうえで、それに定義を代入しなければなりません。
なんとも冗長であるうえに、これではfact
を上書きできてしまいます。残念ながら(?)、Swiftには("use strict"されていない
)JavaScriptのarguments.callee()
やR言語のRecall()
相当の、自己再帰のための構文はありません。素直にfunc
しましょう。どうしてもという方のために、
という手法も一応紹介だけしておきます。何を意味するかは、次回のお楽しみということで。
ただし、ここでfunc
が必要になる実例がもう1つ出てきました。総称関数(Generic Functions)です。総称関数とは何か? 型も「変数」になっている関数です。型が変数とはどういうことか? これまた実例で見てみましょう。
まったく同じ名前、まったく同じ定義の関数が2つあります。違いはなんでしょうか? そうです。型です。Swiftでは「関数のフルネーム」は名前と引数の型の組み合わせであり、引数の型に応じて違う関数が呼ばれています。実にありがたい機能ですが、定義が同じなのに型ごとに別の関数を定義しなければならないとなるとずいぶん面倒です。もっとDRY[1]な方法はないでしょうか?
そこで総称関数です。
C++のテンプレートやJavaのジェネリクスに相当するこの機能は、Swiftにもしっかり実装されています。add(4, 2)
では4
と2
からTは
Int
と推論され、結果add(x:Int, y:Int)->Int
がコンパイラーによって生成され、add(4,2)
ではadd(x:String,y:String)->Int
が生成されるというわけです。
しかし、こう書くことはできません。
なぜそう書けないのか。次回はSwiftにおける総称関数を詳しくみていきます。
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
- 第2特集
「知りたい」「使いたい」「発信したい」をかなえる
OSSソースコードリーディングのススメ
- 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
- 短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
- 短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
- 短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)