M570tをベアリング化しました

Ligicool社製の親指トラックボール、M570tを暫く使っていたのですが時々ボールの動きが物凄く渋くなることがあったためアルミナの支持球をベアリングに置き換えてみました。

kirmav.blogspot.jp

部室に転がってた外形Φ7のベアリングを使い、この方の記事を参考にして改造したのですがベアリングの軸は外側から付けたほうが良さそうです。穴は大きめに開ける必要がありますが、ベアリング軸固定用の溝は結構浅めにしないと組み立てた時にボールが入らなくなります。流石にΦ7のベアリングを入れると結構干渉する部品が多くなり色々な場所を削らないと入らなくなります。

感想

ベアリングを使ったので引っかかりは殆ど無くなるだろうと期待していたのですが、新品M570tより少し小さいぐらいでした。動摩擦も格段に下がってるので結局差がそこまで埋まらなかったのかもしれません。あと動かしてると僕の場合だけかもですが何故かガタガタ言います。操作性にはあまり影響してないのですが、少し気にならないでもないです。

結果としてはかなり満足なのですが必要な工具が結構多いためやろうとすると費用が結構厳しいです。ベアリングの単価はそこまで高く無いんだしベアリングトラックボールを公式で発売してほしいと言う感情が非常に強くなりました。あとベアリングと光学式読み取り器があればトラックボールは作れるようなので光学式読み取り装置さえどうにか出来れば自作トラックボールはマウス以上に簡単にできそうです。M570tの玉はAmazonで売ってますし。

D言語の動的配列について

メモです

int[] darray = [1,2,3];
int[3] sarray = [1,2,3];

pragma(msg,typeof(darray));    //当然int[]
pragma(msg,typeof(darray[]));  //int[]
pragma(msg,typeof(sarray));    //当然int[3]
pragma(msg,typeof(sarray[]));  //int[]

静的配列でも識別子に[]を付けると動的配列に変換することが出来ます。 また、スライス演算sarray[1..2]などでも動的配列が返ります。

ベクトル演算では静的配列でも動的配列でも同じようにarray1[] + array2[]と書きますが、動的配列の場合の[]は単なる構文なのに対し、静的配列の場合は前述の動的配列への変換も含まれているようです(多分)。

void[]

D言語にはvoidという関数やメソッドの返り値の型に指定することが出来る値を持たない型があります。void*という何のポインタかは分からないがとりあえずポインタであることを示す型があります。そして、void[]と言う狐につまさたような型があります。 dlang.orgには次のように記述されています。

原文

There is a special type of array which acts as a wildcard that can hold arrays of any kind, declared as void[]. Void arrays are used for low-level operations where some kind of array data is being handled, but the exact type of the array elements are unimportant. The .length of a void array is the length of the data in bytes, rather than the number of elements in its original type. Array indices in indexing and slicing operations are interpreted as byte indices.

void main()
{
    int[] data1 = [1,2,3];
    long[] data2;

    void[] arr = data1;            // OK, int[] implicit converts to void[].
    assert(data1.length == 3);
    assert(arr.length == 12);      // length is implicitly converted to bytes.

    //data1 = arr;                 // Illegal: void[] does not implicitly
                                   // convert to int[].
    int[] data3 = cast(int[]) arr; // OK, can convert with explicit cast.
    data2 = cast(long[]) arr;      // Runtime error: long.sizeof == 8, which
                                   // does not divide arr.length, which is 12
                                   // bytes.
}

Void arrays can also be static if their length is known at compile-time. The length is specified in bytes:

void main()
{
    byte[2] x;
    int[2] y;

    void[2] a = x; // OK, lengths match
    void[2] b = y; // Error: int[2] is 8 bytes long, doesn't fit in 2 bytes.
}

