白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

staticイニシャライザとstaticコンストラクタは同じだと思っていました

こんにちは。

年の瀬ですね。
お仕事は納まりましたか。
あれ・・・なんかこれ去年も書いた気がする。

そもそもイニシャライザとかコンストラクタってなんだっけ

Java

staticイニシャライザはJavaの機能。

class Hoge {
    
    static String fuga;
    
    // staticイニシャライザ
    static {
        System.out.println("static initializer");
    }
    
    // イニシャライザ
    {
        System.out.println("initializer");
    }

    // コンストラクタ
    Hoge() {
        System.out.println("constructor ");
    }
}

Javaにはコンストラクタとは別にイニシャライザという機能があって、
文字通りイニシャライズ(初期化)する時に呼ばれます。

staticイニシャライザはクラスにstaticアクセスしたときに呼ばれるため、
staticフィールドにアクセスしたり、インスタンス生成すると呼ばれます。

一方で、staticでないイニシャライザとコンストラクタはクラスのインスタンスを生成したときに呼ばれます。

// static initializer
String fuga = Hoge.fuga;

// static initializer
// initializer
// constructor 
new Hoge();

C#

で、staticコンストラクタはC#の機能。

class Hoge
{

    internal static string fuga;

    // staticコンストラクタ
    static Hoge()
    {
        Console.WriteLine("static constructor");
    }

    // コンストラクタ
    internal Hoge()
    {
        Console.WriteLine("constructor");
    }
}

C#にはイニシャライザという機能はなくて、
クラスにアクセスした際に処理を実行したい場合はstaticコンストラクタを使います。

// static constructor
var fuga = Hoge.fuga;

// static constructor
// constructor
new Hoge();

ここまでの感じでは同じように使っていいように思えます。

継承してみる

継承のあるパターンを確認してみます。

Java

class Parent {
    
    static String hoge;
    
    static {
        System.out.println("Parent static initializer");
    }
    
    {
        System.out.println("Parent initializer");
    }

    Parent() {
        System.out.println("Parent constructor ");
    }
}

class Child extends Parent {
    
    static String fuga;
    
    static {
        System.out.println("Child static initializer");
    }
    
    {
        System.out.println("Child initializer");
    }

    Child() {
        System.out.println("Child constructor ");
    }
}

子クラスから親クラスのstaticフィールドにアクセスすると、親クラスのstaticイニシャライザだけが呼ばれます。

// Parent static initializer
String hoge = Child.hoge;

子クラスのstaticフィールドにアクセスすると、親クラスと子クラスのstaticイニシャライザが呼ばれます。

// Parent static initializer
// Child static initializer
String fuga = Child.fuga;

親クラスのインスタンスを生成すると、親クラスのstaticイニシャライザ、イニシャライザ、コンストラクタが呼ばれます。

// Parent static initializer
// Parent initializer
// Parent constructor 
new Parent();

子クラスのインスタンスを生成すると、全部呼ばれます。順番は見たとおり。

// Parent static initializer
// Child static initializer
// Parent initializer
// Parent constructor 
// Child initializer
// Child constructor 
new Child();

C#

class Parent
{

    internal static string hoge;

    static Parent()
    {
        Console.WriteLine("Parent static initializer");
    }

    internal Parent()
    {
        Console.WriteLine("Parent constructor ");
    }
}

class Child : Parent
{

    internal static string fuga;

    static Child()
    {
        Console.WriteLine("Child static initializer");
    }

    internal Child()
    {
        Console.WriteLine("Child constructor ");
    }
}

子クラスから親クラスのstaticフィールドにアクセスすると、親クラスのstaticコンストラクタだけが呼ばれます。
ここは一緒。

// Parent static constructor
var hoge = Child.hoge;

子クラスのstaticフィールドにアクセスすると、子クラスのstaticコンストラクタだけが呼ばれます。
あれ、なんか挙動が違うぞ・・・。

// Child static constructor
var fuga = Child.fuga;

親クラスのインスタンスを生成すると、親クラスのstaticコンストラクタ、コンストラクタが呼ばれます。
ここは一緒。

// Parent static constructor
// Parent constructor
new Parent();

