質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.80%

引数の型を number または string に制限し,引数の型と返り値の型が一致する TypeScript のtwice関数の実装方法

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 283

kuro3

score 6

前提・実現したいこと

TypeScript で, 以下のアルゴリズムと型制約を満たす twice 関数のソースコードが欲しい

アルゴリズム

  • [x] 引数の型が number または string でない場合,エラーオブジェクト(実行時エラー)を返すこと(number, string 以外の型を引数で受け取らないようにするotherwiseに相当するもの)
    追記(DR;TL): 今回の質問の本質は「引数の型制約と引数と返り値の一致」なので,条件から削除する. 元々,関数型言語のパターンマッチングのように,想定外の型の条件分岐ができないようにすること,想定した型の条件分岐を強制的に書かせることの2つを満たすやり方をTypeScript で探していた. 一番目をdefaultとErrorの返却で満たせるのでは,と考えて条件に追加していた. 2つ目は,やり方が分からないので条件に入れていない 
  • [x] 数字(number)の場合は, その数字の2倍の値を返すこと
  • [x] 文字列(string)の場合は, その文字列を2回繰り返した文字列を返すこと

型制約

  • [x] 引数の型が number または string でない場合, 型エラー(コンパイルエラー)が発生すること
  • [ ] 引数と返り値の型が一致しない場合, 型エラー(コンパイルエラー)が発生すること (引数が T で返り値が T 以外の場合)

発生している問題・エラーメッセージ

Type 'number' is not assignable to type 'T'.
  'number' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Twicable'.(2322)

Type 'string' is not assignable to type 'T'.
  'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Twicable'.(2322)

該当のソースコード

twice関数 例1 | TypeSctipt Playground

function assertNever(x: any): never {
    throw new Error("Unexpected object: " + x);
}

type Twiceable = number | string;

function twice<T extends Twiceable>(x: T): T {
    switch (typeof x) {
        case 'number':
            return x * 2; // should not be type error
            // return 'hello' + 'hello'; // should be type error
        case 'string':
            return x + x; // should not be type error
            // return 2 * 2; // should be type error
        default:
            return assertNever(x);
    }
}

console.log(twice(2));
console.log(twice('hello'));
console.log(twice(true)); // should be type error

試したこと

返り値を Twiceable に変更した. 上述の返り値のエラーは出なくなるが, 型制約の二番目 を満たせない.

twice関数 例2 | TypeSctipt Playground

function assertNever(x: any): never {
    throw new Error("Unexpected object: " + x);
}

type Twiceable = number | string;

// NOTE: change T of return value to Twicable
function twice<T extends Twiceable>(x: T): Twiceable {
    switch (typeof x) {
        case 'number':
            return 'hello' + 'hello'; // NOTE: should be error
        case 'string':
            return 2 * 2; // NOTE: should be error
        default:
            return assertNever(x);
    }
}

console.log(twice(2));
console.log(twice('hello'));
console.log(twice(true)); // should be error

コメントの指摘を元に考えたソースコード

type Twiceable = number | string;

function twice<T>(x: T): T {
    switch (typeof x) {
        case 'number':
            return x * 2; // should not be type error
            // return 'hello' + 'hello'; // should be type error
        case 'string':
            return x + x; // should not be type error
            // return 2 * 2; // should be type error

    }
    return never;
}

console.log(twice<number>(2));
console.log(twice<string>('hello'));
console.log(twice(true)); // should be type error

補足情報(FW/ツールのバージョンなど)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • maisumakun

    2020/06/16 18:26 編集

    「// should be error」はコンパイルエラーでしょうか、実行時エラーでしょうか?そして、どのようなエラーが起きるべきところでしょうか。

    キャンセル

  • kuro3

    2020/06/16 18:31 編集

    // shoud be error の部分は,コンパイル時やエディタ等での赤線として,実行前に出るコンパイルエラーを想定しています.

    例外として,アルゴリズムの一番目のエラーオブジェクトの部分のみ実行時エラーを想定しています

    キャンセル

  • kuro3

    2020/06/16 18:38

    具体的には,引数の型と返り値の型が異なる場合,以下のようなコンパイルエラーメッセージが出ることを期待しています.

    ```
    // Type 'string' is not assignable to type 'number'.(2322)
    case 'number':
    return 'hello' + 'hello';

    // Type 'number' is not assignable to type 'string'.(2322)
    case 'string':
    return 2 * 2;
    ```

    同様に,指定外の引数の型の場合は,以下です

    ```
    Argument of type 'true' is not assignable to parameter of type 'Twiceable'.(2345)
    console.log(twice(true));
    ```

    キャンセル

回答 1

0

そもそも論として、

引数の型が number または string でない場合,エラーオブジェクトを返すこと

これが、他の型の値を引数へ渡してコンパイルを通ることを意味していると解釈するなら、引数の型はunknownあるいは無制約のTとして受け取るしかないのではないかと考えます。

そして、「stringあるいはnumberならその型」「それ以外ならnever」となる返り値の型を適切の割り振るためには、

  • 関数の型宣言のオーバーロードを行う
  • 型演算で導出する

のどちらかのプロセスが必要となります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/06/16 18:31

    型制約の2番目は、返り値をTにすればいいだけかと思います。

    キャンセル

  • 2020/06/16 19:13

    >
    関数の型宣言のオーバーロードを行う
    型演算で導出する

    を使用して,どのようなプログラムとすればよろしいでしょうか?

    コメントを参考にして考えたプログラムは,以下の形です.ここからどのようにして,オーバーロードや型演算をすればいいのでしょうか?

    ```typescript
    type Twiceable = number | string;

    function twice(x: string): string;
    function twice(x: number): number;
    function twice(x: never): never;

    function twice<T extends Twiceable>(x: T): T {
    switch (typeof x) {
    case 'number':
    return 'hello' + 'hello';
    case 'string':
    return 2 * 2;
    default:
    return never;
    }
    }

    console.log(twice(2));
    console.log(twice('hello'));
    console.log(twice(true));
    ```

    キャンセル

  • 2020/06/16 19:14 編集

    > 関数の型宣言のオーバーロードを行う
    型演算で導出する

    正しくない引数を型エラーにしていいのなら不要ですね。すでにコメントしたように、返り値の型をTにするだけで問題ありません。

    …ともいかなかったですね。

    キャンセル

  • 2020/06/16 19:53

    (通常はanyやasを使って、「外部から見た型の整合性だけ保つ」ように妥協して、「関数内で型を間違えた場合のコンパイルエラー」までこだわらずに実装します)

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る