総称関数の基本
連載第2回目の今回は、
JavaScriptなら、
function identity(x) {
return x;
}
Perlなら、
sub identity {
shift;
}
Pythonなら
def identity(x):
return x
Ruby なら、
def identity(x):
x
end
どれも実質1行。簡単ですね。
ところが動的言語にとってこれほど簡単なことが、
int int_identity(int x){
return x;
}
double double_identity(double x){
return x;
}
char *str_identity_s(char *x){
return x;
}
要するに型の数だけ実装が必要になってしまうのです。中身が同じでも、
こうなります。
func identity<T>(x:T)->T {
return x
}
実際に動かして確認してみましょう

let i = identity(42)
let d = identity(42.195)
let s = identity("Marathon")
確かに動いています。
ここで<T>
を取っ払ってみましょう。どうなりましたか? Use of undeclared type T
というエラーが出たはずです。これで<T>
の役割がわかりました。T
は型の名前ではなく型の変数なので、
ここで問題です。上記のidentity(42)
のidentity
と、identity(42.
のidentity
は同じものでしょうか?
コンパイルされたコードを見てみれば
% nm identity
0000000100000ee0 T __TF8identity8identityU__FQ_Q_
U __TFSSCfMSSFT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS
U __TFSsa6C_ARGCVSs5Int32
U __TFSsa6C_ARGVGVSs20UnsafeMutablePointerGS_VSs4Int8__
U __TMdSS
U __TMdSd
U __TMdSi
0000000100001050 S __Tv8identity1dSd
0000000100001048 S __Tv8identity1iSi
0000000100001058 S __Tv8identity1sSS
0000000100000000 T __mh_execute_header
0000000100000f10 T _main
0000000100000e10 t _top_level_code
U dyld_stub_binder
_Tv8identity1
までが同じで、
さらに、identity
を使用している行をコメントアウトしてコンパイルしなおすと、
% nm identity
0000000100000f00 T __TF8identity8identityU__FQ_Q_
U __TFSsa6C_ARGCVSs5Int32
U __TFSsa6C_ARGVGVSs20UnsafeMutablePointerGS_VSs4Int8__
0000000100000000 T __mh_execute_header
0000000100000f30 T _main
0000000100000ef0 t _top_level_code
U dyld_stub_binder
identity
は完全に消えてしまいました。このことから、
総称関数は、
前回私はこう書きました。
- 「最低限文化的な関数型」
の関数は第一級オブジェクト - 変数に代入できる
- 関数の引数にできる
- 関数を返す関数が書ける
総称関数は、
満たしているのであれば、
let identity:<T>(T)->T = { x in
return x
}
「関数は第1級オブジェクト」
これで、func
がいらない子でないことが証明されました。定義はできても代入はできないSwiftの総称関数には欠かせないのです。
型の型もやはり型
この総称関数は、
let numbers = [3,2,1,0]
let strings = ["three","two","one","zero"]
let sorted_nums = numbers.sorted { $0 < $1 }
let sorted_strs = strings.sorted { $0 < $1 }
sorted_nums // [0, 1, 2, 3]
sorted_strs // ["one", "three", "two", "zero"]
この並べ替えを決める関数ブロック、(Int,Int)->Bool
、(String,String)->Bool
。なのに同じように書けるのは、Array<T>
が1つだけ定義してあって、Array<Int>
やArray<String>
を生成しているのですArray<Array<T>>
となるわけです。
型を型にはめるプロトコル
この総称型の
struct Point {
var x:Int
var y:Int
}
var origin = Point(x:0, y:0)
println(origin)
ここで何がprintln
されるでしょう? (x:0,y:0)
とか
とかとはなりません。Playgroundでは__
、proto.
とかと、Point
は自分がどうプリントされるべきかを知らないのです。どうやってPoint
型にそれを教えてあげればいいのでしょうか?
まず、struct Point
の後ろに:Printable
とつけてみてください。するとSwiftはType'Point' does not conform to protocol'Printable'
と文句を言ってくるはずです。次に、Point
の定義の中でvar description:String
を定義してみてください。まとめるとこんなふうに。
struct Point:Printable {
var x:Int
var y:Int
var description: String {
return "Point(x:\(x), y:\(y))"
}
}
var origin = Point(x:0, y:0)
println(origin)
これをコンパイルすると、Point(x:0,y:0
)と出てきます。
Playgroundだと、__
のママなのですが、
この、struct Point
はPrintable
プロトコルに準拠している。なぜならdescription
プロパティを持つからだ」。プロトコルという言葉は本誌の読者であれば毎号必ず目にしているかと思いますが、
上記のPoint
では、Dictionary
は次のように定義されています
struct Dictionary<Key:Hashable, Value>
「辞書の値Value
はどんな型でもOKだけれども、Key
はHashable
プロトコルに準拠したもののみですよ」Point
を
var fromto = [origin:origin]
と書くと、Type 'Point' does not conform toprotocol 'Hashable'
と文句を言ってきます。
それでは、Point
をHashable
プロトコルに準拠させるにはどうしたらよいでしょうか? Printable
の時と同様に、var hashValue:Int
を定義すればOKかと思いきや、Type 'Point'does not conform to protocol 'Equatable'
と文句を言ってきます

そうなんです。実は==
の再定義も可能なんですよ。Swiftならね。
おわりに
次回はいよいよ私の一番お気に入りのSwiftの機能、
本誌最新号をチェック!
Software Design 2022年9月号
2022年8月18日発売
B5判/
定価1,342円
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識 - 第2特集
「知りたい」 「使いたい」 「発信したい」 をかなえる
OSSソースコードリーディングのススメ - 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画] Red Hat Enterprise Linux 9最新ガイド - 短期連載
今さら聞けないSSH
[前編] リモートログインとコマンドの実行 - 短期連載
MySQLで学ぶ文字コード
[最終回] 文字コードのハマりどころTips集 - 短期連載
新生「Ansible」 徹底解説
[4] Playbookの実行環境 (基礎編)