色々なメモ。

私的メモ。プログラミングとか。主に自分用まとめ。

rustの開発環境構築メモ(on WIndows10)

rustをWindows10環境にインストールしたのでメモ。 (といってもほとんどメモするようなことはない)

手順まとめ

時系列順でやったことメモ

rustコンパイラ本体等のインストール

以下URLからインストーラをダウンロードして実行
https://www.rust-lang.org/ja-JP/install.html

出てきたコンソール画面で1(設定変更せずにインストール)を入力してEnterを押したら、あとは勝手にやってくれる。

インストールの確認

ターミナルを開きなおして以下を入力。

$ rustc --version
rustc 1.22.1 (05e2e1c41 2017-11-22)

ちゃんとバージョン番号出ているので問題なさそう。

チュートリアルページ

以下を参照。
https://rust-lang-ja.github.io/the-rust-programming-language-ja/1.6/book/getting-started.html

Hello, World⇒失敗

上記チュートリアルページに従って、作業用ディレクトリを切って、以下の内容のファイルを "main.rs" という名前で作成。

fn main() {
  println!("Hello, world!");
}

以下のコマンドでコンパイル

$ rustc main.rs

が、以下のようなエラーが出て失敗。

error: linking with `link.exe` failed: exit code: 3221225781
(この下に長いnote)

ググったところ、以下のページを発見
https://github.com/rust-lang/rust/issues/42744
.NET framework がないのでは? と言われている。

rustのインストールページに戻ってよく見ると、WindowsではVisual C++ Build Toolsが必要と書いてあった(見落としていた)(Visual Studio 本体はいらない)
https://github.com/rust-lang/rust/issues/42744

Windowsでは、Rustは追加でVisual Studio 2013以降のC++のビルドツールを要求します。 入手するにはVisual C++ビルドツールを同梱している Microsoft Visual C++ Build Tools 2017 をインストールします。 あるいは、Visual Studio 2015かVisual Studio 2013を インストール して、インストール時に「C++ tools」を選択します。

Visual C++ Build Toolsのインストール

以下のページのずっと下のほうの「Build Tools for Visual Studio 2017」をダウンロードして実行(かなり下のほうにあるので注意)
https://www.visualstudio.com/ja/downloads/

しばらく待って出てきた画面から「Visual C++ Build Tools」を選択してインストール。

インストール後に再起動が必要。

再びHello, World

$ rustc main.rs
$ ./main.exe
Hello, world!

今回はエラーも出ずに完了。

VSCodeをインストールした

vim+サクラエディタの両刀使いだったが、VSCodeを導入してみた。 以下、インストール時に行ったことの個人的な備忘録。 (この文章自体、VSCodeで書いてみている)

インストールしたプラグイン

「ようこそ」画面の案内に従って適当にインストール。 (「再読み込み」をしないと反映されない?)

設定

「ファイル」⇒「基本設定」⇒「設定」から設定。 左側に設定項目の一覧が出ており、右側に実際の設定内容を書いていく模様。 個々の設定項目の説明もついており、最初は設定項目がよく分からないので楽。

テーマの変更

「Tomorrow Night Blue」に変更。 普段白っぽい背景のエディタを使うことが多いが、気分を変えて暗い色にしてみた。

vim拡張の設定

とりあえず以下だけ変更。

    "vim.useSystemClipboard": true,
    "vim.hlsearch": true

困ったこと

Markdownのプレビュー

「Ctrl+K V」だの「Ctrl+Shift+V」だの押しても出なくて困った。 どうも、言語モードをMarkdownにするために、以下のいずれかが必要らしい。

  • “.md” 拡張子で保存する
  • “Ctrl+k m” から “Markdown” と入力してMarkdownモードにする
    • ただしvim拡張を入れている場合、Normalモードだとこのショートカットが効かない? “:"を押してから入れるといいっぽい

“Ctrl+K V” は何故かきかなかった(vim拡張とバッティングしている?)

