[TypeScript] ちょっとしたこと。

■typeとinterfaceの使い分けと使い方
 どちらもほぼ同じように使えるが
 interfaceはオブジェクトやクラスの形状を定義し
 type(型のエイリアス)はinterfaceでは表現できない複雑な「型」を扱うのに使用する。

1.typeが扱える複雑な型
1.1.プリミティブ型(Primitive Type)
type UserId = string;
1.2.ユニオン型(Union Type)
type Status = "idle" | "loading" | "success" | "error";
1.3.タプル型(Tuple Type)  実際はだたの配列(Array)  オブジェクト表記でデータを持つより軽い。  可読性が下がらない場所か最適化が必要な場所で使う。  push等による型崩壊を防ぐため、原則 `readonly` を付与する。
type Point = readonly [x: number, y: number];

// 使用例
const path: Point[] = [
  [0, 0],
  [10, 20],
  [35, 40],
];
for (let i = 0; i < path.length; i++) {
  const [x, y] = path[i];
  console.log(`X: ${x}, Y: ${y}`);
}
1.4.マップドタイプ(Mapped Types)などの高度な型操作
type ReadOnly<T> = {
  readonly [P in keyof T]: T[P];
};
1.5.マップドタイプの応用  型引数(ジェネリクス)を用いた条件付き型による再帰的な定義 - infer(型の自動推論・抽出) 条件付き型(extends ? :)の右側で使い 型の一部分を「変数」として型抜きする機能
type DeepReadonly<T> = T extends Function
  ? T
  : T extends Map<infer K, infer V>
    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
    : T extends Set<infer U>
      ? ReadonlySet<DeepReadonly<U>>
      : T extends object
        ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
        : T;

  ※上記は標準では用意されていない自前の深層完全不変の定義。
    内部全てが不変(readonly)になる。はず。
1.6.判別共用体型(Discriminated Union Type)  共通するリテラル型のプロパティが「判別子」として扱われる。  判別子は複数可能。
interface NetworkSuccess {
  status: "success"; // 判別子(Discriminant )
  data: string[];
}

interface NetworkError {
  status: "error";   // 判別子(Discriminant )
  error: Error;
}

// 判別共用体型を type で合成
type NetworkResponse = NetworkSuccess | NetworkError;
  or
type NetworkResponse = 
 | { status: "success"; data: string[] }
 | { status: "error"; error: Error };

function handleResponse(res: NetworkResponse) {
  // res.status の値によって、コンパイラが型を確定させる
  if (res.status === "success") {
    console.log(res.data); // ここで data にアクセス可能
  }
  else {
    console.error(res.error.message); // ここで error にアクセス可能
  }
}
2.継承・合成のやり方 2.1.interface同士(extends)
interface Animal {
  name: string;
}

interface Dog extends Animal {
  bark(): void;
}
2.2.type同士(& インターセクション)
type Flyable = {
  fly(): void;
};

type Swimmable = {
  swim(): void;
};

type Duck = Flyable & Swimmable;

type Duck2 = Duck & {
  sleep(): void;
};
2.3.interfaceとtypeの相互合成
// 1. interface が type を extends する
type Identifiable = {
  readonly id: string;
};
interface Product extends Identifiable {
  price: number;
}

// 2. type が interface を & で合成する
interface Serializable {
  serialize(): string;
}
type Config = Serializable & {
  version: string;
};

※同名プロパティが存在する場合はエラーになるがそれぞれタイミングが異なる。
3.関数の定義 3.1.type での定義:関数型リテラル (Function Type Literal)  通常の「純粋な関数」はこれ一択。
type LogFn = (message: string) => void;

const logger1: LogFn = (msg) => console.log(msg);
3.2.interface での定義:コールシグネチャ (Call Signature)  関数であり、かつプロパティも持つハイブリッド型を作る時に使う。
interface LogInterface {
  (message: string): void; // コールシグネチャ
  level: number;           // プロパティ
}
const logger2: LogInterface = Object.assign(
  (msg: string) => {
    console.log(`[Level ${logger2.level}] ${msg}`); 
  },
  { level: 3 }
);
結論:定義の仕方は違っても呼び出し側のコードは `logger("msg")` で完全に同じ。    違いは「型を定義する側」が、関数にプロパティを持たせたいかどうかだけ。 3.3.オブジェクトのプロパティとしての定義  メソッドシグネチャ (Method Signature)  関数単体ではなく、「オブジェクトが持つ関数(メソッド)」の形状を定義する構文
interface Drawer {
  draw(): void; // 「メソッドシグネチャ」
}
■for文について (JavaScript)
const items = ["a", "b", "c"];
const itemObj = { a: 1, b:2, c:3 };

