Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

イトーキF レビュー

中古でイトーキのFというオフィスチェアを購入したのでレビューです。

感想

すごくいいです。座椅子でずっと作業してたのですが、苦労が全てなくなりました。

ヘッドレストがないモデルですが、あまり気になりません。ディスプレイを買ったので下を向いて作業することがなくなったのも、おそらくはヘッドレストが要らない要因の一つです。

肘掛けは絶対にあったほうがいいと思いました。肘掛けあるとすごく楽に作業できます。試しに肘掛けを外に向けて肘が宙にある状態で作業してみましたが、圧倒的にやりづらいです。可動式の肘掛け、これは本当にオススメです。写真ではあまり内側への角度調整ができなそうに見えましたが、十分でした。めっちゃいいです。

座り心地もいいですね。「わー高級!」みたいにはなりませんが、疲れません。若干背もたれとの間に隙間があるのが気になりますが、疲れないので問題ないです。

結構背が高い(180cm弱)ですが、十分ゆったりと使えます。いい椅子です。

HHKB所感

HHKB HYBRID Type-Sを買ったのでレビューします。

所感

  • ノートのキーボードより打ちやすい
  • Bluetooth接続でも遅延をほとんど感じない
  • Fnキーは気にならないが、チルダキーの位置がとんでもないのでつらい
  • Ctrl + Shift + Spaceで入力切り替えというのはやってみると案外手間に感じない
  • 音はType-Sという割にはスコスコなりまくっている。これでType-Sなら無印はどうなんだ…?と思っている
  • ホームポジションがでたらめな我流なのでHHKBのサイズ感だと打ち間違えが多い

ノートのキーボードより打ちやすい

これはまあ当然なのですが(35,000円もするので)、やはり打ちやすいですね。底につかないので指が疲れにくいです。

ぼくは研究室で1年間Realforceを使っていましたが、打鍵感はそんなに変わらない気がします。

Bluetooth接続でも遅延がほぼない

無線接続というとラグで使い物にならない印象がありますが、HHKBはほぼラグがありません。無線と有線で差が全くないです。タブレットやスマホと接続してもほとんどラグがなかったので、ほとんどの環境でラグがないと思われます。タブレットと無線で繋いでブログを書くという芸当ができるのはすごく楽でいいですね。

キー配列が地味に辛い

HHKBといえばFnキーを使った移動ですが、これはすぐ慣れるので全く問題ありません。それよりもチルダキーの位置が右上にあるのが非常に気になります(US配列の場合チルダキーは左上が多い)。チルダキーは結構使うので、真反対の位置に置かれると脳が混乱します。

また、僕はjキーに右手人差し指を置いてタイピングをする癖があります。HHKBはキーが小さいため、この我流ホームポジションだと感覚が狂います。左手はともかく、右手のタイプミスがかなり目立っています。まあ、ホームポジションが我流なのが問題なので、きちんとホームポジションができていればおそらく問題ありません。

日本語切り替えはそこまで手間でない

US配列には半角全角キー、無変換・変換キーがありません。そのため、日本語入力はCtrl + Space(あるいはCtrl + Shift + Space)で切り替える必要があります。僕は普段、無変換キーと変換キーで入力切替できるよう設定しているため、入力を切り替えるときに複数キーを押すのは面倒なんじゃないか?と思っていましたが、慣れるとそこまで手間に感じません。

Type-S…なのか?

Type-SでないHHKBを使ったことがないので比較ができませんが、Type-Sといいつつ結構カチャカチャと音がなっています。別に不満というわけではないですが、「これでType-Sってことは無印はどれだけ音が大きいんだ…」と思いました。もし無印を買っていたら音が気になっていたかもしれません。下手に金をケチるよりType-Sを買ったほうが良いのかもしれません。

まとめ

まとめると、「慣れればかなり快適」ですね。35,000円かける価値はあると思います。

TypeScriptのExcludeはなぜT extends K ? never : Tで実装できるのか?

直感に反しているExclude型についてconditional typeの話をしつつ解説します。

Exclude型とは?

Union型から特定の要素を取り除く型です。ある型から特定のプロパティを取り除きたいときに使えます。

interface Person {
  name: string;
  age: number;
  country: string;
}

type PersonWithoutAge = Pick<Person, Exclude<keyof Person, "age">>;

Exclude型の実装と解説

Exclude型は簡潔に実装できます。

type Exclude<T, K> = T extends K ? never : T;

わけがわからないと思うので1つずつ解説します。

extendsキーワードと三項演算子

extendsは「右辺は左辺のサブクラスであるか」を判定します。特にここではプロパティ名同士の比較になるので、単に等しいかどうかを判定しています。

