白猫のメモ帳

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

JavaScriptでメソッドをイベントバインドするとthisは何を指すの?

こんにちは。

ワールドカップが始まりましたね。
3時からとかだといっそ諦めがつくのですが、
0時くらいから始まる試合とかをつい見てしまって翌日ねむねむになる気がしてなりません。

さて、この前JavaScriptを書いていてふと思ったこと。
表題の通りです。

先に結論を書いてしまえば、イベントが発生したDOMになります。

JavaScriptのthisの指すものの種類はいろいろとあるのですが、
今回の目的はそれを解説することではないので、話題にする2つだけまず確認しましょう。

メソッドが指すthis

JavaScriptに限った話ではないですが、
わりとあいまいに使われている「関数」と「メソッド」は実際の挙動としては明確に区別されます。
Javaとかのオブジェクト指向言語だと関数はstaticメソッドとか呼ばれますね)

簡単にいえばメソッドとはオブジェクトに属する「メンバ関数」のことです。
なので、基本的にはオブジェクトに対する操作を定義した手続きなのですが、
オブジェクトのメンバにまったく関係しない、参照透過性のある関数を定義することができないわけではありません。

おっと、ちょっと話がそれましたね。
つまり、こんな感じのfunctionの定義はメソッドです。

var obj = {};
obj.method = function() {
    alert("hoge");
}

もしくはこんなのとか。

var obj = {
    method: function() {
        alert("hoge");
    }
};

で、このメソッドの中でthisが何を指すかというと、「自身が属しているオブジェクト」です。

var obj = {
    value: "hoge",
    method: function() {
        alert(this.value);
    }
};
obj.method();  // hoge

こんな感じですね。ほかの言語でも大体同じなんじゃないでしょうか。

イベントバインドした関数が指すthis

次にイベントバインドについてみていきましょう。

クライアントサイドのJavaScriptを考えた場合、画面初期表示時に様々な処理を行うほかに、
ユーザが「ある要素をクリックした」とか「マウスカーソルを動かした」とか「キーボードで何か入力した」
といった様々なイベントが発生します。

これらのイベントが発生した際に、なにかしらの処理をさせたい場合、
イベントに処理を「バインド」してあげる必要があります。

document.getElementById("hoge").addEventListener("click", function() {
    alert("hogeをクリックしたよ!");
});

jQuery的で書くとこんな感じ。

$("#hoge").on("click", function() {
    alert("hogeをクリックしたよ!");
});

で、この関数の中でthisが何を指すかというと、「イベントが発生したDOM」です。

<input type="text" id="hoge" value="fuga" />

document.getElementById("hoge").addEventListener("click", function() {
    alert(this.value);
});
// クリックするとfugaがアラートされる(inputクリックしたらアラートとかどういうこと)

メソッドをイベントバインドした場合のthis

やっと本題。

これら2つって実は共存することができます。
つまりどういうことかというと、

<input type="text" id="hoge" value="fuga" />

var obj = {
    value: "hoge",
    method: function() {
        alert(this.value);
    }
};
document.getElementById("hoge").addEventListener("click", obj.method);
// クリックするとfugaがアラートされる

この場合、「this.value」が指すのは

・「obj」のメンバ変数である「value」の値である「hoge
・イベントが発生したinputが持つ値「fuga」

どっちなのでしょうか。

正解は、イベントが発生したinputが持つ値「fuga」です。

じゃあこんな風にinputが持たない属性の「val」とかにしてみると使えるのかというと、
あくまでinputから「val」属性を探しに行って、結果は「undefined」になります。

var obj = {
    val: "hoge",
    method: function() {
        alert(this.val);
    }
};
document.getElementById("hoge").addEventListener("click", obj.method);
// クリックするとundefinedがアラートされる

オブジェクトのメンバを参照することはできないので残念ですね。
で終わってもいいのですが、せっかくなので回避方法を検討します。

var obj = {
    val: "hoge",
    method: function() {
        var self = this;
        return function() {
            alert(self.val);
        };
    }
};
document.getElementById("hoge").addEventListener("click", obj.method());
// クリックするとhogeがアラートされる

こんな感じでオブジェクトのthisを囲い込んだクロージャをバインドしたりするとなんとかなったりしますね。

使いどころがそんなにあるかは謎ですが、覚えておいて損はないかな。

fastTextでニュースをカテゴリごとに分類する

こんばんは。

暑かったり暑くなかったり、晴れだったり雨だったり。
日によって全然気候が違いますね。関東もそろそろ梅雨入りでしょうか。

さて、fastTextによる単語のベクトル表現を試してからまたずいぶんと間が空いてしまいましたが、
今回は分類をやってみようかと思います。

分類するものがないんだよなーとか思っていたんですが、
そういえばだいぶ前にベイジアンフィルタでニュースの分類したときのデータが
どこかに残っていたような気がするな、と思って探してみたら発見できたのでこれを使います。