文法
[1] for (let i = 0; i < items.length; i++) { xxxx }
[2] for (const item of items) { xxxx }
[3] for (const [i, item] of items.entries()) { xxxx }
[4] items.forEach((item, i) => { xxxx });
[5] for (const [key, value] of Object.entries(itemObj)) { xxxx }

解説
[1]: 最速・最軽量
[2]: 標準
[3 - 4]: 他言語とは違いCPUとメモリとGCに負荷をかけるので高負荷領域では使用NG。
[5]: 外界との境界(バリデーション、シリアライズ)でしか使用してはならない
■マップ定数の作り方
// 定数の定義
//   ROLES: 原本リスト。
//   ROLE:  ROLESから抽出したユニオン型。
//   ROLE_PERMISSIONS: ROLE型をキーとしたマッピング定数。
//
//   isRole: 型ガード関数(型述語タイプ)
//           実行時の型ガードはTypeScript世界のROLEでは行えない
const ROLES = ["admin", "manager", "user"] as const;

type ROLE = typeof ROLES[number]; // --> "admin" | "manager" | "user"

// const ROLE_PERMISSIONS: Readonly<Record<ROLE, readonly string[]>> = {
//   or
const ROLE_PERMISSIONS: Readonly<{ [K in ROLE]: readonly string[] }> = {  
  "admin":   ["Read", "Write", "Delete", "Execute"],
  "manager": ["Read", "Write", "Delete"],
  "user":    ["Read"],
};

function isRole(value: string): value is ROLE {
  return (ROLES as readonly string[]).includes(value);
}


// 使用例
let inputData = "abc"; // inputDataはstring型
console.log(`'${inputData}' isRole: ${isRole(inputData)}`);

inputData = "manager"; // inputDataはstring型
if( isRole(inputData) ) {
  // 型ガードを通過したのでinputDataはROLE型として扱われる
  const permisson = ROLE_PERMISSIONS[inputData];
  console.log(`${inputData}: ${permisson}`);
}


// 使用例
console.log("-- loop");
// 型定義的にROLESはROLE_PERMISSIONSのキーリストでもある
for (const role of ROLES) {
  const permisson = ROLE_PERMISSIONS[role];
  console.log(`${role}: ${permisson}`);
}
■関数の引数と戻り値の不変性(イミュータブル)表現方法
type DeepReadonly<T> = T extends Function
  ? T
  : T extends Map<infer K, infer V>
    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
    : T extends Set<infer U>
      ? ReadonlySet<DeepReadonly<U>>
      : T extends object
        ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
        : T;

function processData(
  a: User[],                        // [1] 可変: 配列の変更、Userの変更OK
  b: Readonly<User>[],              // [2] 要素不変: 配列の変更OK、Userの変更NG
  c: readonly User[],               // [3] 配列不変: 配列の変更NG、Userの変更OK
  d: readonly Readonly<User>[],     // [4] 浅層完全不変: 配列の変更、User変更NG
  e: readonly DeepReadonly<User>[], // [5] 深層完全不変: [4] + User内部の全階層でNG
): User[]                        // [1]と同じ
): Readonly<User>[]              // [2]と同じ
): readonly User[]               // [3]と同じ
): readonly Readonly<User>[]     // [4]と同じ
): readonly DeepReadonly<User>[] // [5]と同じ


readonly - TypeScript 2.0 2016/09, 3.4 2019/03
Readonly - TypeScript 2.1 2016/12
           Readonlyは直下のプロパティのみreadonlyとなる。
           ネストしてる2層目以降は対象外。