雑感

  • 一度に複数のフォルダを開けない?
    • 追記: 複数のウィンドウは開けるので、それぞれで別々のフォルダを開いて、Switch Windowで切り替えて使うと良さそう。
  • vimキーバインドを導入する拡張にはvimStyleというのもあるらしい。 ⇒ vimStyle - Visual Studio Marketplace
    • しばらく使ってみて、微妙そうだったらこちらを試してみる。
  • git連携が便利そう?

参考にしたサイト

ダブルディスパッチとオーバーロード、及びVisitorパターン

ダブルディスパッチという概念を初めて知ったのでメモ。 オーバーロードと似た概念に見えて全然違う。

ダブルディスパッチ

あるオブジェクトのメソッドを呼び出すときに、 そのオブジェクトの実行時の型とその引数の実行時の型をもとに動的に異なる処理を行うこと。

オーバーロード

あるオブジェクトのメソッドを呼び出すときに、 引数の変数の型コンパイル時に分かる型)をもとに静的に異なる処理を行うこと。

具体例

この二つの違いは、具体例で見るのが早い。 以下、コードは全てJava

まず以下のコードを考える。

interface Page {
}

class BlogPage implements Page {
}

class InfoPage implements Page {
}

interface PageScorer {
    void evaluate(BlogPage page);
    void evaluate(InfoPage page);
    void evaluate(Page page);

    int getScore();
}

/**
 * BlogPageとInfoPageを異なる点数で点数付けする
 */
class ConcretePageScorer implements PageScorer {
    private static final int BLOG_SCORE = 2;
    private static final int INFO_SCORE = 1;
    private static final int UNKNOWN_SCORE = 0;
    private int score = 0;

    @Override
    public void evaluate(BlogPage page) {
        this.score += BLOG_SCORE;
    }

    @Override
    public
    void evaluate(InfoPage page) {
        this.score += INFO_SCORE;
    }

    @Override
    public
    void evaluate(Page page) {
        // Unknown page type
        this.score += UNKNOWN_SCORE;
    }

    @Override
    public int getScore() {
        return this.score;
    }
}

public class Main {
    public static void main(String[] args) {
        PageScorer scorer = new ConcretePageScorer();
        BlogPage blogPage = new BlogPage();
        InfoPage infoPage = new InfoPage();

        scorer.evaluate(blogPage);
        scorer.evaluate(infoPage);

        System.out.println(scorer.getScore());
    }
}

これを実行すると、「3」が出力される。 特に、以下の行

scorer.evaluate(blogPage);

では、PageScorerの以下のメソッドを呼び出している。

void evaluate(BlogPage page)

このように、Javaは言語機能としてオーバーロードを備えており、引数の変数の型(ここではBlogPage)に合わせて適切なメソッドを呼び出すことができる。

ところが、main関数の中を以下のように変えるだけで、上記コードで「3」の代わりに「0」が出力されてしまう。

PageScorer scorer = new ConcretePageScorer();
List<Page> pageList = new ArrayList<>();
pageList.add(new BlogPage());
pageList.add(new InfoPage());

for (Page page : pageList) {
    scorer.evaluate(page);
}

System.out.println(scorer.getScore());

これは、page変数の型があくまでPage型であるために、以下の行

scorer.evaluate(page);

で、PageScorerの以下のメソッド

void evaluate(Page page)

が呼び出されてしまうためである。

ダブルディスパッチとは、こののような状況で、page変数の実行時の型(BlogPageやInfoPage)に合わせて動的に呼び出すメソッドを変えることを指す。 Javaは言語機能としてはダブルディスパッチに対応していない

ダブルディスパッチをJavaで実現する方法はいくつか存在する。

一つは、instanceofを使って変数の実行時の型に合わせてif文で処理を分岐する方法。

しかし、instanceofを使わずとも、以下のようにすることで、実際にダブルディスパッチを実現できる(Visitorパターン)。

interface Page {
  void acceptScorer(PageScorer scorer);
}

class BlogPage implements Page {
    @Override
    public void acceptScorer(PageScorer scorer) {
        scorer.evaluate(this);
    }
}