shironeko.hateblo.jp

前処理

fastTextに分類してもらうためには書式を変換してあげる必要があります。

学習データが

__label__【ラベル】, 【半角スペースで分かち書きした文】

という書式。

__label__IT・科学, アップル iPhone 不正 ユーザー 指紋 収集 特許 出願

こんな感じ。
前の記事見てもらうとわかるかもしれませんが、名詞しか抜き出していないのでそのまま使います。
ちなみに「__label__」の部分は引数の設定で変更することもできます。

で、分類データが

【半角スペースで分かち書きした文】

という書式。こっちはただの分かち書きだけ。

国内 映画 ランキング 君の名は。 会心 V スタート 後妻業 女 4 位 タートルズ 10 位

こんな感じ。
2年近く前のニュースなので、なんだか懐かしい。

学習

前処理が終わったら学習モデルを作ります。

# /fastText/fasttext supervised -input learn.txt -output model -epoch 1000

エポック数だけ1000にしてみましたが、それ以外はデフォルトです。

Read 0M words
Number of words:  4051
Number of labels: 5
Progress: 100.0% words/sec/thread:  350035 lr:  0.000000 loss:  0.263343 ETA:   0h 0m

こんな感じの表示が出て、2秒くらいで終わりました。はやーい。
まぁ、コーパス数800なのでこんなもんですかね。

分類

あっという間に学習が終わったので、分類してみます。

/fastText/fasttext predict-prob model.bin predict.txt 3

ほんとに一瞬で終わります。

__label__エンタメ, 0.999987
__label__IT・科学, 0.99884
__label__エンタメ, 0.999866
__label__スポーツ, 0.999957
__label__エンタメ, 0.999686
__label__経済, 0.999839
__label__エンタメ, 0.99961
__label__エンタメ, 0.999815
__label__スポーツ, 1
__label__経済, 0.999296

あれ、なんかラベルがちょっとイメージと違うけどまぁ分類はできているんでしょう。たぶん。

結果

○__label__スポーツ, 0.999846 大江香織、藤本麻子が“破格”の韓国ツアー出場)
○__label__IT・科学, 0.999927 「ねこ休み美術館」
○__label__スポーツ, 0.999587 【MLB】3Aでストライク率70% ロッテ戦力外の中後、11戦無失点で防御率0.00継続
○__label__スポーツ, 0.999922 元時天空 髪は失っても親方で「成長したい」
○__label__スポーツ, 0.999848 熾烈な優勝争い続くパ ソフトバンク&日本ハム、カギ握る残りの対戦は?
○__label__エンタメ, 0.999742 【ディズニー】ジェラトーニが街中に涼しげアート!期間限定夏デコレーション
○__label__ライフ, 0.9997 医師が考える。新しいタバコ「iQOS(アイコス)」は健康的なのか?
○__label__エンタメ, 0.999801 「ブラクロ」がジャンプのアニフェスでアニメに!参加受付もスタート
○__label__エンタメ, 0.999793 俳優イ・サンヨプ、コン・ヒョンジュと破局後の近況報告
○__label__スポーツ, 0.999718 フットゴルフ日本代表、第1回アジアカップで上位独占 初代アジア王者に

おお、いきなり100%だ。

×__label__エンタメ, 0.999997 伝説の映画「バトルシップ」 まさかの極上爆音上映が決定!
○__label__経済, 0.706788 エコ運転教習のファインモータースクール、エコドライブ全国大会で3年連続表彰台
×__label__エンタメ, 0.94055 アトピー性皮膚炎ってどんな病気? 乳児湿疹とアトピー性皮膚炎の違いとは?
○__label__スポーツ, 0.997004 バルサ、29日中にもP・アルカセル獲得を発表へ すでにメディカルチェックをパス
×__label__IT・科学, 0.742081 拡張現実開発プラットフォームの新バージョンをリリース
×__label__エンタメ, 0.815593 【鈴鹿8耐】あの感動をもう一度、公式ブルーレイ&DVD 917日発売
○__label__経済, 0.889665 「電力事業者」の新設法人数が初の減少、「太陽光」が4割減
○__label__エンタメ, 0.981648 まゆゆが舞妓さんに! メロメロの豊川悦司「ファンクラブに入りたい」
○__label__スポーツ, 0.939998 広島松山8号2ラン「自分のスイングできました」
○__label__スポーツ, 0.764349 3基のパワーユニットを確保したハミルトン、今後はアップグレードの恩恵得られず

とおもったら6割。安定しませんね。