そして、T extends Kが真であったならneverを、偽であったならばTを返します。

動作をみて分かるとおり、これは三項演算子、もっといえばif文のような役割を果たしています。そのため、名前もconditional typeとなっています。

さて、ここまで読んでいて「おや?」と混乱してきませんか?僕もとても困惑しました。なぜならExclude<keyof Person, "age">は書き下すとkeyof Person extends "age" ? never : keyof Personとなるわけで、意味がわからないからです。ご安心ください。これであっています(この書き下しは間違えています)。このあときちんと説明します。

conditional typeは分配法則が適用される

conditional typeはT extends KのTがUnion型の場合、以下のように分配が起きます。

T extends K ? never : T
-> T = (U1 | U2 | U3)とする
-> (U1 extends K ? never : U1) | (U2 extends K ? never : U2) | (U3 extends K ? never : U3)

これを見ればExclude<keyof Person, "age">でなぜうまく行くかはほとんど明らかですね。

参考

TypeScript 2.8 の Conditional Types について - Qiita

JavaScriptでPriorityQueue

優先度付きキューを実装する必要に駆られたので書きました。

実装

TypeScript

class PriorityQueue<T> {
    private container: T[] = [];
    private size = 0;
    private comp: (a: T, b: T) => boolean;
    constructor(comp = (a: T, b: T) => a < b) {
        this.comp = comp;
    }
    push(val: T) {
        let index = this.size;
        this.container[this.size] = val;
        ++this.size;

        for (let parent = 0|(index-1)/2;
             parent >= 0 && this.comp(this.container[parent], this.container[index]);
             index = parent, parent = 0|(index-1)/2) {
                 const tmp = this.container[index];
                 this.container[index] = this.container[parent];
                 this.container[parent] = tmp;
             }
    }
    pop() {
        this.container[0] = this.container[this.size-1];
        this.container.pop();
        --this.size;

        for (let index = 0, l = 1, r = 2;
             l < this.size && (this.comp(this.container[index], this.container[l]) || this.comp(this.container[index], this.container[r]));
             l = index * 2 + 1, r = index * 2 + 2) {
                 let target = l;

                 if (this.comp(this.container[index], this.container[r])) target = r;
                 if (this.comp(this.container[index], this.container[l]) && this.comp(this.container[index], this.container[r]))
                     if (this.comp(this.container[r], this.container[l])) target = l;
                 else target = r;

                 const tmp = this.container[index];
                 this.container[index] = this.container[target];
                 this.container[target] = tmp;
                 index = target;
             }
    }
    top() { return this.container[0]; }
    empty() { return this.size == 0; }
}

JavaScript

class PriorityQueue {
    constructor(comp = (a, b) => a < b) {
        this.size = 0;
        this.container = [];
        this.comp = comp;
    }
    push(val) {
        let index = this.size;
        this.container[this.size] = val;
        ++this.size;

        for (let parent = 0|(index-1)/2;
            parent >= 0 && this.comp(this.container[parent], this.container[index]);
            index = parent, parent = 0|(index-1)/2) {
            const tmp = this.container[index];
            this.container[index] = this.container[parent];
            this.container[parent] = tmp;
        }
    }
    pop() {
        this.container[0] = this.container[this.size-1];
        this.container.pop();
        --this.size;

        for (let index = 0, l = 1, r = 2;
            l < this.size && (this.comp(this.container[index], this.container[l]) || this.comp(this.container[index], this.container[r]));
            l = index * 2 + 1, r = index * 2 + 2) {
            let target = l;

            if (this.comp(this.container[index], this.container[r])) target = r;
            if (this.comp(this.container[index], this.container[l]) && this.comp(this.container[index], this.container[r]))
            if (this.comp(this.container[r], this.container[l])) target = l;
            else target = r;

            const tmp = this.container[index];
            this.container[index] = this.container[target];
            this.container[target] = tmp;
            index = target;
        }
    }
    top() { return this.container[0]; }
    empty() { return this.size == 0; }
}

使い方

new PriorityQueue()でインスタンスを生成できます。デフォルトは降順で要素が出てきます。

const q = new PriorityQueue<number>();
for (const e of [4, 1, 6, 2, 5, 3, 9, 10, 8, 7]) {
    q.push(e);
}
while (!q.empty()) {
    console.log(q.top());
    q.pop();
}

比較関数を渡すことで優先順を変えることができます。

const q = new PriorityQueue<number>((a:number, b:number) => a>b); // 昇順
for (const e of [4, 1, 6, 2, 5, 3, 9, 10, 8, 7]) {
    q.push(e);
}
while (!q.empty()) {
    console.log(q.top());
    q.pop();
}