While it may seem that void arrays are just fancy syntax for ubyte, there is a subtle distinction. The garbage collector generally will not scan ubyte arrays for pointers, ubyte being presumed to contain only pure byte data, not pointers. However, it will scan void arrays for pointers, since such an array may have been implicitly converted from an array of pointers or an array of elements that contain pointers. Allocating an array that contains pointers as ubyte[] may run the risk of the GC collecting live memory if these pointers are the only remaining references to their targets.

意訳

これは任意の配列を取るための特殊な型で、void[]と宣言されます。void[]は配列の要素の値が重要でない場合に低レベルな配列の操作をする際に使われます。.lengthプロパティは元の型の配列での.lengthの値ではなく、その配列のbyteでの大きさを返します。void[]でのインデックスアクセスやスライスはbyteの配列に対するそれらの操作として解釈されます。

void main()
{
    int[] data1 = [1,2,3];
    long[] data2;

    void[] arr = data1;            // OK, int[]はvoid[]に暗黙の返還が出来る.
    assert(data1.length == 3);
    assert(arr.length == 12);      // .lengthは暗黙にbyteのものとして扱われる.

    //data1 = arr;                 // 不正な操作 : void[]はint[]に暗黙に変換できない.
                                   // int[]に変換。
    int[] data3 = cast(int[]) arr; // OK, 明示的なint[]への変換は出来る.
    data2 = cast(long[]) arr;      // Runtime error: longのサイズは8バイトであるため、
                                   //12バイトのvoid[]をうまく分割出来ないため 
                                   //ランタイムエラーになる. 
}

void[]コンパイル時にその長さが判明しているならば、void[12]のように静的配列にすることも出来ます。

void main()
{
    byte[2] x;
    int[2] y;

    void[2] a = x; // OK, 同じ長さ.
    void[2] b = y; // Error: int[2]の大きさは8バイトなので2バイトのvoid[2]には変換できない.
}

void[]ubyte[]はちょっとした構文上の違いのようですが、微妙な違いがあります。ubyte[]は純粋なバイト列のデータとみなされるために、GCのスキャンの対象となりません。しかし、void[]はポインタを含む任意の配列から変換された可能性がある為、GCののスキャンの対象となります。つまり、ubyte[]だとそこに何か生きているデータへのポインタが含まれていた場合に、もしそのポインタ以外の参照が無ければGCに回収されてしまうリスクが出来てしまうということです。

問題点

このvoid[]任意の型TにおけるT[]から暗黙に変換できてしまうと言うのが中々の曲者で、場合によっては予想外の動作を引き起こします。

例えば、再帰を使って各要素をそれぞれ2乗するコードです。

import std.stdio;

void main() {
    [1,2,3].square.writeln;
}

auto square(int[] ary) {
    if (ary.length == 0) return [];
    return [ary[0] ^^ 2] ~ square(ary[1..$]);
}

