構文遊び: 返り値型注釈、複数の返り値

返り値型注釈の構文と関数型の一貫性、複数の返り値を持つ関数呼び出しの構文について思ったことを書く

構文遊びシリーズ

「構文遊び」シリーズは構文について思ったことを書く一連の記事。 プログラミング言語の構文を考えるのは楽しい、というのがモチベーションにある。 特定の言語に対する提案とか文句とかではない

型注釈の記号

返り値型注釈の構文は:を使う言語と-> (または=>) を使う言語をよくみる

モチベーションとして、TypeScriptを書いているときの話をする。 返り値型注釈つきのアロー関数のパラメータリストは関数型の構文と似ているので、関数型を書く際にパラメータリストをコピーすることがたまにある。 ただし返り値型注釈の部分は:から=>に置き換える必要がある

const f = (x: T): U => g(x)
//        ^^^^^^^^^
//        関数を定義するときのパラメータリストと返り値型注釈

type F = (x: T) => U
//       ^^^^^^^^^^^ 関数型

アロー関数のパラメータリストが関数型と同じ構文だったらうれしいかもしれない。 例えば、返り値型注釈を: U=> Uではなく-> Uに揃える:

const f = (x: T) -> U => g(x)
//        ^^^^^^^^^

type F = (x: T) -> U
//       ^^^^^^^^^^^

アロー関数の構文と関数型の構文で異なる記号を使う (->=>) のでややこしいというのはある

複数の値を返す関数を返す構文

関数のパラメータは2つ以上でもよいのに対して、返り値は最大1つという制約がかけられていることがよくある。 関数呼び出しのターゲット (その式の外側) からすると返り値が1つでないと困る

例として、割り算をして商と剰余の2つの値を返す関数divideを定義したとする。 なお構文を似せているだけでTypeScriptの話ではない

// 割り算をして商と剰余の2つの値を返す関数とする
function divide(x: number, y: number) -> number, number {
    return x / y, x % y
}

式文や変数宣言ならともかく、他の式の一部である関数呼び出しが複数の値を返したらどうするか

    g(divide(7, 3) + 1)

少なくない言語で、返り値は最大1つということにして、代わりにタプルなどの複合的なデータ構造や、出力引数を使うようになっている

関数呼び出しの構文を拡張して、2つ目以降の返り値を変数に束縛する構文というのはどうか

    let q

    q = divide(7, 3) -> r
    //  ^ 1つ目の返り値関数呼び出しの評価された値となり、qに代入される
    //               ^^ 2つ目の引数を変数rに束縛

    // 7 = 2 * 3 + 1
    assert(q == 2 && r == 1)

->が返り値型注釈と関数呼び出しにおいて返り値の前に出現する結びつきがあって面白い気がする

拡張

どの返り値をターゲットに渡すか指定できたほうが使いやすい。 関数を定義する時点で返り値に名前を付けられるということにしてみる。 (C# などにある名前付き引数の返り値バージョンといえる)

function divide(x: number, y: number) -> q: number, r: number {
    return q: x / y, r: x % y
}

複数の返り値を持つ関数呼び出しの構文をさらに拡張して、どの返り値をターゲットに戻すのかを.記法で指定できることにする

    let r
    r = divide(7, 3).r -> q
    assert(q == 2 && r == 1)

もっと構文を拡張して、2つ目以降の返り値を変数に束縛するのではなく、それに対してパターンマッチするということもありうる。 しかし式が大きくなって読みにくくなりやすいので、拡張しなくてよいだろう

関連記事