class InfoPage implements Page {
    @Override
    public void acceptScorer(PageScorer scorer) {
        scorer.evaluate(this);
    }
}

//PageScorer interface 及び ConcretePageScorer class には上と変更はないので省略.

public class Main {
    public static void main(String[] args) {
        PageScorer scorer = new ConcretePageScorer();

        List<Page> pageList = new ArrayList<>();
        pageList.add(new BlogPage());
        pageList.add(new InfoPage());

        for (Page page : pageList) {
            page.acceptScorer(scorer);
        }

        System.out.println(scorer.getScore());
    }
}

肝は、BlogPageクラス及びInfoPageクラスに存在する以下の行である。

@Override
public void acceptScorer(PageScorer scorer) {
    scorer.evaluate(this);
}

ここで、scorer.evaluate(this)におけるthisは、BlogPage内ではBlogPage型、InfoPage内ではInfoPage型であるから、オーバーロードによりそれぞれ対応したPageScorerのメソッドが正しく呼ばれる。

上記コードの欠点

上記コードにはいくつか欠点もある。

  1. Pageのサブクラスに毎回同じコードを書かなければならない。
  2. Pageの新しいサブクラスを作ったときの変更が煩わしい。

2について補足する。 たとえば、以下のように新たなQAPageを作ったとしよう。

class QAPage implements Page {
    @Override
    public void acceptScorer(PageScorer scorer) {
        scorer.evaluate(this);
    }
}

このときに、QAPageに対応した新たなPageScorerを作りたい。 そこで、以下のようなクラスを作る。

class ConcretePageScorer2 implements PageScorer{
    private static final int BLOG_SCORE = 1;
    private static final int INFO_SCORE = 1;
    private static final int QA_SCORE = 5;
    private static final int UNKNOWN_SCORE = 0;
    private int score = 0;

    public void evaluate(QAPage page) {
        this.score += QA_SCORE;
    }

        /*
         * 残りのコードはConcretePageScorerと同じなので省略
        */
        ...
}
PageScorer scorer = new ConcretePageScorer2();

List<Page> pageList = new ArrayList<>();
pageList.add(new BlogPage());
pageList.add(new InfoPage());
pageList.add(new QAPage());

for (Page page : pageList) {
    page.acceptScorer(scorer);
}

System.out.println(scorer.getScore());

しかし、上記コードを実行すると「2」と表示され、うまくいかない。 PageScorerインターフェイスにQAPage用のメソッドがないために、オーバーロードの解決時にConcretePageScorer2の以下のメソッドが考慮されないためである。

public void evaluate(QAPage page)

この問題を直接解決するためには、PageScorerインターフェイスにQAPage用のメソッドを新たに追加しなければならない。 しかしそうすると、PageScorerインターフェイスをimplementsしているConcretePageScorerクラスにも、対応したメソッドを追加しなければならない(たとえConcretePageScorerクラスをQAPageに対して使用しないとしても!)。 もしPageScorerの実装クラスが大量にあると、これは大変な手間である。

これに対処するには、全体の構成を以下のように変えればよい。

interface Page<S extends PageScorer> {
  void acceptScorer(S scorer);
}

class BlogPage implements Page<PageScorer> {
    @Override
    public void acceptScorer(PageScorer scorer) {
        scorer.evaluate(this);
    }
}

class InfoPage implements Page<PageScorer> {
    @Override
    public void acceptScorer(PageScorer scorer) {
        scorer.evaluate(this);
    }
}

class QAPage implements Page<NewPageScorer> {
    @Override
    public void acceptScorer(NewPageScorer scorer) {
        scorer.evaluate(this);
    }
}

interface PageScorer {
    void evaluate(Page<?> page);
    void evaluate(BlogPage page);
    void evaluate(InfoPage page);

    int getScore();
}

interface NewPageScorer extends PageScorer {
    void evaluate(QAPage page);
}

/**
 * BlogPageとInfoPageを異なる点数で点数付けする.
   それ以外のPageはもしあっても全て0点.
 */