○__label__IT・科学, 0.922675 東大、「水」ベースのリチウムイオン伝導性液体を発見
○__label__経済, 0.715266 セダンのトランクに積める折りたたみ自転車、あさひが発売
×__label__スポーツ, 0.882106 個人間カーシェアのエニカ、1周年記念イベント 917日
○__label__スポーツ, 0.950249 田口 調整に自信「世界戦では割といい方」
×__label__エンタメ, 0.634755 怖いと思う災害1位は地震、「住んでいる地域・家は危険だと思う」32.7%、保険クリニック調べ
○__label__ライフ, 0.999923 【高校受験2017】大阪府公立高、体験入学や説明会一覧…茨木11/9・北野10/8
×__label__経済, 1.00001 都市開発:5 とび職 正確な鉄骨組み、段取り勝負
○__label__エンタメ, 0.804691 俳優イ・サンヨプ、コン・ヒョンジュと破局後の近況報告
○__label__スポーツ, 0.999977 インテルデビュー戦を白星で飾れなかった伊代表MF「勝つためにここへ来たから残念」)
○__label__エンタメ, 0.42318 エースコック、主力商品のスーパーカップ1・5倍シリーズをリニューアル

7割。やっぱり学習データが足りていなさそうですね。

まとめ

fastとつくだけあってとても処理が速いし、使い方もとてもわかりやすいです。
しっかりと分類するためにはデータの量がもっと必要かとは思いますが、
その実力の片鱗くらいは見ることができたかなという感じです。

そのうちにもっと分類に適したデータを集めることができたら、また試してみたいですね。
それでは。

総称型メソッドの型指定

こんにちは。

新年度が始まって二週間がたちました。
新しい環境で頑張っている方はとてもお疲れのことでしょう。
新しくなくても眠たいです。春眠春眠・・・。

この前コードを書いていてふと気づいたこと。
JavaC#で総称型メソッドの挙動が違う。

総称型メソッド

まずそもそも総称型メソッドってなんだって話です。
ジェネリクスメソッドとかジェネリックメソッドとか呼んだりもします。

public static String firstOrDefault(List<String> list, String defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

例えばこんなメソッドを作ったとして(StreamAPI使えとか無粋なことは言わないこと)、
Listで同じことをしたい場合、メソッドのオーバーロードをしなくてはいけません。

public static Integer firstOrDefault(List<Integer> list, Integer defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

ひい。めんどくさい。
ということでこんな風にします。

public static <T> T firstOrDefault(List<T> list, T defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

第一引数に「List<String>」、第二引数に「String」を渡せばちゃんと動きます。
第一引数に「List<Integer>」、第二引数に「Integer」を渡してもちゃんと動きます。
第一引数に「List<String>」、第二引数に「Integer」を渡したらコンパイルエラーになります。
えらい。

引数の前についてる<T>がポイント。
ここでなんかよくわからんけど、なんでも良い型「T」っていうのが定義されます。

public static <T extends Number> T firstOrDefault(List<T> list, T defaultValue) {
    return list != null && list.size() > 0 ? list.get(0) : defaultValue;
}

こんな風にすると、Numberを継承した何か。
になるので、IntegerはOKだけどStringはNGになります。

C#的にはこんな感じ。

public static T FirstOrDefault<T>(IList<T> list, T defaultValue) where T : struct
{
    return list != null && list.Count > 0 ? list[0] : defaultValue;
}

で、このTってコンパイラ的にはどうやって決めてるのっていうのが今日のお話。

明示的に指定

class Util {
    public static <T> List<T> newArrayList() {
        return new ArrayList<>();
    }
}

例えばこんな定義をしたとして、

Util.<String>newArrayList();

こんな風にメソッド呼び出し時に明示的にTの型を指定してあげることができます。

引数の型で指定

class Util {
    public static <T> List<T> newArrayList(Class<T> c) {
        return new ArrayList<>();
    }
}

次にこんな風に定義を変えてみると、

Util.newArrayList(String.class);

ジェネリクスの指定なしに呼び出すことができました。
これは引数の型情報からTの型がわかるからです。

戻り値の型で指定

class Util {
    public static <T> List<T> newArrayList() {
        return new ArrayList<>();
    }
}

メソッド定義を元に戻してみて、

List<String> list = Util.newArrayList();

戻り値側で型を決めてしまえば、明示的に指定しなくてもOKになりました。
やったね。

が、これをC#でやろうとするとエラーになります。

class Util
{
    public static IList<T> newList<T>()
    {
        return new List<T>();
    }
}

こんな定義をして、

IList<string> list = Util.newList<string>();  // OK
IList<string> list = Util.newList();  // NG

試してみると、戻り値側で型が判定できても明示的に指定しないとコンパイルエラーになってしまいます。

いいとこどりしたい

とまぁ、JavaからC#に移ってきた私としてはちょっと不満ではあるのですが、
C#ジェネリックにはJavaにはない素晴らしい機能があります。

class Util
{
    public static T newInstance<T>() where T : new()
    {
        return new T();
    }
}

そう、newができるのです。
これは型削除されてしまうJavaにはできない芸当です。
(可変長引数とか使って無理やり頑張るとできるとかできないとか・・・)

いいとこどりしたいなー。
できるようにならないかなー。なんて。