型とは何か?
今回は型(type)の話をします。漢字の「型」だけみると、プログラミングに限定しても「モデル」(model)だったり「キャスト」(cast)だったりどの「型」なのか定まりませんが、本記事における「型」はデータ型(data type)を指します。
型がなければ意味がない
およそどんなプログラミング言語にも登場するこの型という概念ですが、まだプログラムを知らない子供に「型ってなあに」と問われたら読者のみなさんならどう答えますか?
筆者はこう答えます。「値(value)の意味(meaning)」と。
Software Designの読者であれば100%ご存じのとおり、計算機が扱えるのは0と1からなる二進数の羅列だけです。ここであえて計算機と言ったのは、現代人にとっての「コンピュータ」は計算装置だけではなく、キーボード、マウス、マイクロフォン、タッチパネルといった入力装置、ディスプレイ、スピーカ、プリンタといった出力装置、Ethernet やWi-fi やLTE といった通信装置までまとめたものだから。しかしそういった装置を我々プログラマは直接操作できません。できるのは0 と1の羅列を別の0と1の羅列に変えるだけです。ここで32個の0と1の羅列をご覧いただきましょう。
これが何を意味するか、わかる読者はいらっしゃいますか?
フラグが32個並んでるだけかもしれませんし、整数の1668244581
かもしれませんし、アドレス0x636f6465
を指すポインタかもしれませんし、単精度浮動小数点の6.74221425242669e+22
のことかもしれませんし、ASCII文字列のcode
のことかもしれませんし、色指定のrgba(99,111, 100, 0.39453125)
のことかもしれません……。
つまり、わかるわけありませんよね? 型がわからなければ。コンピュータがただの計算機から万能情報処理機になれたのも、型の賜物(たまもの)。ただの数値の羅列に文字どおり意味を与えるのが型なのです。
型はイイが型付けはイタい
コンピュータに意味ある仕事をさせるのがプログラマの仕事なのですから、ほとんどのプログラミング言語は値(value
)に型(type
)を持たせています。しかしそのやり方はプログラミング言語ごとに異なります。筆者の見解では、プログラミング言語による差異が最も顕著なのがこの型の扱いです。
主な言語をざっと見てみると、二進数で10通りのやり方が存在します。型を必ず書かねばならない言語とそうでない言語。Javaは前者の代表的言語です。言わずと知れた“Hello,world!”を出力するプログラムはこんな感じ。
型を指定しているpublic class
も、public static void
も、main
の括弧のなかにあるString[]
も省けません。そしてソースコードの名前はHello.java
でなければなりません。そして動かすにはTerminal.app
から、
とします。“Hello, world!”と出てきましたか?
ずいぶん面倒ですが、一度javac
したら次回以降はjava Hello
だけで実行できます。Hello.java
をゴミ箱にポイしてもjava Hello
で実行できます。ls
してみるとHello.class
というファイルができています。これをJavaがインストールされた別のコンピュータにそのままアップロードしたうえでjava Hello
と命じるとやはりそのまま動きます。まさにWrite once, run everywhere
なわけです。
そして書かなくてもいいプログラミング言語として、JavaScript[1]もといECMAScriptが挙げられます。“Hello, world!”であれば、
とたった1行。動かす方法はいくつもあるのですが、ここではnode.jsを使うとして、
でjava
のjavac
相当は不要です。これくらい短いとソースコードすらファイルに落とさずとも、
で動いてしまいます。紙幅制限ゆえ、ここではJavaとJa……ECMAScriptだけを取り上げますが、
- ソースコードとは別の実行用ファイルを出力するタイプの言語では型を明記する。CやC++がこれに相当
- ソースコードを直接実行するタイプの言語では型を省略できる。いわゆるスクリプト言語、Perl、PHP、Python、Rubyがこれに相当
という大まかな傾向には賢明な読者のみなさんはすでにお気づきと思います。
ここで本記事の主題であるSwiftがやっと登場します。Swiftはどっち?
ソースコードはこうです。
この1行をhello.swift
という名前のテキストファイルにセーブしたら、
で実行できてしまいます。ところが、
とするとhello
というファイルが生成されて、
でやはり実行できてしまいます。ただしJavaとは異なり、このファイルが実行できるのは同じOSのマシンだけで、Linuxで生成したものをmacOSにアップロードしても動きません。
つまりSwiftはスクリプト言語のように型を略記でき、スクリプト言語のように直接ソースファイルを実行できるにもかかわらず、実行ファイル(executable
)を生成=コンパイル(compile
)することもできるという意味で、両者のユースケース双方をカバーしようというアンビシャスな言語だということです。
名を持つ値の型
“Hello, world!”ではHello, world!
という文字列の値をそのまま使っていましたが、より複雑なプログラムではデータに名前を付けて操作します。たとえばこんな感じ。
一度データを指定したらそのまま最後まで使うものを定数(constant
)、値を変更して使い回すものを変数(variable
)、両方合わせて識別子(identifier
)と呼びます。数以外の型も使うので、筆者個人は「定値」「変値」のほうがよかったと思わぬでもありませんが、それはさておきSwiftの変数には1つの特徴があります。値は変えられても型は変えられないのです。
比較のためにRubyを見てみましょう。次のv
は値だけではなく型(Ruby用語ではclass)も変わっていることが見てとれます。
しかし、Swiftでは次のように怒られてしまいます。
Swiftでは、変数の値は変えられても型は変えられないのです。しかも、一度も値が変更されない場合「var
でなくてlet
にしたら」と警告さえします(図1)。
型は値から推論される
本記事の主題は型なのに、Swiftのコードではまだ型を書いていません。Swiftには型推論(type inference
)という機能があって、推論できる場合はそれを使ってくれるからです。Swiftは何から型を推論しているのでしょうか?
値、です。
let hello = "Hello "
のHello
というリテラル(literal
)が文字列型、String
であることは一目瞭然です。42が整数Int
、42.195が浮動小数点数Double
であることも。
それでは関数(function
)はどうでしょうか? どんな値が来てどんな値を返すかを決めるのはSwiftではなくプログラマですよね。というわけで2つの値を+
する関数はこうなります。
これでplus(40, 2)
は42
になります。簡単ですね。しかし今度はplus(42.0, 0.195)
としてみてください。cannot convert value of type 'Double' to expected argument type 'Int'
というエラーになるはずです。
今度はさきほどのfunc plus
を残したまま次を追加しています。
今度はplus(40, 2)
もplus(42.0, 0.195)
も期待どおりの答えを返したはずです。関数の名前が同じでも引数(argument
)と戻り値(return value
)の型が変わればSwiftは別物として扱ってくれるのです。
しかしこのやり方だと、型の種類だけ同じ名前の関数を用意しなければならなくなりそうでなんとも冗長です。まとめて1つで済ませる方法はあるのでしょうか?
あります。総称型(generics
)が。
今度は先ほどのfunc plus
を両方消して、次を定義します。
どっちもうまく行ったことが確認できるでしょう。先ほどと違うのは<T:Numeric>
という表記。これはNumeric
というプロトコル(protocol
)に準拠(comply
)する型T
という意味になります。そしてInt
もDouble
もNumeric
というプロトコルに準拠しているので、これ1つで(Int,Int)->Int
型の関数と(Double,Double)->Double
型の関数を兼ねることができるわけです。
ところでSwiftは値から型を推論すると先ほど述べました。そして関数自体も値です。ということは名前のない関数リテラルに推論可能な引数を与えたら、関数の型も型推論できないのでしょうか?
それに気づいた読者は鋭い。
({$0 + $1})
だけではダメで、引数があって初めて最初の({$0 + $1})
が(Int,Int)->Int
で(Double,Double)->Double
だという推論が成立するので、型を書かずに済むのです。
これが配列(Array
)や辞書(Dictionary
)など、同じ型の値がいくつも入ったコレクション(Collection
)の処理で威力を発揮します。たとえば総和(sum)を求めたかったら、
と型を明示せずに書けますし、さらにここまで何の断りもなく使ってきた演算子(operator)+
もその正体は総称関数なので、
とさらに簡単に書けます。実際に二項演算子+
が関数であることは
のように確認できます。
まとめ
もう10年以上前にPerl 6をHaskellで実装して全スクリプト言語プログラマの度肝を抜き、現在は台湾の無任所大臣となった天才プログラマ、Audrey Tang(唐鳳)は次のような名言を残しています。
Type 😊
Typing 🙁
邦訳すると「型はイイが型付けはイタい」となりますが、型の痛みを伴わず型を縦横無尽に駆使するという意味で、Swiftは10年前の「人類には早すぎた」Perl 6やHaskellの野望を普通のプログラマにもたらしたと言えるのではないでしょうか。
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
- 第2特集
「知りたい」「使いたい」「発信したい」をかなえる
OSSソースコードリーディングのススメ
- 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
- 短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
- 短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
- 短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)