class ConcretePageScorer implements PageScorer {
    private static final int BLOG_SCORE = 2;
    private static final int INFO_SCORE = 1;
    private static final int UNKNOWN_SCORE = 0;
    private int score = 0;

    @Override
    public void evaluate(BlogPage page) {
        this.score += BLOG_SCORE;
    }

    @Override
    public
    void evaluate(InfoPage page) {
        this.score += INFO_SCORE;
    }
    
    @Override
    public void evaluate(Page<?> page) {
        this.score += UNKNOWN_SCORE;
    }

    @Override
    public int getScore() {
        return this.score;
    }
}

/**
 * BlogPageとInfoPageとQAPageを異なる点数で点数付けする.
   それ以外のPageはもしあっても全て0点.
 */
class ConcretePageScorer2 implements NewPageScorer {
    private static final int BLOG_SCORE = 1;
    private static final int INFO_SCORE = 1;
    private static final int QA_SCORE = 5;
    private static final int UNKNOWN_SCORE = 0;
    private int score = 0;

    @Override
    public void evaluate(BlogPage page) {
        this.score += BLOG_SCORE;
    }

    @Override
    public
    void evaluate(InfoPage page) {
        this.score += INFO_SCORE;
    }

    @Override
    public void evaluate(QAPage page) {
        this.score += QA_SCORE;
    }
    
    @Override
    public void evaluate(Page<?> page) {
        this.score += UNKNOWN_SCORE;
    }

    @Override
    public int getScore() {
        return this.score;
    }
}



public class Main {
    public static void main(String[] args) {
        NewPageScorer scorer = new ConcretePageScorer2();

        List<Page<? super NewPageScorer>> pageList = new ArrayList<>();
        pageList.add(new BlogPage());
        pageList.add(new InfoPage());
        pageList.add(new QAPage());

        for (Page<? super NewPageScorer> page : pageList) {
            page.acceptScorer(scorer);
        }

        System.out.println(scorer.getScore());
    }
}

これにより、Pageのサブクラスの追加が容易になる。 すなわち、Pageのサブクラスを新たにいくつか足したら、 それらに合わせてPageScorerを継承したinterfaceを新たに作れば良い。 過去に作ったPageScorerは一切手を加えずにそのまま使える。

ただしこれだとPageの種類が増えるたびに新しくPageScorerを継承したインターフェイスを作らなければならないので、Pageの追加が頻繁な場合には汚くなってしまう。 あくまで、Pageのサブクラスの追加が滅多に起こりえない(せいぜい1,2回)の場合にのみ使える。

参考: ジェネリクスによるVisitorパターン拡張の考察 - プログラマーの脳みそ

Mapのgetメソッドの引数はObject型

諸事情により最近Javaを始めたので、初心者なりに気になったことをメモしていく。

Javaにおいて、Map<K, V>からkeyを指定して値を取り出すときにはMap<K,V>.get()メソッドを使う。 このget()メソッドだが、引数の型は(K型ではなく)Object型とされている。

Map (Java Platform SE 8 )

V get(Object key)

なので、間違ってKとは異なる型の変数をgetにつっこんでも、コンパイルエラーにはならない。

実際、以下のようにあるクラスAをKeyとしたMapを使っていて

Map<A, Integer> someMap = new HashMap<>();
someMap.put(this.getA(), someInteger);
...

A a = this.getA();
int data = someMap.get(a);

あとからMapのkeyをAのあるプロパティに変更した。 このときに、以下のように一部コードを修正し忘れてしまった。

Map<String, Integer> someMap = new HashMap<>();
someMap.put(this.getA().someProperty(), someInteger);
...

A a = this.getA();
//↓修正し忘れ!!
int data = someMap.get(a);
//正しくは
//int data = someMap.get(a.someProperty());

これでコンパイルエラーになってくれればいいのだが、 なってくれないので、 結局バグを見落としてしまった。


以下の記述はおかしなことを書いてあったので一時的に消しました。

ダッシュボードを全読みするためのTumblrクライアント「Tambooru」を作りました

Tumblrダッシュボードを手軽に全読みするためのクライアントを作った。

