関数の型——注釈ということば
値の来ない名前
前のレッスンで、変数の型は最初の代入で決まると学びました。x = 10 と書いてあれば、型の目は右側を読んで「x は数」と名簿に書きこめます。
ところが、関数の引数にはこの手が使えません。fn double(x) { x * 2 } の x には、呼ばれるまで値が来ないのです。double(21) と呼ばれれば数ですが、まだ誰も呼んでいない時点で、型の目は x を何と読めばいいのでしょう。
答えは、拍子抜けするほど率直です。書き手が、先に宣言する。 その宣言のことばを、型注釈と呼びます。
「x には数が来ます」という一筆
注釈の書き方はこうです——引数の名前のあとに、コロンと型名を添えます。
型のまちがいは、見つかりませんでした。
double関数(数) → 数fn double(x: 数) ——「x には数が来ます」という、読み手への一筆です。この一筆があれば、型の目は呼び出しを待たずに本体を読めます。x は数、x * 2 は数と数のかけ算で数。
型の目のチップを見てください。double: 関数(数) → 数 ——「数をひとつ受け取り、数を返す関数」と読みます。レッスン2で操作につけた「何を受け取り、何を返すか」の札が、あなたの作った関数にもついたのです。
戻りの型は、書いていない
さっきのチップを、もう一度よく見てください。関数(数) → 数 の「→ 数」の部分、つまり戻りの型は、あなたはどこにも書いていません。
型の目が自分でたどったのです。本体の最後の式は x * 2、x は数だから x * 2 も数——よって戻りは数。書かれていないことを、書かれていることから機械が埋める。これを推論と呼びます。
ほんとうに推論しているのか、戻りの種類を変えて確かめましょう。上の実験室を fn isBig(x: 数) { x > 100 } と isBig(150) に書きかえてください。チップは isBig: 関数(数) → 真偽 に変わります——比較の答えは真偽だと、表を引いて自分で読んだのです。
契約は、両方を縛る
注釈は、ただの読み手へのメモではありません。呼ぶ側と作る側、両方を縛る契約です。2方向から確かめます。
まず呼ぶ側。実験室で double(21) を double(1 == 1) に変えてください。「「double」の1番目の引数は 数 のはずですが、真偽 が渡されています。」——契約とちがう種類を渡そうとした呼び出しが、実行前に止まりました。
次に作る側。fn f(x: 真偽) { x + 1 } と打ちこんでください。今度は本体の + で止まります——「「+」の左には数が来るはずですが、真偽 が来ています。」。「x は真偽」と宣言したのはあなた自身なので、本体で x を数あつかいすれば、約束やぶりはあなたの側です。
注釈を忘れると
では、注釈を書き忘れたらどうなるか。fn f(x) { x * 2 } ——コース2ではふつうに動いていた書き方です。
型の目の答えは「引数「x」の型が書かれていません。」。この言語の型の目は、引数の注釈を必須にする設計を選びました。手がかりのない名前を推測でごまかすより、書き手に一筆を求めるほうが、まちがいの場所がはっきりするからです。
ちなみに型名として書けるのは「数」と「真偽」の2つだけで、fn f(x: もじ) と書くと「「もじ」という型を知りません。」と返ります。コロンは全角の「:」でもかまいません。
⟡ よりみち:型推論を極めた言語たち
「本体が x * 2 なら、x は数に決まっているのでは」——鋭い指摘です。実際、引数の注釈すら書かせず、使われ方から型を逆算する言語があります(ML や Haskell と呼ばれる一族が有名です)。その推論のしくみはみごとなものですが、このコースの射程の外です。ここでは「機械にどこまで任せるかは、言語ごとの設計判断」とだけ覚えてください。
旧友は、型つきでも元気か
最後にひとつ、確かめておきたいことがあります。コース2で書き、コース4でメモ化までした、あの再帰の fib——自分で自分を呼ぶ関数は、この型の目を通れるのでしょうか。
型のまちがいは、見つかりませんでした。
fib関数(数) → 数通ります。チップは fib: 関数(数) → 数、実行すれば 55——旧友は型つきでも元気でした。本体の中の fib(n - 1) を読む時点では fib 自身の戻りがまだ確定していないので、型の目は「戻りはひとまず数」と仮置きして読み進めます。この割り切りで困る場面もあるのですが、ここでは立ち入りません。
✎ 演習:注釈を書き、推論を当てる
次の関数の型のチップに何と出るか、実行前に予想してください。それから実験室で確かめ、最後に呼び出しを onaji(3, 3 == 3) に変えて、エラー文を読んでください。
fn onaji(a: 数, b: 数) { a == b }onaji(3, 3)ヒント1(考え方)
引数は2つとも注釈どおり。戻りは本体の最後の式 a == b の型です。== は表のどの行だったでしょう。
こたえ
チップは onaji: 関数(数, 数) → 真偽(実行すると true)。引数2つはあなたの一筆、戻りの真偽は機械の推論です。onaji(3, 3 == 3) に変えると「「onaji」の2番目の引数は 数 のはずですが、真偽 が渡されています。」——何番目がどう契約違反か、実行前に名指しされます。
このレッスンで分かったこと
- 関数の引数には呼ばれるまで値が来ない。だから書き手が型注釈で先に宣言する——
fn double(x: 数) - 関数の型は
関数(数) → 数と読む——「何を受け取り、何を返すか」の札 - 戻りの型は書かない。本体の最後の式から型の目が推論する
- 注釈は呼ぶ側と作る側の両方を縛る契約。どちらが破っても実行前に止まる
- 注釈を省くか推論で補うかは、言語ごとの設計判断