Hatena::ブログ(Diary)

矢野勉のはてな日記 このページをアンテナに追加 RSSフィード Twitter

2006 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 |

2012-02-09

Clojure勉強

最近、Clojure をやってます。全部リスト、というか遅延リストで、ともかくリストを作らないことにはどうしようもない感がいいですね。

とりあえず作った。

(import [java.net URL])
(defn hatena-serif [] 
    (with-open [stream (.openStream (URL. "http://serif.hatelabo.jp/seriflist/"))] 
        (let [lines (line-seq (java.io.BufferedReader. (java.io.InputStreamReader. stream)))] 
            (loop [line (first lines) remains (rest lines) body false] 
                (let [m (re-matcher #"<img .*src=\"(.*?)\" " line) 
                       bodyMatcher (re-matcher #"<div id=\"body\">" line)] 
                    (if (re-find bodyMatcher) 
                        (recur (first remains) (rest remains) true) 
                        (if (and (re-find m) body) 
                            (nth (re-groups m) 1) 
                            (recur (first remains) (rest remains) body)
                        )
                    )
                )
            )
        )
    )
)

2012-01-06

分散コンピューティングの八つの誤信

いまや、インターネットはあまねく広がり、いままで何度も喧伝されて来てはバズワードとして去っていた「分散コンピューティング」が、現実のものとして普通に取りざたされる世の中です。

でも今よりももっと前、10年以上も前に、「分散コンピューティングの八つの誤信」と呼ばれる、簡潔かつ重要な提言がされていました。一部の人はこの話を今でも覚えていると思いますが、忘れ去られるのも惜しいので、ここに記録しておきます。

この提言が生まれた企業はもう既にありません。その会社のマントラは「ネットワークこそがコンピュータだ」でした。

分散コンピューティングの八つの誤信

  1. ネットワークは安定している
  2. レイテンシはゼロだ
  3. 帯域は無制限だ
  4. ネットワークはセキュア(安全)だ
  5. トポロジーは変化しない
  6. 管理者はひとりだ
  7. 転送コストはゼロだ
  8. ネットワークは均質だ

これらのひとつであっても、前提にしている分散システムは、欠陥を抱えている、という考え方です。

たとえば、リモート・プロシージャ・コール(RPC)は、分散環境でのリモート通信を、関数呼び出しと同一と見なせるように仮想化しています。これはつまり、ネットワークはローカルの関数呼び出しと同じく安定しており、関数と見なして何度呼び出しても良いくらいに帯域が保証されており、呼び出しは常に成功することを前提にしています。当然ながら、それは事実ではなく、RPCは安定しません。

この提言は1999年ごろにサン・マイクロシステムズで生まれ、「ネットワークは不安定で、トポロジは常に変化する」ことを前提にしなければならない、という考えの元、「JINI」というJVMを前提とした分散環境におけるコンピューティング仕様が生まれました。

分散コンピューティングが当たり前になりつつなる今こそ、この「八つの誤信」は頭の片隅に置いておくべき考え方だと思います。

(追記)

JINI仕様は死んだわけではなく、その実装として今でも「Apache River」が存在します。メーリングリストにも、いまでもちゃんとポストがあります。

2011-12-25

iTunes Matchについて

なんか、「日本を除く」17カ国に広がったらしい、Appleの「iTunes Match」ですが…

これ、どういうものかというと、年間2000円くらい支払うと、iTunesに入ってる(iTunes以外で買った)曲がiTunesで売ってるのと同じ曲だと確認できたら、iTunesで買ったのと同じように、いつでもiTunesからダウンロード可能にしますよ、音質もiTunesの256kbpsのになりますよ、というもの(英語解説だと「しかもDRMフリーで」って書いてるけど、これはiTunes Plusの曲だったらってことかもしれない)。ついでに、iTunes Storeの曲とマッチしなかったもの(だけ)がiCloudアップロードされて、iCloudに接続してる所有デバイスと同期される。FAQによると、年間契約を解除したあとでも、一度iTunes化されたものは聞き続けられるらしい。

CDをインポートするたびに、iTunes Storeインポートした曲が自動的にチェックされて、iTunes Storeに同じものがあればiCloud同期の対象となり、なければ曲をiCloudに自動アップロードするそうです。

ユーザの利点としては、自分でリップしたCDの曲を、iCloudによる音楽同期の対象にすることができるってことですね。iCloud同期の対象になった曲は、iCloudバックアップしてるのと同じことなので、元音源がなくても復活できるという効果もある。

で、このサービス、非正規ダウンロードした音楽を合法化するとか権利者が認めるわけがない、という話になってるようですが…

現時点で、非正規ダウンロードされた楽曲は、権利者にとって一円も生んでないわけです。でも、iTunes Match にてiTunes化すると、おそらく年間料金から、一部のお金は権利者に流れる。いままで一円も生まなかったものが、お金になることになる。


「そんなことやったら違法音楽ダウンロードをむしろ促進するだろ」


という話にもなろうかと思います。

んで、私が思うに、非正規ダウンロードした人は違法ダウンロードし続けるだけで、iTunes Matchも使わないんじゃないかと思うんですよね。256kbps以上の、もしかしたらLosslessで音源持ってたりするかもしれないじゃないですか。しかも一円も払いたくないから非正規ダウンロードしてるわけで、年間料金を払うと思えない。

むしろ、レンタルCDからさらに金を取れる、という点の方が大きいんじゃないでしょうか。レンタルが隆盛してる日本において、(ネット購入したものを除いて)iTunesに入れてる音の音源は、ほとんどレンタルCDなんじゃないでしょうか。

レンタルCDが借りられることによって、いくばくかが権利者に入ってるそうですが(だから許容している)、その上、それがiTunes MatchにてiTunes化されると、さらに入ってくるわけです。


非正規ダウンロードした人からももしかしたらいくばくかはとれるかもしれませんが、そっちはどの道期待できない。払いたくないからダウンロードしてるわけで。つまり、その点はiTunes Matchがあろうがなかろうが、一緒です。もし仮にiTunes Match化してくれたら、いままで一円にもならなかったところから、いくらか得られる。

「iTunes Matchが始まったからよし! 怪しいサイトとか共有ソフトでダウンロードしまくるぞー!!」という人はあんまりいないでしょう。やるひとは、iTunes Matchがあろうがなかろうがダウンロードしそう。

「iTunes Matchが始まったしレンタルCDたくさん借りるか」ならあるでしょう。これは、もともとよりプラスの収入が得られるようになるわけで、損することはない。

と、並べてみると、iTunes Match、権利者にとってもお得な気がするんですけど、どうなんでしょうかね。

2011-12-07

AnnotationProcessorを利用して楽してintrefaceを徹底活用したプログラミングをしようぜ

この記事は Java Advent Calendar 2011 の一環で書いてます。詳細はこちらを参照してください。昨日の id:ttmmrr さんの記事はこちら (2012年の公休日とか)JTableのフィルタリング - ttmmrr(@o_tmr)の日記

たまたま、いま作ってるプログラムで、Javaプログラマに役立ちそうな部分があったので、その部分を抜き出して公開することにしました。今回はその紹介をしようと思います。

インタフェースこそが型である」世界

Java: The Good Parts という本がありまして、その3章において、(私の理解としては)Javaにおいて、型とはインタフェースのことあり、クラスは型に実装を提供するものであり、すべてのクラスはインタフェースを備えるべきだ、とかいう意味の論が展開されてます。

インタフェースはクラスから型としての情報をのみを抜き出したものだと理解していた場合、この考え方は完全に逆ですね。「インタフェースこそが型であり、クラスは型じゃない」と考えると、世界が逆転する。インタフェースを実装していないクラスとは何なのか?という話になってしまう。型がないのに実装だけがある、と。

もちろん結局は程度論に落ち着くのかな、という気はしてるんですが、あらゆるシーンで、まずインタフェースを定義し、その実装のひとつとしてクラスを定義する、ということを徹底すると、たしかに、汎用性が高くてよいプログラムになる。

単純なBeans的なものでさえ、インタフェースを先に定義すると、あるプロパティは読み込みオンリーなのか、書き替えできるべきなのかまで、きっちり考えるようになって、プログラミングの中にモデリングがしっかり組み込まれてくる。すばらしい。

で、徹底するのはいいんですが、単純なBeans的なインタフェースを定義して、各プロパティを実装する作業ってものすごく単調なんですよね。

ほかの言語を参照してみる

Javaってめんどくさい言語なんで、それを改善しようとすると、実際にそういう問題をうまく解決してる別の言語を参照して、それをJavaなりに取り込んだライブラリを作る、ってなことをよくやります。(しないですか?)

で、getter/setterとか、Scalaではうまく解決されている。

特に、case classは、属性だけを定義すると、そのアクセッサはもちろん(フィールドとそのアクセッサが統合されてるのは言語的特性であってcase classの特性じゃないですけど)、equals/hashCodeもちゃんと実装される。コンパイラが、そのようなクラスを自動的に提供してくれて便利です。

case class Person(name: String, age: Int)

コンパイラが勝手に用意してくれる...それ採用しよう!

アノテーションプロセッサ

Java 6にはPluggable Annotation Processing APIというのがあります。いまやおなじみのアノテーションですが、ほとんどのケースが、実行時にクラスファイルからアノテーションを読み取って、その設定値を使って実行時に自動処理を実行する、というケースです。

Pluggable Annotation Processing APIを使うと、実行時ではなく、コンパイル時に、ソースコードに埋め込まれたアノテーションによって、新しいソースを生成して、生成したコード自身もコンパイル対象としてまとめて処理してしまうことができます。ソース書き替えとまではいきませんが、コンパイル時に自分の書いたプログラムを介入させることができるのです。夢が広がりますね。

このために作るのが、AnnotationProcessor というクラスの実装です。

Google AppEngine用のライブラリで有名な「Slim3」は積極的にこの機構を使ってるので、Slim3ユーザにはおなじみだと思います。

インタフェースをもとに、クラスをコンパイラに用意させる

というわけで、Pluggable Annotation Processing APIを使うことで、インタフェースアノテーションを書けば、コンパイル時に自動的にインタフェースの実装クラスを生成し、まとめてコンパイルすることができます。

インタフェースを書き替えれば、何度でもコードが生成され直します。また、ソースコードをファイルとして生成せず、コンパイル段階の内部コードとして捨ててしまい、クラスファイルだけを生成する、ということまでできます。夢が広がりますね。

今回書いたプログラムgithubに上げています。こういう用途のライブラリはすでにあるだろうなと思って探したら、フィールドにアノテーションつけると実装クラスが、とかとか、インタフェースアノテーションを付けるってのが見つからなかったので、作りました。

https://github.com/tyano/interface-processor

また、上記ライブラリを使える状態にしたmavenプロジェクトをサンプルとして、githubに起きました。上記を mvn install したあと、使ってください。

https://github.com/tyano/interface-processor-sample

このアノテーションプロセッサを使うと、

import com.shelfmap.interfaceprocessor.annotation.GenerateClass;

@GenerateClass
public interface SimpleDomain {
    String getName();
    void setName(String name);

    int getAge();
    
    boolean isMale();
    void setMale(boolean male);
    
    float getCoefficient();
    void setCoefficient(float value);
}

というインタフェースを定義するだけで、コンパイル時に自動的に実装クラスが生成されます。もちろん、このクラスのサブクラスを作って動作を改変するのは自由です。自動生成したくなければ、単にアノテーションをはずせばよい。

デフォルトでは、「DefaultInterfaceName」といった、インタフェース名の頭に「Default」を付けたクラスが生成されます。次のような感じです。

@javax.annotation.Generated(value = "com.shelfmap.interfaceprocessor.InterfaceProcessor", date = "2011-12-06T23:19:04.300+0900")
public class DefaultSimpleDomain implements jp.javelindev.simplequerytest.SimpleDomain {
    private java.lang.String name;
    private int age;
    private boolean male;
    private float coefficient;

    public DefaultSimpleDomain(java.lang.String name, int age, boolean male, float coefficient) {
        super();
        this.name = name;
        this.age = age;
        this.male = male;
        this.coefficient = coefficient;
    }

    public DefaultSimpleDomain(int age) {
        super();
        this.age = age;
    }

    @Override
    public java.lang.String getName() {
        return this.name;
    }

    @Override
    public void setName(java.lang.String name) {
        this.name = name;
    }

    @Override
    public int getAge() {
        return this.age;
    }

    @Override
    public boolean isMale() {
        return this.male;
    }

    @Override
    public void setMale(boolean male) {
        this.male = male;
    }

    @Override
    public float getCoefficient() {
        return this.coefficient;
    }

    @Override
    public void setCoefficient(float coefficient) {
        this.coefficient = coefficient;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (!(obj instanceof DefaultSimpleDomain)) {
            return false;
        }

        final DefaultSimpleDomain other = (DefaultSimpleDomain) obj;
        if (this.name != other.name && (this.name == null || !this.name.equals(other.name))) {
            return false;
        }
        if (this.age != other.age) {
            return false;
        }
        if (this.male != other.male) {
            return false;
        }
        if (Float.floatToIntBits(this.coefficient) != Float.floatToIntBits(other.coefficient)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
        result = 31 * result + this.age;
        result = 31 * result + (this.male ? 1 : 0);
        result = 31 * result + Float.floatToIntBits(this.coefficient);
        return result;
    }
}

このクラスはすべてのプロパティの実装を提供します。また、すべてのプロパティを引き数として受け取るコンストラクタと、すべてのリードオンリーのプロパティを引き数として受け取るコンストラクタの、二つのコンストラクタを自動生成します(リードオンリーのプロパティがない場合は、引き数なしコンストラクタを生成します)。上記では、ageプロパティはリードオンリーなので、ageだけを受け取るコンストラクタが生成されていますね。

プロパティでない(getter/setterでない)メソッドインタフェースにあった場合は、クラスがabstractとなります。名前も「AbstractInterfaceName」のように、頭に「Abstract」を付けたものに変えます。

また、上記ソースのように、生成されたクラスはequals/hashCodeも、「Effective Java」に書かれた方法できっちり実装しています。equals/hashCodeをちゃんと実装していないクラスってよく見るし自分でも書き忘れることが多いので、この二つを適切に、勝手に生成してくれるのはすごく便利です。

この後、PropertyChangeEventの発行処理までサポートして、PropertyChangeListenerも登録できるようにしたいなーと思ってます。なんか存在自体が無視されてるっぽいJavaBeansのイベントシステムが身近になるぞ!

あと、toString()とかも、デフォルト実装がほしいですね。このへんも後ほど実装しましょう。このライブラリは自分でも使ってるもの(から抜き出したもの)なんで、そのうち実装します。

唐突に思ったけど、getter/setterのめんどくささは一旦置いておくとして、equals/hashCode的なものってどの言語にもあるけど(同一性チェックって絶対必要でしょ)、これらは各言語的にはどう処理してるのかが気になったので、あとで調べよう。このへんあまり話題に上らないように思うけど、勝手にフィールド使って同一性チェックしてくれるような感じになってるんだろうか。

@GenerateClass アノテーションには

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateClass {
    boolean autoGenerate() default true;
    String packageName() default "";
    boolean isPackageNameRelative() default false;
    String className() default "";
    Class<?> superClass() default Void.class;
}

という属性があり、これらを指定することで、生成されるクラスの名前やパッケージ名、スーパークラスを制御できます。

さらに、各プロパティ値を、単純にフィールドに格納するのではなく、コピーしてから格納したい(戻り値として返すときもコピーを返したい)というケースには、@Property アノテーションを使って対応できます。

@Property(retainType = RetainType.NEW, realType = ArrayList.class)
Collection<? extends Number> getCollection();
void setCollection(Collection<? extends Number> collection);

と書くと、プロパティの出し入れは、コンストラクタを使ったコピー処理によって行われます。

@Override
public java.util.Collection<? extends java.lang.Number> getCollection() {
   return new java.util.ArrayList(this.collection);
}

public void setCollection(java.util.Collection<? extends Number> collection) {
   this.collection = new java.util.ArrayList(collection);
}

という実装になります(コンストラクタ引数も同じようにコピーされます)。

RetainType.CLONE を使うと、コンストラクタではなくclone()メソッドを使います。何も指定しない場合は RetainType.HOLD になります。

ソースコード生成ってなんか自動生成コードの管理がめんどくさいしださい

かつてEJB2のころ、EJBの使用がクソすぎて、こんなコードをまるごとXMLで書いたような定義書いてられるかってことで、XDocletとかが流行った時期がありました。で、自動生成したXMLとかソースコードも、開発リソースとして管理してて大変なことになってた印象があります。あるいは、定義を変更したけど生成処理を走らせてなかったので、うまく反映されなくてたいへんなことになったり。

Pluggable Annotation Processing API では、すべての処理がコンパイル段階で行われ、途中で生成されたコードもきっちり一括コンパイルされます(コンパイル途中でソースコードが生成された時のために、javacコンパイラは多段フェーズのコンパイルを実行するそうです)。使う側は、いつもどおりコンパイルするだけでいい。私はEclipseをあまり使わないのでよく知らないのですが、Eclipseは保存するとコンパイルが走るので、ちゃんと設定すれば、保存するだけでソースがちゃんと生成されるようです。

生成されたソースコードに関しても、コンパイラと統合されているおかげで、「コンパイル途中の中間生成物」扱いすることができます。実際、mavenでAnnotationProcessorを使うと、すべての生成コードは(通常はsvn/gitなどで管理しないリソースが吐き出される場所である)targetディレクトリ以下に生成されます。

実際のところ、私は、生成コードは見もせず、interfaceを書けば、インタフェース名の頭にDefaultを付けたクラスを使い始められる、という感覚で使ってます。中間コードを何度も見ても仕方がないですからね。

コンパイル段階の中間生成物ですから、ソース管理しなくていいんです。

逆に言えば、コンパイル中に中間ソースを出力することで、コンパイル処理に部分的に介入できるとも言えます。

コンパイラの処理中に介入できるってなんか面白いしかっこよくないですか?

生成されたクラスをいじりたくなったら?

生成コードはコンパイラの中間生成物なので、いじらないで、サブクラスをつくりましょう。AnnotationProcessorがコードを生成すると、コンパイラは2段階目のコンパイルを行いますので、スーパークラスがないからサブクラスコンパイルエラーになる、ということはありません。

使い方

IDEを使った場合は、IDEごとに設定方法が違うので、ググってしらべてください。すみません、Eclipseを使ってないのでよくわからないんです...

しかし、最近のJava開発だと、mavenを使ってることが多いんじゃないでしょうか。IDEmavenサポートも優秀ですし。

mavenですと、次のようにmaven-compiler-pluginを設定すればOKです。

<dependency>
    <groupId>com.shelfmap</groupId>
    <artifactId>interface-processor</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.2</version>
    <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <compilerArgument>-implicit:class</compilerArgument>
        <annotationProcessors>
            <annotationProcessor>com.shelfmap.interfaceprocessor.InterfaceProcessor</annotationProcessor>
        </annotationProcessors>
        <showWarnings>true</showWarnings>
    </configuration>
</plugin>

これだけで、@GenerateClassアノテーションが有効になります。

アノテーションプロセッサを自分で書くのって難しい?

わかってしまうと面白いんですけど、なかなかわかりにくいですね... ソースコードレベルでのリフレクションAPIとでも言うべき機能があって、その辺りを調べないと書けないです。

キーワードとしては

  • TypeMirror
  • Element
  • Elements
  • Types

あたりのインタフェースやクラスが重要だと思います。このあたり、時間があったら紹介したいなあ...

私も、櫻庭さんの次の連載記事を参考にして書きましたので、この連載のアノテーションの章を全部読んで、その気になれば書ける感じです。

http://itpro.nikkeibp.co.jp/article/COLUMN/20060915/248243/


明日は Seacolor さんです。

2011-11-15

シェルスクリプトからsshでリモートコマンドを実行する際の注意点

シェルスクリプトからsshで複数のリモートホストにコマンドを投げつける処理を書いてて、いろいろ気がついたことを、あとで自分で検索できるようにメモしておく。

ループからsshでコマンドを投げつける

シェルスクリプトのwhileループ内から、複数のホストへ順番にコマンドを投げつける処理をかいていると、最初のホストにしかコマンドが飛ばない(ループしない)という事象が起きてた。

これは、シェルスクリプトのwhileループ自体が、標準入力の読み取りに依存しているのに、ssh標準入力を読み取るので、ssh実行により標準入力が全部吸い取られるのが原因。たとえばこんなループの場合に起きる。

somecommand | while read LINE
do
    ssh user@hostname 'sh yourcommand < /dev/null > /dev/null 2>&1 &'
done

-nオプションを付けることで、ssh標準入力が/dev/nullに変わりバックグラウンドモードとなり、ちゃんとループが回る

somecommand | while read LINE
do
    ssh -n user@hostname 'sh yourcommand < /dev/null > /dev/null 2> /dev/null &'
done

「Are you sure you want to continue connecting (yes/no)?」の抑止

初めて接続するホストの場合、ssh

RSA key fingerprint is 3f:1b:f4:bd:c5:aa:c1:1f:bf:4e:2e:cf:53:fa:d8:59.

Are you sure you want to continue connecting (yes/no)?

とか聞いてきます。yesを入力すると、known_hostsに記録されて、それ以降は表示されなくなる。

ただ、Amazon EC2ec2-run-instanceコマンドで10台とかホストを起動して、ec2-describe-instancesでrunningになってる全ホストにコマンドを投げつける、みたいな処理をかいていると、毎回新しいホストになるので、この質問に毎回答えないといけない。

なので、次のオプションを付ける

ssh -n -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no user@hostname 'sh yourcommand < /dev/null > /dev/null 2> /dev/null &'

StrictHostKeyChecking=noによって、新しいホストへの接続時に、質問なしに、フィンガープリントをknown_hostsに書き込むようになる。ただ、これだとどんどん要らない情報がknown_hostsに書き込まれてうっとうしい。なので「UserKnownHostsFile=/dev/null」でもって、know_hostsを/dev/nullにしてしまえば、know_hosts が汚れない。

おまけ: 社内のsubversionリポジトリからチェックアウトする際の質問抑止

社内にsubversionがあって、httpsになってる場合、「証明書を信頼するか?」みたいなことを聞かれます。これは(p)ermanently を選択すれば二度と聞かれないんですが、これもやはり、起動したばかりのEC2インスタンスだと毎回聞かれます。

私は、無理やり次のように回避しちゃいました。

svn checkout --username user --password pass https://yourhost/svn/trunk/yourproject >logfile 2>&1 <<EOA
p
yes
yes
EOA

ヒアドキュメントで、聞かれるだろう質問の解答を全部流し込んでしまう。