このコードを実行すると僕の環境では[1, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0]と言う結果が得られます。 この関数squareでは2つのreturn文が存在します。まず、[]はvoid[]なので、片方の型はvoid[]です。そしてreturn [ary[0] ^^ 2] ~ square(ary[1..$]);の型はint[] ~ typeof(square([334/+適当+/])の結果となります。int[] -> void[]への暗黙の変換は出来ますが、void[] -> int[]への暗黙の変換は出来ないのでsquareが返す値の型はvoid[]と推論されてしまいintの配列ではなく結果のバイト配列を返します。x86はリトルエンディアンの為、1,2,3はそれぞれ[1,0,0,0],[4,0,0,0],[9,0,0,0]で表されるバイト列に変換されます。

この場合は[]int[]にcastするか、返り値の型をint[]と明示してやれば[1,4,9]と期待通りの結果が得られます。

Fcitxに手こずったArch再インストール

少し早めの大掃除とPCにArch Linuxクリーンインストールしました。 /rootと/varを大きめに取り、swapパーティションを設定しgnomeを入れたまでは良かったのですがfcitxが変な挙動をするようになっていました。

fcitx-gtk3を使うと、アドオンのFcitx XIM Frontendが有効化されていると左下に変換パネルが表示されてしまい、無効化すると変換が確定するまでテキスト入力エリアに表示されない、変換途中で文字を入力し直すなどすると意図してない文字が入力されるなどの症状ができるようになりました。

適当に何度かアドオンの組み合わせを変更してみましたが一向に改善する気配がなかったのでfcitx-gtk3をアンインストールしfcitx-gtk2に切り替えると問題なく動作するようになりました。

なぜgtk3だとダメなんだ...

D言語のオフラインドキュメントのビルド

人権がない(十分に高速なインタネット環境が無い)場合でもD言語のドキュメントが読めるようにビルドしておく方法です。

git clone https://github.com/dlang/dlang.org
cd dlang.org
# LATEST=の部分は適宜バージョンに合わせて読み替えてください。
make -f posix.mak LATEST=2.072.0 docs

実行するとカレントディレクトリ内のwebディレクトリにビルドされたdlang.orgが出来ています。あとはその中身だけ適当なディレクトリに移し替えてcloneしてきたdlang.orgのローカルリポジトリを削除すれば良いだけです。 マルチコアCPUの場合はmakeする際に-jオプションを付けて並列化するとビルドが早くなり嬉しいかもしれません。

修正

make -f posix.mak LATEST=2.072.0 html

ではphobosのドキュメントがビルドされないようです。

D言語でclass同士のopEqualsをオーバーロードする

D言語のclass同士の等価比較演算子オーバーロードは少々特殊です。

structだと

struct S {
    int x;
    bool opEquals(S s) {
        return x == s.x;
    }
}

と書けますがclassの場合だと

class C {
    int x;
    override bool opEquals(Object o) {
        auto c = cast(C)o;
        if (c is null) return false;
        return x == c.x;
    }
}

のように書きます。 D言語ではclassにopEqualsが実装されてる際にはobject.opEqualsの定義に従って比較がされます。 object.opEqualsは以下のように定義されています(実際のソースコードは多少の違いがありますが)

bool opEquals(Object a, Object b)
{
    if (a is b) return true;
    if (a is null || b is null) return false;
    if (typeid(a) == typeid(b)) return a.opEquals(b);
    return a.opEquals(b) && b.opEquals(a);
}

この定義を見れば分かるようにopEqualsには比較対象がなんであってもObjectが渡されるので引数はObjectでなくてはいけません。また、全てのクラスの基底クラスであるObjectには

class Object{
//省略
    bool opEquals(Object o) {
        return this is o;
    }
//省略
}

とopEqualsが実装されているのでopEqualsにはoverride修飾子を付けて定義しなければいけません。

D言語ではObjectから任意のクラスへのキャストが不正なものであった場合は結果がnullになるのでまずnullになっていない(不正な変換ではない)かをチェックします。 そして比較のコードを書くようになります。

immutable class

immutable class IC {
    int x;
    this (int x) {
         this.x = x;
    }
    override bool opEquals(Object o) {
        auto ic = cast(IC)o;
        if (ic is null) return false;
        return x == ic.x;
    }
}

このように定義した場合

Error: function dmatch.core.parser.AST.opEquals does not override any function, did you mean to override 'object.Object.opEquals'?

というエラーメッセージが出ます。原因はimmutable classとした場合immutableが付いているので実際にはbool opEquals (Object o){}でなくなってしまうためobject.Object.opEqualsをオーバーライドすることが出来ないためです。そのため、この場合はclassの定義自体を

class IC {
    immutable int x;
    this (int x) immutable {
         this.x = x;
    }
    override bool opEquals(Object o) {
        auto ic = cast(IC)o;
        if (ic is null) return false;
        return x == ic.x;
    }
}

と変更してやると動くようになります。 僕はこれでハマったのですが lempiji@思秋期 (@lempiji) | Twitterさんにこの解決法を教えていただき解決することが出来ました。ありがとうございました。