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

constructor signature(@TypeScript)

interfaceで定義できるconstructer signatureなるものがTypeScriptにはあります。

以下のコードでinterface SigAがそれに該当しますが、let mem = Create(ImplA)でlet mem = new ImplA()でも結果は同じですが、SigA interfaceを定義することで、コンストラクターのsignatureを分離できることになります。分離して何が嬉しいのですが、この例では嬉しさはなさそうですが色々なコンストラクタシグネチャーを定義することで定義の分離ができて見通しは良くなるように思います。

// constructor signature
type Stat = {
    [key: string]: string
}

interface A {
    get(key: string): string | null
    put(key: string, value: string):void
}

interface SigA{
    new(): A
}

function Create(constA: SigA): A{
    return new constA()
}

class ImplA implements A{
    stat: Stat = {}
    get(key: string){
        let value = this.stat[key]
        return value
    }
    put(key: string, value: string){
        this.stat[key] = value
    }
}

let mem = Create(ImplA)
mem.put('a', 'alpha')
mem.put('b', 'beta')

console.log(mem.get('a'))   // -> alpha

インスタンスを作成する関数(この場合にはcreate())が必要になるので、この部分はクラスとは別定義になります。SigAを直接的にはimplementsせずにcreate()関数で実はSigAをinplementsしていると考えても良さそうですが。

 

admin

classは名前でなく構造で型づけされる(@TypeScript)

多くの言語ではclassは名前で型づけされますが、TypeScriptのclassは構造(つまり以下の例では同じメソッドを持っていれば互換であるということ、かなり奇妙に感じますが。

// class exchangeable if it has same structure
class Cat {
    walk(){                         // only available in case of public scope
        console.log('cat walks')
    }
    cry(){
        console.log('mew')
    }
}

class Dog{
    walk(){
        console.log('dog walks')
    }
    cry(){
        console.log('bow-wow')
    }
}

function move_cry(animal: Cat){
    animal.walk()
    animal.cry()
}

let cat = new Cat()
let dog = new Dog()

move_cry(cat)
move_cry(dog)

実行結果は、

cat walks
mew
dog walks
bow-wow

move_cryの引数の型にCatを指定しているにも関わらず、Dogのインスタンスを引き渡しても問題ありません。これはCatもDogも同じメソッドを持っているからで、構造で型づけされると言われる所以です。

 

admin

abstract classとinterface

TypeScriptで改めて思うことですが、色々と世の中で議論されているのは承知のうえで、

過去の多くのオブジェクト指向言語と言われるものは、おそらくJavaが代表的ですがabstract classとinterfaceを持ちます。

どちらも例えて言えば建物ならば骨格を作る役目ですが(骨格なしでは粘土の家のように脆い作りになるから)、interfaceが全ての種類の骨格を規定するとするならばabstract classは壁とか柱の大括りのパーツを規定するようなもので、interfaceの方がより汎用性がある骨格定義方法だろうと思います。

ならば抽象化部分は全てinterfaceを使えばabstract classは不要に行き着くのですが、例えばGolangは抽象化はinterfaceだけで生態系として成り立っているわけで(Golangにはオーバライドも継承も無い、但しinterfaceで他のinterfaceは組み込めるけれども、従来型のオブジェクト指向言語の特徴は持たない)すから、おそらくinterfaceだけで不都合を感じることはないだろうと思うのでそうやってみようかと。

 

admin

VScodeでのTS(TypeScriptフォーマット)

Mac osだと、

Shift + Option + Fキー

で整形してくれました。

デフォルトでformatterがインストールされているようです。Golangだと、

% go fmt hoge.go

のようにコマンドを入れても良いですが、実は”Shift + Option + F”で同じように整形されました。ショートカットキーで扱える方が便利ですね。

 

admin

restricted polymorphism(@TypeScript)

なんのことかは、以下のコードがわかりやすいだろう、o’reillyからほぼそのままですが、


// restricted polymorphism
type TreeNode = {
    value: string
}

type LeafNode = TreeNode & {
    isLeaf: true
}

type InnerNode = TreeNode & {
    children: [TreeNode] | [TreeNode, TreeNode]
}

let a: TreeNode = {value: 'a'}
let b: LeafNode = {value: 'b', isLeaf: true}
let c: InnerNode = {value: 'c', children: [b]}

let a1 = mapNode(a, _ => _.toUpperCase())
let b1 = mapNode(b, _ => _.toUpperCase())
let c1 = mapNode(c, _ => _.toUpperCase())

function mapNode<T extends TreeNode>(       // if omit extends, node.value makes error since TypeScript
    node: T,                                // cann't define type of the value
    f: (value: string) => String
    ): T{
        return {
            ...node,
            value: f(node.value)
        }
    }

console.log(a, a1)      // { value: 'a' } { value: 'A' }
console.log(b, b1)      // { value: 'b', isLeaf: true } { value: 'B', isLeaf: true }
console.log(c, c1)      // { value: 'c', children: [ { value: 'b', isLeaf: true } ] } { value: 'C', children: [ { value: 'b', isLeaf: true } ] }

TreeNode以下をtype定義しているので、それを継承したTはnode.valueにアクセス可能、TreeNodeを継承しないでTだけならばnode.valueはTypeScriptが型を決められないのでエラーになります。

制約(この場合にはTreeNode)が複数必要な場合にはそれらを&で連結すれば良い。

 

admin