子クラスのインスタンスを生成すると、全部呼ばれます。
が、なんか順番が違います。
staticコンストラクタは子が先で、コンストラクタは親が先。ややこしい…。

// Child static constructor
// Parent static constructor
// Parent constructor
// Child constructor
new Child();

深くは掘り下げず、そういうもんだと思ってほかのパターンも見ていきます。

クラスジェネリクスをつけてみる

なんかずいぶん長くなってきてしまったので、いらないとこ除外しますね。

Java

class Parent<T> {
    
    static {
        System.out.println("Parent static initializer");
    }

    Parent() {
        System.out.println("Parent constructor ");
    }
}

class Child1 extends Parent<String> {
    
    static {
        System.out.println("Child1 static initializer");
    }

    Child1() {
        System.out.println("Child1 constructor ");
    }
}

class Child2 extends Parent<Integer> {
    
    static {
        System.out.println("Child2 static initializer");
    }

    Child2() {
        System.out.println("Child2 constructor ");
    }
}

Child1とChild2を連続してインスタンス生成すると、Parentのstaticイニシャライザは1回しか呼ばれません。
これは型削除(イレイジャ)という機能によって、コンパイル時にジェネリクスが消されているからでしょう。

// Parent static initializer
// Child1 static initializer
// Parent constructor 
// Child1 constructor 
new Child1();

// Child2 static initializer
// Parent constructor 
// Child2 constructor 
new Child2();

C#

class Parent<T>
{
    static Parent()
    {
        Console.WriteLine("Parent static constructor");
    }

    internal Parent()
    {
        Console.WriteLine("Parent constructor ");
    }
}

class Child1 : Parent<string>
{
    static Child1()
    {
        Console.WriteLine("Child1 static constructor");
    }

    internal Child1()
    {
        Console.WriteLine("Child1 constructor ");
    }
}

class Child2 : Parent<int>
{
    static Child2()
    {
        Console.WriteLine("Child2 static constructor");
    }

    internal Child2()
    {
        Console.WriteLine("Child2 constructor ");
    }
}

Child1とChild2を連続してインスタンス生成すると、Parentのstaticイニシャライザが2回呼ばれます。
C#では型削除は行われないので(内部実装的にはプリミティブは展開して、それ以外は共有している?)、別のクラスとして扱われるのでしょう。

これは別にstaticイニシャライザ/コンストラクタの機能の違いというわけではなく、
あくまで副次的なものだと思いますが、なかなかにややこしいです。

おまけ 自身のインスタンスをstaticに持つ場合

これはJavaC#も挙動は同じなのですが、シングルトンパターンなんかでよくやる、
自分自身のインスタンスをstaticで保持する場合の呼び出し順が難しいのでついでに見ておきます。

class Parent {
    
    static {
        System.out.println("Parent static initializer");
    }

    Parent() {
        System.out.println("Parent constructor ");
    }
}

class Child extends Parent {
    
    static Child singleton = new Child();
    
    static {
        System.out.println("Child static initializer");
    }

    private Child() {
        System.out.println("Child constructor ");
    }
}

なんでstaticがあとにくるんじゃーい。

// Parent static initializer
// Parent constructor 
// Child constructor 
// Child static initializer
Child child = Child.singleton;

ほかにも違いがあるんですかね

普通にコードを書いている上ではそんなに出会うパターンではないとは思うのですが、
なんだかこの辺りって難しいですよね。

誰にでもわかりやすいコードを書けるようにしたいものです。

fastTextをインストールしてみる

こんばんは。

寒さに負けて早くもムートンブーツを出してしまったのですが、
もっと寒くなったらどんな格好をすれば良いのでしょうか。

さて、今日はFacebookの公開している自然言語処理ライブラリ「fastText」を使ってみたいので、
その環境構築をしてみます。

インストールするぞ

とりあえず調べたらコマンドライン実行のための構築手順と、
pythonライブラリのための構築手順がごっちゃになってヒットしたのでしょっぱなから混乱します。
どどどういうことなの・・・私はpythonは書けないのでコマンド実行が良いのですが・・・。

ググる

とりあえず、わかったことはWindows環境よりもLinux環境の方が良さそうということ。
というわけで、MacOSにDockerコンテナを立てて試してみることにします。
こういう時にDockerはミスったらやり直しができるので便利ですね。

