6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年4月9日 りあクト! 第4章 型アサーションと型ガード asによる型アサーション (p.203~)

復習

in演算子は、指定したオブジェクト上に指定したプロパティがあるかを判定できます。

"プロパティ名" in オブジェクト; // true or false

アサーション

型を断定するものです。as 断定する型とします。 コンパイラによる型の解釈を変えているだけで、実際の値が変化しているわけではありません。

問題点

以下のケースでは、無理やりコンパイラが通っており、実行時にエラーになっています。つまり、型の安全性が保証されていません。

type User = { username: string; address: { zipcode: string; town: string } };
const str:unknown = { username: "patty", town: "Maple Town" };
const user = str as User;
console.log(user.address.town); // TypeError: Cannot read property 'town' of undefined

アサーションはスーパータイプ、サブタイプの関係性がないと使用することが出来ません。

型ガードを使うことで、任意の型に絞って型安全性を保ちながら型を割り出すことができます。

型ガード

  • 型ガードの種類
    • typeof・・・プリミティブな値の型を取得し条件式でコンパイラに伝え型を保証します。
    • instanceof・・・クラスのインスタンスか真偽値を取得し条件式でコンパイラに伝え型を保証します。
    • ユーザー定義型ガード・・・型述語(関数の戻り値の型を引数名 is 型とする)を使った関数などを自身で作って型を絞り込むしくみを作ります。

アサーションと型ガードの違い

アサーション

中身の型がわからないものの型を断定する事で、その値のプロパティやメソッドにアクセスできるが、型定義にあって実際のプロパティやメソッドにないものにアクセスするとコンパイルエラーにならずに実行時にエラーになるため型安全が保証されていません。

type User = { username: string; address: {zipcode: string; town: string} };

const str = `{"username": "patty", "town": "Maple Town"}`;
const data: unknown = JSON.parse(str);
const user = data as User;

// TypeScriptのコンパイルエラーが出ない。
// TypeScriptのコンパイル時にはエラーが出ず、実行時にエラーが出る。
console.log(user.address.town); // => Cannot read properties of undefined (reading 'town') 

型ガード

中身の型がわからないものをif文で型を絞り込むため、型安全が保証された上でプロパティにアクセスできます。 以下はtypeofを使った例です。

const foo: unknown = '1,2,3,4';

if (typeof foo === 'string') {
    console.log(foo.split(','));
}

consolo.log(foo.split(',')); // compile error!
  • typeof で型ガードを行なっています。
  • typeofによってstring型だと判断されたブロック内では、変数fooに stringのプロトタイプメソッドである split()が使えています。

本章

カリー化

カリー化の説明は「りあクト! 【Ⅰ. 言語・環境編】p.136」に記述されている為、以下に引用します。

複数の引数を取る関数を、引数が「元の関数の最初の引数」で戻り値が「引数として元の関数の残りの引数を取り、それを使って結果を返す関数」である高階関数にすることを「カリー化」と呼ぶ。

// JavaScript
const add = a => b => a + b;

// TypeScript
// TypeScriptでは、関数の引数に型アノテーションをつける必要がある
const add = (a: number) => (b: number) => a + b;

console.log(add(1)(2)); // 3

型ガード 応用

type Result<T, E extends Error> = Ok<T, E> | Err<T, E>;

export class Ok<T, E extends Error> {
    constructor(readonly val: T) {}
    isOk = (): this is Ok<T, E> => true;
    isErr = (): this is Err<T, E> => false;
}

export class Err<T, E extends Error> {
    constructor(readonly err: E) {}
    isOk = (): this is Ok<T, E> => false;
    isErr = (): this is Err<T, E> => true;
}

export const withResult = <T, A extends any[], E extends Error>(
    fn: (...args: A) => Promise<T>,
) => async (...args: A): Promise<Result<T, E>> => {
    try {
        return new Ok(await fn(...args));
    } catch (error) {
        if (error instanceof Error) {
            return new Err(error as E);
        }
    }
};
  • このコードに関しては全体を読み解きながら復習のための議論をしましたが、ブログにアウトプットする内容にはならなかったため、コードの掲載のみにとどめます。

参考

りあクト! 【Ⅰ. 言語・環境編】 p.206

サルでもわかるカリー化とそのメリット