distributive conditional type(@TypeScript)

日本語だと、分配条件型という訳になるようですが、以下のコード例でT extends U ? never : Tで表現される三項演算子的な演算で型を決めるやり方です。

TとUというジェネリック型を実際の型で呼び出すと、Uに含まれる型(boolean)はnever扱いされるようになって、number/string型だけがAの有効型となります。型の集合演算的な機能ですね。

// distributive conditional type
type WithOut<T, U> = T extends U ? never : T;
//type WithOut<T, U> = T | U
type A = WithOut<boolean | number="" | string="", boolean="">; // type of A : number/string

let a: A = "sd";
a = 123;
//a = false   // error

 

admin

user defined type guard(@TypeScript)

条件ブロック内のオブジェクトの型の制限(Type Guard)の一つのやり方としてユーザー定義(user defined)をする方法があります。typeof/instanceof/inによる方法もありますが、これらは全てインラインで行う必要がありますが、関数定義時点で型制限することで読みやすいコードになるでしょう。

<o’reillyのサンプル>

// user defined type guard
function isString(a: unknown): a is string {
  // if a is not defined, parseInput isString cause error
  return typeof a === "string";
}

function parseInput(input: string | number) {
  let formatedInput: string;
  if (isString(input)) {
    formatedInput = input.toUpperCase();
    console.log(formatedInput)
  }
}

parseInput('qwerty')

 

<TypeScript Deep Diveのサンプル>

// TypeScript deep dive sample
interface Foo {
  foo: number;
  common: string;
}

interface Bar {
  bar: number;
  common: string;
}

function isFoo(arg: any): arg is Foo {
  return arg.foo !== undefined;
}

function doStuff(arg: Foo | Bar) {
  if (isFoo(arg)) {
    console.log(arg.foo);
    //console.log(arg.bar)  // VScode detects as an error
    console.log(arg.common);
  } else {
    //console.log(arg.foo)  // error
    console.log(arg.bar);
    console.log(arg.common);
  }
}

doStuff({ foo: 234, common: "abc" });
doStuff({ bar: 567, common: "efg" });

実行結果は、

QWERTY
234
abc
567
efg

 

どちらも xx is yyyで型を限定することで意図した動作を実現できます。

 

admin

companion object pattern(@TypeScript)

サバイバルTypeScriptに書かれているように、クラスを作るまでもないけど、値(value)と型(type)を同名にして扱いを簡単にしたいというケースで使います。これはTypeScriptは値と型は異なる空間に存在しているので、同名で扱えるということに他なりません。

<Currency.ts>

type Unit = "EUR" | "GBP" | "JPY" | "USD";

export type Currency = {
  unit: Unit;
  value: number;
};

export let Currency = {
  from(value: number, unit: Unit): Currency {
    return {
      unit: unit,
      value,
    };
  },
};

 

<呼び出し側>

importは共通名Currencyで、amountDueはtypeでotherAmountDueはvalueですが、同名で扱えます。

// companion object pattern
import { Currency } from "./Currency"; // works like a class

let amountDue: Currency = {   // set object directly as a 'type'
  unit: "JPY",
  value: 12000,
};

let otherAmountDue = Currency.from(85, "EUR"); // use 'from' function as a 'value'

 

admin

Record & mapped type(@TypeScript)

https://isehara-3lv.sakura.ne.jp/blog/2023/07/02/型定義しない気持ち悪さtypescript/

の続編になりますが、recordとmapped typeというのはオブジェクトの型表現に関わるTypeScriptに固有の機能です。どちらもできることはリンク先のコードと同じですが、記述方法がよりスマートだと思います。

type WeekDay = "Mon" | "Tue" | "Wed" | "Thu" | "Fri";
type Day = WeekDay | "Sat" | "Sun";

// record type(to use above types)
let nextDay: Record<WeekDay, Day> = {
  Mon: "Tue",
  Tue: "Wed",
  Wed: "Thu",
  Thu: "Fri",
  Fri: "Sat",
};

console.log(nextDay.Wed);   //Thu

// mapped type
let nextDayM: { [k in WeekDay]: Day } = {
  Mon: "Tue",
  Tue: "Wed",
  Wed: "Thu",
  Thu: "Fri",
  Fri: "Sat",
};

console.log(nextDayM.Mon);  //Tue

この例だとrecordもmappedもできることは変わらないのですが、mapped typeの方がルックアップ型と組み合わせて使うことでrecord typeよりもできることが広いです。

また、強力なのでTypeScriptには組み込み型でmapped typeが存在していますが、Record<keys, value>などは代表的な物です。

 

admin

ようやく買えるようになったラズパイ

と言っても、まだzero wだけ(@switch science)でしたが早速購入。

ヘッダーピン無しなので、手持ちのGPIOコネクタをジグで打ち込んで準備完了。

最初はzero wのあり物のイメージカード突っ込んで動作確認、その後ラズパイimagerでヘッドレスイメージ書き込んで動作確認。

https://isehara-3lv.sakura.ne.jp/blog/2022/11/14/raspberry-pi-imager(最近変わった?)/