というわけで余談ですが、珍しくMacで記事も書いているのでなんだかわたわたします。

とりあえずDockerコンテナを立てよう

Dockerのインストールは良い・・・ですよね?

Docker For Mac | Dockerから落としてきて、
言われた通りに操作すればあら簡単という感じですぐにインストールできます。
Bash On Windows(名前変わるんでしたっけ?)でも同じようなもんだと思います。試してないけど。

まずはイメージの確認。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

当然何もありません。
じゃあまずは空っぽのCentOSのイメージを探して来て・・・とか思いますが、
Dockerさんはrunする時にイメージがなければちゃんと取って来てくれる賢い子なので気にしないことにします。

$ docker run -it --name fasttext centos
Unable to find image 'centos:latest' locally
latest: Pulling from library/centos
85432449fd0f: Pull complete 
Digest: sha256:3b1a65e9a05f0a77b5e8a698d3359459904c2a354dc3b25ae2e2f5c95f0b3667
Status: Downloaded newer image for centos:latest
[root@596e34b5ca1f /]# 

素晴らしく簡単。-itオプションは起動したコンテナにそのままアタッチする呪文です。
あ、そうでした。Dockerがメインじゃないんでした。

Let's 環境構築

プレーンなCentOSコンテナでコマンド実行用のfastTextを使うのに必要なのは以下。

これらをyumで取って来ます。

# yum install -y git make gcc gcc-c++
(なんかいっぱい出る)

あ、コンテナ内は「#」つけますね。

で、cloneしてmake。
コンテナ内だからルートで良いやという適当さ。

# git clone https://github.com/facebookresearch/fastText.git
(なんかちょっと出る)
# cd fastText
# make
(なんかちょっと出る)

これだけでおしまい。簡単。
一応インストールできているかちょっと確認。

# ./fasttext     
usage: fasttext <command> <args>

The commands supported by fasttext are:

  supervised              train a supervised classifier
  quantize                quantize a model to reduce the memory usage
  test                    evaluate a supervised classifier
  predict                 predict most likely labels
  predict-prob            predict most likely labels with probabilities
  skipgram                train a skipgram model
  cbow                    train a cbow model
  print-word-vectors      print word vectors given a trained model
  print-sentence-vectors  print sentence vectors given a trained model
  print-ngrams            print ngrams given a trained model and word
  nn                      query for nearest neighbors
  analogies               query for analogies

大丈夫そうですね。

最小構成がわかるまでに余計なものを色々インストールしてコンテナ作りなおしたのは秘密。

今回はこれでおしまい。
次回はfastTextを使ってみます。

プロパティとかインデクサはインクリメントできる

こんにちは。

気温の変化についていけない私です。
今日はC#ネタ。

C#にはプロパティという便利な機能があります。
Javaばっかり書いていた私としては単なるアクセサの便利な書き方くらいに思っていたのですが、
ふと違いに気づいたのでメモ。

たとえばこんなint型のプロパティを持つクラスを作って、

class Test
{
    internal int Cnt { get; set; }
}

こんな風にインクリメントするとちゃんと演算してくれます。

var test = new Test();
Console.WriteLine(test.Cnt);
test.Cnt++;
Console.WriteLine(test.Cnt);

0
1

おお賢い。
ということはきっとインデクサもこんな風にすれば、

class Test
{
    private int[] ary = new int[1];
    internal int this[int i]
    {
        get => ary[i];
        set => ary[i] = value;
    }
}

インクリメントできる!

var test = new Test();
Console.WriteLine(test[0]);
test[0]++;
Console.WriteLine(test[0]);

0
1

単なるgetterだと考えるとこんなことはできません。
(intは参照型じゃないので)

これはインクリメント演算子がちゃんとgetterで取って、setterで入れているからなんでしょう。
ということはですよ、こんないたずらをすれば・・・

private int cnt;
internal int Cnt
{
    get => cnt;
    set => cnt = value + 1;
}

インクリメントしただけなのに、

var test = new Test();
Console.WriteLine(test.Cnt);
test.Cnt++;
Console.WriteLine(test.Cnt);

0
2

2増えた!

なるほど。楽しい。
よいこは真似しないようにしましょう。