Tambooru
https://tambooru.herokuapp.com/

  • マルチカラム。(列数は変更可能)
  • 前回開いたところから自動で開く
  • 新しい方にも古い方にも件数の制限なく読み込み可能
  • スクレイピングは使っておらず、OAuthのAPI使用。
  • Chrome(Windows, iPad, Android), Firefox, IE11 で動作確認済み。

既存クライアントは、API使ってるのは基本的に最新250件(ぐらい。少し違うかも)までしかとれないし、スクレイピング使ってるやつは件数の制限ないけど古い方にしか遡れない。なので、ダッシュボードを全読みしたい場合には少し面倒だった。

そんなわけでこのクライアントでは、少々力技で、任意の箇所から古い方・新しい方に際限なく読めるようにした。

元々自分用に作ったものだけど、ダッシュボード全読みするのに不便を感じている人がいたら使ってください。

デザインがダサいのはどうしようもない。

#何か良いクライアント名募集中。

f:id:mor-eve:20140525000123j:plain

更新履歴
2014.04.27
Backboneを使って一から書き直し
ナビゲーションバーをページ下部に移動&スワイプで引っ込められるように
Androidでフォトセットのポップアップを表示したときのバグを修正
2014.04.28
画像のポップアップが表示されないバグがあったので修正
2014.05.24
表示列数を変更できるようにした(設定画面から変更可能)
投稿日時や投稿者名をデフォルトで非表示に(同上)
2014.05.25
画像のみの表示を可能に
画像のキャプションを隠せるように変更
全体幅を変更できるようにした
2014.06.21
Firefoxでリブログ後にツールチップが残ってしまう問題を修正
Firefoxで中クリックによるスクロールができなかったのを修正
スペースキーによるスクロールを追加
2014.06.24
リンク上で中クリックしたときにはスクロール開始しないように修正
chatポストが正しく表示できていなかったのを修正
video/audioの表示サイズがおかしかったのを修正
2014.06.28
名前を「Tumblr Marker(仮)」から「Tambooru」に変更
合わせてサイトのURLを変更
ついでにこの記事のタイトルも変更
2015.02.07
リブログできなくなっていた問題を修正. 長らく放置していてすいませんでした
2015.02.08
一時的に画像以外の投稿が正常に表示できなくなっていたのを修正しました

for in 中でのプロパティの追加・削除

メモ。

Javascript の for...in 文中での要素の追加・削除について。

for...in - JavaScript | MDN

A for...in loop iterates over the properties of an object in an arbitrary order (see the delete operator for more on why one cannot depend on the seeming orderliness of iteration, at least in a cross-browser setting). If a property is modified in one iteration and then visited at a later time, its value in the loop is its value at that later time. A property that is deleted before it has been visited will not be visited later. Properties added to the object over which iteration is occurring may either be visited or omitted from iteration. In general it is best not to add, modify or remove properties from the object during iteration, other than the property currently being visited. There is no guarantee whether or not an added property will be visited, whether a modified property (other than the current one) will be visited before or after it is modified, or whether a deleted property will be visited before it is deleted.

簡単にまとめれば、

  • まだ訪れていないプロパティを削除すると、そのプロパティは残りのループでも訪れられることはない
  • プロパティを新たに足した場合は、それが訪れられるかは分からない
  • 現在のプロパティ以外を操作(削除・追加・変更)するのは避けたほうが無難

for ... in 文中で現在のプロパティを delete するのは完全に valid っぽい。

for (key in obj) {
  if (toBeDeleted(obj[key])) {
    delete obj[key];
  }
}

Twitter Bootstrap の Carousel

Twitter Bootstrap の Carousel コンポーネント(http://getbootstrap.com/javascript/#carousel)使おうとしてちょっと嵌ったのでメモ。

上記リンク先の例にあるように、data-slide や data-slide-to 属性を付けることで carousel の表示をコントロールできるが、これは内部的には document に対して jQuerydelegateイベントハンドラを付けることで動いているので、 document に至る途中で e.stopPropagation() などしてしまうと正しく動作しない。