関数の型——注釈ということば

よみもの+手を動かす時間:およそ25分

値の来ない名前

前のレッスンで、変数の型は最初の代入で決まると学びました。x = 10 と書いてあれば、型の目は右側を読んで「x は数」と名簿に書きこめます。

ところが、関数の引数にはこの手が使えません。fn double(x) { x * 2 } の x には、呼ばれるまで値が来ないのです。double(21) と呼ばれれば数ですが、まだ誰も呼んでいない時点で、型の目は x を何と読めばいいのでしょう。

答えは、拍子抜けするほど率直です。書き手が、先に宣言する。 その宣言のことばを、型注釈と呼びます。

「x には数が来ます」という一筆

注釈の書き方はこうです——引数の名前のあとに、コロンと型名を添えます。

型の目(実行する前の検査)

型のまちがいは、見つかりませんでした。

double関数(数) → 数
評価された値
42

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関数(数) → 数
評価された値
55

通ります。チップは 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: 数)
  • 関数の型は 関数(数) → 数 と読む——「何を受け取り、何を返すか」の札
  • 戻りの型は書かない。本体の最後の式から型の目が推論する
  • 注釈は呼ぶ側と作る側の両方を縛る契約。どちらが破っても実行前に止まる
  • 注釈を省くか推論で補うかは、言語ごとの設計判断