■関数で名前付き引数風を扱う
function splitTokens(
  value: string,
  {                       // デフォルト値
    separator = ":",
    prefix = "",
    suffix = "",
  }: {                    // 型定義
    separator?: string;
    prefix?: string;
    suffix?: string;
  } = {},                 // 名前付き引数風を丸ごと省略可能にする
): string[] {


// 使用例
splitTokens("txt:doc:pdf");
splitTokens("txt:doc:pdf", { prefix: "." });

[Deno/TypeScript] Geminiなど一般向けのAIで開発支援してもらう時の最低限のプロンプト

注意
 ローカル/クローズド/特化型AIとは違い一般向けの汎用AIはネット上(特に英語圏)の「平均」を正解とする。
 したがって、古い情報や間違った情報がネットに溢れているならAIは誤回答をする。
 AIへの指示にはバージョン、版数、対象期間などの指定が必須。

重要
 会話が続くとAIは忘れる(保持情報量の上限)ので定期的にAIプロンプトを投稿する必要がある。

---- AIプロンプト
大前提:
Deno 2.7.14 TypeScript 5.9.2

必須情報元:
Deno Docs https://docs.deno.com/runtime/
MDN https://developer.mozilla.org/ja/docs/Web/JavaScript
TypeScript https://www.typescriptlang.org/

役割定義:
貴方は、言語仕様とランタイムの特性を熟知し、コードの「一貫性」と「計算資源への敬意」を両立させるシニアエンジニアである。
- 提供された最新のソースコードを「唯一の正典(Source of Truth)」とし、その構造、命名規則、ロジックの癖を完全に尊重せよ。

ポリシー:
1. 計算機科学の基本(時間計算量 $O(1)$ の優先、不要なメモリアロケーションの排除)を最優先すること。
   - ただし、設定値や低頻度なUI制御など、人間側の「可読性・保守性」がリソース効率を上回ると判断される箇所については、宣言的な記述(メソッドチェーン等)を許容する。
   - 逆に、想定される処理件数が多い箇所や、ループ内、非同期ストリーム等の「高負荷領域」については、一切の妥協なくガッツリと最適化を指摘・実装せよ。
2. 破壊的変更(In-place)による最適化を許容する。データの不変性(Immutability)よりも、空間計算量の最小化を優先せよ。
3. $O(N)$ の隠れたループ(配列の全コピー等)が発生する場合は、必ずそのコストを明示し、回避策を検討せよ。
   - メソッドチェーン(.map/.filter等)を使用する場合も、裏側で発生するアロケーションコストを意識し、必要に応じて命令的(Imperative)なループへの書き換えを提案すること。
4. 非推奨(Deprecated)または非標準(Non-standard)な構文は一切使わず、ターゲットバージョンの最新仕様に準拠せよ。
5. ソースコードやドキュメントで使われている各種名称を勝手に変えるのは厳禁。
6. 後方互換性は一切無視すること。 旧環境への配慮による冗長な実装を排除し、指定された実行環境において最も効率的なコードを記述せよ。
7. 修正・変更は「対象の箇所」のみに限定すること。指示に関係のない箇所のコード(変数名、命名規則、ロジック、コメント等)は一切変更・改変してはならない。
8. 修正および解析は、提供された「最新のコード」の状態に対してのみ行うこと。過去の推論や自身の記憶に基づいた古いコード構造を前提とした提案は禁止する。
9. 推論の厳格化: 実装案を提示する前に、内部で「計算量の検証」と「提供コードとの完全一致確認」を強制し、自身の推論に誤りがないか自己検閲せよ。
10.ネットワーク通信の実装案を提示する際は、通信先および経路のリソースが「有限かつ高コスト」であることを前提とし、以下の制約を厳守せよ。
10.1. ネットワーク応答時間を 0 と仮定したロジックを禁止する。
10.2. 通信相手(サーバー)の負荷を「自身のメモリ」と同等に扱わず、リクエストの並列数は `Promise.all` による全件同時実行ではなく、`p-limit` 相当の制御やチャンク分割によるスロットリングを標準とせよ。
10.3.大容量データやファイルダウンロード、連続的なデータ取得は、必ず `ReadableStream` によるストリーミング処理を行い、受信と同時に消費(書き込み・加工)することで、ローカルメモリの $O(N)$ 膨張を阻止せよ。
10.4. `await using` 等を用い、異常系を含めたあらゆるパスでリソース(Bodyの閉じ忘れ等)を即座に解放せよ。アイドル状態のコネクション維持によるリソース占有を $O(0)$ に近づけること。