ロワーケースだけでも3Dプリンタで準備しようと思ったけど、この時期フィラメントの脱湿から始めないといけないので地べた置き。まだ使い道は決まっていないし、そもそもフラッシュカードが32GBではバックアップに時間かかりすぎるからオーダー中の8GBカード到着を待って運用用のイメージを焼き込む予定。

 

admin

keyof演算子(@TypeScript)

オブジェクトのルックアップと関連しますが、オブジェクトのキーを取得できるのがkeyof演算子です。

以下のコード例ではActiveLog型のactiveLogオブジェクトを作成し、そのキーをGet型で指定してオブジェクト要素["events"] ["timestamp"]にアクセスしています。

// keyof operator
type ActiveLog = {
  lastEvent: Date;
  events: {
    id: string;
    timestamp: Date;
    type: "Read" | "Write";
  };
};

let activeLog: ActiveLog = {
  lastEvent: new Date(),
  events: {
    id: "active",
    timestamp: new Date(),
    type: "Read",
  },
};

type Get = {
  <o extends="" object="" k1="" keyof="" o="">(o: O, k1: K1): O[K1];
  <o extends="" object="" k1="" keyof="" o="" k2="">(
    o: O,
    k1: K1,
    k2: K2
  ): O[K1][K2];
};

let get: Get = (object: any, ...keys: string[]) => {
  let result = object;
  keys.forEach((k) => (result = result[k]));
  return result;
};

console.log(get(activeLog, "events", "timestamp"));

実行結果は、

2023-07-07T04:40:22.301Z

 

admin

VScodeでのフォーマッター(Prettier)

TypeScript用にはgofmtのような特別のフォーマッターというのは用意されていないように思うので、VScode用のフォーマッターをVScodeの拡張機能でインストールして使ってみました。当然TypeScriptに限らずあらゆるソースコードで使えるようになるのかと思いましたが、VScodeの拡張機能の説明を見ると、

フロントエンド用の言語が対象のようです。

Prettierをインストールしただけでは有効にならずに設定が必要ですが、以下のリンクにやり方が細かく記載されています。ソースコードのセーブ時にPrettierが実行されます。

https://ralacode.com/blog/post/vscode-prettier/

 

admin

ラズパイの需給も結構緩んで来たようです

スイッチサイエンスでraspberry pi zero wがオーダーできる状態だったので一枚オーダーしました。元々CPUは数十nmルールなので、競合するユーザーが多そうでコンシュマー用は優先度が下げられていたようですが、2023年の下期には改善と言われていたのでその通りでしょうか。

追加で買えるようになったので、ラズパイ1Bで動かしていたmifareカード/chatアプリをCO2センサーデータロガーで使っているラズパイzero wに移行しました。

sqlite3は入っていなかったので、それだけはインストールして、あとはDocker上のアプリディレクトリを丸ごと転送でそのまま動作しました。1Bとzero wでOSも同じ32bitだから当然と言えば当然ですが。USBはzero wはmicro-Bなのでtype-Aに変換が必要ですが、それは手持ち品を流用。

 

admin

Dcokerが起動しない(Mac OS)

おそらくVScodeとの競合だろうと思いますが、Dockerが起動しない時の対応方法です。

アクティビティモニターで、

Dockerのプロセスを強制終了させて、Dockerをクリックすれば起動しました。イメージは正常にDesktopが起動っできている状態の時ですが、起動できない時にはプロセスがもっと少ない状態です。

 

admin

型定義しない気持ち悪さ(@TypeScript)

をTSとTSからtscで生成されるJSを比較してみます。

以下はTSのコード、

// safety
type WeekDay = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'
type Day = WeekDay | 'Sat' | 'Sun'

function getNextDay(w: Day): Day | undefined{
    switch (w){
        case 'Mon': return 'Tue'
        case 'Thu': return 'Wed'
        case 'Wed': return 'Thu'
        case 'Thu': return 'Fri'
        case 'Fri': return 'Sat'
        case 'Sat': return 'Sun'
        case 'Sun': return "Mon"
    }
    return undefined
}

console.log(getNextDay('Mon'))

tscでコンパイルされて生成されるコード、

function getNextDay(w) {
    switch (w) {
        case 'Mon': return 'Tue';
        case 'Thu': return 'Wed';
        case 'Wed': return 'Thu';
        case 'Thu': return 'Fri';
        case 'Fri': return 'Sat';
        case 'Sat': return 'Sun';
        case 'Sun': return "Mon";
    }
    return undefined;
}
console.log(getNextDay('Mon'));

もちろんどちらも結果は同じですが、素のJSでは裸で外を歩いているように感じます。TSの場合には最初に取り得る型を定義することでその型以外を指定すればコンパイラ(VScode)がチェックしてくれる(例えばgetNextDay(w: Day)のDayをWeekDayにすればcase 'Sat'とcase 'Sun'はエラー表示表示されます)訳だから、数百行程度のプロジェクトでもTypeScriptを使うメリットはあると思います。

 

admin