あざらしとペンギンの問題

主に漫画、数値計算、幾何計算、TCS、一鰭旅、水族館、鰭脚類のことを書きます。

やはり\アッカリーン/しようかな

やはり twitter4j でやるには厳しいものがあることのは感じていたのです。

特に、interface StreamListener がなぜか Scala から見えない(なぜかプライベート扱いになっていて、コンパイル時にアクセス権がないと怒られる。interface のくせに。Groovy からは普通に見えるしアクセスもできる)ので、解決のために少しの Java コードを書かなければいけなかったのは、私としては美しくないと思いました。(sbt では Java ファイルも一緒にコンパイルしてくれるので実用上の問題はありませんでしたが。)

bot のタスクマネージャに akka を使ったのは、特定の日時にイベントを発生させるものとして他に使えるのが java.util.cuncurrent.ScheduledThreadPoolExecutor とかいうやたら長い名前のクラスしかなかったことがあります。これを用いた場合、ストリームを開いているときにイベントが衝突する可能性がありました。そんなこんなで akka を採用することを決定しました。

twitter4j が比較的元の Twitter API に忠実であることは評価できるのですが、Java 8 で非推奨となった API が使われていたり、media/upload (chunked) をサポートしていなかったりと、既に古くなった印象は否めません。なにしろ最初に作られたのが10年も前、Twitter が日本でそれほど普及していなかった頃ですからね。また、ところどころに設計の一貫性がないことも気になりました。その上で開発版の更新が半年前から止まっていることを見る限りにおいて、これ以上を望むのは厳しいと思いました。そもそも mvn のテストに落ちるのでスキップ必須なのはさすがにどうかと。

その辺りを考慮すると、Scala でそれらの問題を解決するためには、やはり新しいライブラリを書く必要があるという結論に至らざるを得ないようです。もちろん検索はしてみましたが、わざわざ Scala でそんなことをやろうとしている人はいないようでした。

開発にはもちろん akka を使うことになるでしょう。akka にはアクターモデルの実装のみならず、HTTP タスク処理用のライブラリも含まれています。ドキュメントを読んだ限り、SNS や Web クライアント/サーバでの使用が意図されていると思われました。

というわけで、書く書く詐欺に終わらなければ、akka を用いた純粋な Scala での Twitter ライブラリを作ることにしました。

名前は、akka にちなんで\アッカリーン/とか、まぁそれでは Twitter との関係性が見えないですが、プロジェクトを作ってから決めます。

天之御船学園放送部♪、新聞部♪の顧問を務めます

みなさんこんにちは、こんばんは、こんにゃくは群馬が日本一ィィィイイイイ!!!!!

すみません。荒ぶりました。がおー

この度、私 azapen6 は天之御船学園放送部♪および同新聞部♪の顧問を務めることになりました。以前より少しずつ動いてはいたのですが、7月27日を目処に活動を本格化させることにしました。

天之御船学園とは?

あんハピ♪』(作:琴慈、『まんがタイムきららフォワード』にて連載、単行本既刊7刊)の作中で『花小泉杏』(はなこいずみ・あん、通称:はなこ)、『雲雀丘瑠璃』(ひばりがおか・るり、通称:ヒバリ)、『久米川牡丹』(くめがわ・ぼたん)、『萩生響』(はぎゅう・ひびき、通称:ヒビキ)、『江古田蓮』(えこだ・れん、通称:レン)の5人が通う学校です。各地から才能溢れる少女たち*1が集まり、1-3組は勉強で、4-6組はスポーツで頂点を目指すことを教育方針として掲げています。実際にある学校では洛南、智弁和歌山あたりが近いでしょうか。

さて、この5人はというと……7組に属しています。あれ?その7組とは……『幸福クラス』なる謎の組でした。担任の小平によって、7組には不幸体質を持った生徒が集められ、幸福を目指すことが告げられます。ヒバリは不服から抗議のために立ち上がりますが、自らの不幸に心当たりがないのかを問われに詰まってしまいます。この理不尽に他の生徒たちも騒ぎ始めます。そこに放たれる小平の一言

いいからさっさと黙れよガキども
そんなだからろくな運持ってねぇんだろうが

これにはヒバリも黙って座るしかありませんでした。

不幸にも7組に集められた生徒たちの未来やいかに?

というのが『あんハピ♪』冒頭のあらすじです。7組専用の授業『幸福実技』では校外に出ることも数多くあります。この学校、一体何なんだ?

天之御船学園・放送部♪

私が顧問を務める放送部は、Twitter アカウント @anhpms (Anne Happy Mifune School) にて活動しています。『あんハピ♪』に関して伝えるべき情報があるときには広報活動を行います。後述の新聞部♪の更新の通知も行います。

それ以外のときは、日付が変わったときに過去のその日あった出来事、朝にタロット占い、設定時刻に時報、正午に昼休みの音楽(再生は自分で行ってください)などをつぶやいています。今後は随時機能を追加していく予定です。

放送部♪ bot について

放送部♪のアカウント @anhpms<> は上述のことからお察し通り、通常時は bot として動作しています。前の記事にも書きましたが、bot のコード/aは Scala で書いています*2。最初は Groovy で書いていたのですが、コードが大きくなるにつれて実行時にミスが顕在化してしまうことがあり、途中から Scala に変えています。

また、twitter4j のコードが結構アレなところがあるので、安全性の観点から akka というライブラリを利用してアクターモデルなるもので書いています。この bot を作るために twitter4j のコードを何度も見ました。更新が半年止まっていることから新バージョンが出ることへの期待は薄いです。正直を言えば全部書き直したいくらいです。まぁそれは開発を進めていく過程で、やはり根本的に書き換えたいと思ったときまで取っておきます。

なお、bot のコードは次で公開しています。まだまだ機能が少ないので、随時更新していく予定です。

[https://github.com/azapen6/AnhpMSBot:title]

Scala

とは、一言で言ってしまえば、関数型 Java です。Scala は以前の記事で出した Groovy と同様に JVM で動かすことを前提とした言語です。もちろん、Java と同様にオブジェクト指向言語でもあります。ただし、Scala と Java は文法的というより思想的に大きな違いがあります。それが関数型というところです。

関数型言語とは

などと言ってしまうといろいろ面倒なことがあるのですが、大雑把に言えば、関数そのもの第一級オブジェクト、すなわち変数に入れたり別の関数に渡したりできるものとして扱う言語ということです。関数型言語はというと、Lisp (Common Lisp, Scheme, etc.), Haskell, OCaml, F# あたりが有名です。他には、Python, Ruby, Groovy, JavaScript などの、通常は関数型と言われない多くのスクリプト言語でも関数を第一級オブジェクトとして扱うことが可能です。さらに言えば、C++11 以降、Java 8 ですらこの意味では関数型と言うことが可能です。これを言うと多分怒られます。

参照透過性と副作用

関数型言語における重要な概念として、参照透過性があります。これは、同じ式を評価すると常に同じ値が返されるということです。数学でいうところの関数はこの性質を満たしていますが、計算機プログラムにおける関数はこれを満たさないことが多々あります。例えばC言語における次のような関数です。

int counter = 0;

void increment() {
    counter = counter + 1; // counter++; と同じだが、敢えて代入の形で書いた。
}

void get_count() {
    return counter;
}

変数 counter の値は関数 increment を実行するごとに1ずつ増えます。よって、関数 get_count が帰す結果は関数 increment を呼び出した回数に依存します。

このようにある行為が他の式評価に影響を及ぼすとき、副作用を伴うと言います。この場合、関数 increment の呼び出しは副作用を伴います。一方で、関数 get_count の呼び出しは副作用を伴いません。

この違いは何かというと、変数に代入を行っているか否かです。変数だから代入するのは当たり前だって?もちろんそうなのですが、変数への代入をコードの様々な場所で行ってしまうと、バグが発生したときに原因がどこにあったのかを調べるのが大変になります。

副作用の問題を解決する方法のひとつとして、変数にそれを操作する関数を紐付けて、変数への操作をその関数を通してしか行えないようにすることです。これがオブジェクト指向の基本的なポリシーです。例えば、先程のプログラムは、C++ では次のようにクラス化できます。

class A {
private:
    int counter = 0;

public:
    void increment() {
        counter = counter + 1;
    }

    void get_count() {
        return counter;
    }
};

クラス A のインスタンスは内部にメンバ変数 counter の実体を持ちますが、外からそれを変更することはできず、メンバ関数 increment によって1ずつ増やすことしかできません。また、値の取得は get_count を通して行う必要があります。

オブジェクト指向的なプログラミングでは、変数を隠して操作を限定することによって、副作用を局所的に閉じ込めることができます。

さて、話がオブジェクト指向の方にずれてしまいましたが、参照透過性という意味では上のコードのどちらも参照透過性を満たしてはいません。代入がある限り参照透過性を満たすことは不可能です。

並列処理と参照透過性

参照透過性が実用的に重要な意味を持つのは並列処理を行うときです。今、スレッドが二つあり、両方がクラス A の同じインスタンス a にアクセスできるとします。そのとき、次のような問題が起こり得ます。

  1. スレッド1が a.get_count で値を取得
  2. スレッド2が a.get_count で値を取得
  3. スレッド1が a.increment を実行
  4. スレッド2が a.increment を実行
  5. スレッド2が a.get_count で値を取得
  6. スレッド1が a.get_count で値を取得

このとき、スレッド1は一回だけ a.increment を実行したにもかかわらず、次の呼び出しで2増えているのです。なぜなら、その間にスレッド2が a.increment を実行してしまっているからです。これと同様のこと(競合)がもっと複雑なデータ構造で起これば、大変面倒なことになることは想像に難くないでしょう。一般に並列処理は再現性のないバグを抱えやすいです。

再現性のないバグというところに目を向けると、では再現性を常に保てればいいのではないか、という話になります。それが参照透過性なのです。

Scala は原則として名前(変数とは呼ばない)に対するアサインメント(変数でいうところの初期化)を一度だけしか行いません。その後に代入文を書くとするとコンパイラに殴られます。例えば、

val a = 1 // value、つまり値の宣言

a = 3 // エラー

変数(val の代わりに var で宣言)を書くことも可能ですが、原則は変数を使わないことが推奨されています。

静的型付けと型推論

Scala のもうひとつの特徴は、静的型付け言語であるということです。つまり、コンパイル時に型が決まります。しかし、上の例を見ると型が書かれていません。明示的に型を宣言に含めるときは、

val a : Int = 1

のように書きます。Java との違いは、型宣言を必ず後ろに書くことです。このように書くと、型はあくまでおまけという感じで省略しても不自然にならない、と思います。もちろんのこと宣言していない名前は使えません。

なぜ型宣言を省略できるかというと、アサインされているものから型が分かるからです。関数の場合にも型推論は適用されて、

def func(x: Int, s: String) = x + s

> func(1, "2")
12 : String

のように、戻り値の型は自動的に String に決まります。これは、Int クラスの + メソッドを String 型引数に適用した結果が String 型になることによります。

このような型推論のおかげで、Scala では多くの場合、型宣言を明示的に書く必要がありません。例外は関数の引数で、上の例のように必ず書く必要があります。上の関数の完全な定義と型は

> def func(x: Int, s: String) : String = x + s
(Int, String) => String

です。

静的型付けのメリットは、単純なミスをコンパイル時に発見することができることにあります。動的型付けのように型が整合しない場合でも実行してみるまでエラーが出ない、ということはありません。プログラムがある程度の大きさになったとき、動的言語でプログラムを書くためには相当注意深くないといけません。

静的型付けでも Java などでは型宣言が煩雑になりがちですが、Scala では型推論のためにそのようなことがありません。

メソッド

先程の説明で + メソッド?と一瞬驚かれたかもしれませんが、Scala では多くの記号をメソッド名に使うことができます。

また、Scala には Java の int 型のようなプリミティブ型は存在せず、すべての値が何らかのクラスのインスタンスです。つまり、Ruby と同様の純粋なオブジェクト指向言語です。例えば 1 という数値に対してもメソッド呼び出しができます。そのひとつが + メソッドです。

さらに、特定のメソッドはドットをつける必要がない場合があります。ということで、x + s は実は x.+(s) と等価です。

このようなシンタックスシュガーが Scala には数多くあり、プログラムを簡潔に書くことを可能にしています。

以上のことはあくまでプログラムを書くときの理屈です。整数などはコンパイル時に都合よくプリミティブ型に変換されるので、Java で書いたときより実行速度が劣るということはありません。

Java のプリミティブ型の扱い

Scala にはプリミティブ型がないと言いましたが、Java と連携するためにはプリミティブ型を避けるわけにも行きません。そこで、Scala のプリミティブ「な」型は Java のプリミティブ型と相互に変換できるようになっています。

Java の int 型は Scala の Int 型と相互に変換されます。

配列の場合は Java の int[] 型が Scala の Array[Int] 型と対応します。

その他にもいろいろとありますが、ここで列挙することはしません。

感想

Scala はよく Java プログラマが移行するには解りづらいところがあると言われますが、私はそのようには感じたことはありません(プロとして Java での開発経験がないというのもありますが)。Groovy と比較しても30行以上書くなら Scala の方が安心感があります。むしろ Scala や多くの関数型言語が備える強力なマッチングスキームは記述を簡潔にしてくれます。ここでは言語に関してこれ以上突っ込みませんが、とりあえず言えることは、Scala でプログラムを書くことはそう難しいことでもないということです。

アクターモデル

\アッカリーン/(7月24日お誕生日おめでとう!)ではなく akka とは、Scala(および Java)でアクターモデルを扱うためのライブラリ群です。アクターモデルといえば 1980 年代に開発されたプログラム言語 Erlang で用いられたことで知られます。Erlang は電話交換器をプログラムするための言語であり、当初から大規模並列システムでの運用を前提としていました。その中核を担ったのがアクターモデルです。

上において、参照透過性が並列処理の問題を解決すると言いましたが、アクターモデルはそれとは異なる解決を図るものです。それはオブジェクト指向の原型に近いものです。

アクターとは

大雑把に言うとまた怒られるかもしれませんが、アクターとはオブジェクトとスレッドを結びつけたものです。

アクタークラス A があるとすると、A のインスタンス actor1 を生成したとき、actor1 はひとつのスレッド上だけで動作します。続けて A の新たなインスタンス actor2 を生成したとき、actor2actor1 とは別のひとつのスレッド上だけで動作します。というように、アクターそれぞれがそれ専用のスレッドで独立して動作するというのがアクターモデルの基本となります。

アクター(俳優)という名前の通り、それぞれのアクターオブジェクトはクラスごとに決められた役割をこなします。あるいはアクターとまで行かずとも普通に人に喩えることもできるできるでしょう。人は原則として非同期に動作しますよね。仕事や用事などは各自が独立にこなしているはずです。

もうひとつの特徴は、各アクターオブジェクトは自分の中だけでアクセスする情報を持っているということです。アクターはその情報に基づいて行動を決めます。情報を外に出すのは他のアクターから質問を受けたときだけで、それに対する解答も非同期的に行われます。他のアクターからのメソッド呼び出しは一般的に禁止されています。これはオブジェクト指向でいうところのデータへのアクセス方法を限定することに相当します。その意味でアクターモデルは極めてオブジェクト指向的な方針を採用していると言えるでしょう。

アクターモデルではメッセージのやり取りによってアクターに対する命令や質問を行います。
問題は異なるスレッドで動作しているアクターに対してどうやってそれを行うかですが、アクターは個々にメールボックスを持つという方法で解決しています。つまり、他のアクターから送られたメッセージは一旦メールボックスに入れられ、受け取ったアクターはそれを順次処理していくのです。これまた普通の人が普段やっていることと同じです。もちろん、アクターは非同期で動作しているので、メソッドのように直ちに答えを返すというものではありません。

以上がアクターモデルの概要です。

    1. なぜアクターモデル?

アクターモデルが使われるべき理由は、最初にも行った通り並列処理における競合の問題を解決するためです。

ここで Java において複数のスレッドが同じオブジェクトにアクセスするとき、いかにして競合を防いでいるかを思い出してみます。Java ではひとつのスレッドが競合の問題があるオブジェクトにアクセスするとき、それをロックして自分だけがオブジェクトにアクセスできるようにします。処理が終わったらロックを解除して他のスレッドに明け渡します。

この方法の問題点は、ひとつのスレッドがオブジェクトをロックしている間、他のスレッドが止められてしまうことと、単純なミスでデッドロックが起こりやすいことです。そのため、プログラマは相当気を使ってオブジェクトのロックと解放を適切に行う必要があります。

一方で、アクターモデルでは、それぞれのアクターが完全に非同期で動作していて、他のアクターからのメッセージの処理も非同期で行うため、ロックという概念は存在しません。よって、デッドロックが発生する理屈がありません。

アクターは基本的にイベントドリブンです。すなわち、メッセージを受け取った瞬間から動作します。その間スレッドを止めているのはアクターシステムであり、アクター内部でスレッドを止めることは推奨されません。というのも、そうすると次のメッセージの処理が滞ってしまうからです。とはいえ場合によっては重い計算のためにすぐには答えを返せない場合もあり得ます。そのときは Scala の Future を使って計算を非同期にして、答えが返ってきたときに自分にメッセージを送るようにできます。

以上のことをまとめると、アクターモデルはオブジェクトを非同期で動作させ、スレッドという存在を隠蔽することによって、ロックを行わずに並列処理を安全に行う方法を提供します。

akka

アクターモデルは当初 Scala 標準として採用され、API も公開されていました。ところが、バージョン 2.10 からは非推奨となり、それ以降は akka という外部ライブラリを使うようにとの勧告が出されました。akka はアクターモデルのみならず、それを土台にした様々な機能を提供する強力なライブラリ群ですが、元々の Scala の実装に比べるとやや面倒になっています。

ところで、akka はもちろん Java からも使うことができます。しかし、あくまで Scala のために作られたライブラリ群のため、プログラムがかえって複雑になるという問題があります。

bot で使った理由

最後に私の bot でなぜアクターモデルを使った理由を述べます。最大の問題は、twitter4j のコードが本体とストリームと非同期版とに分かれていて、整合性が取れているか怪しいことです。twitter4j は Twitter の Stream API を処理するためのライブラリを含んでいますが、その中核ではメッセージを受け取ったときにそれを処理するためのスレッドを生成します。つまり、メッセージの処理は非同期に行われるわけです。ところが、Twitter API と Twitter Stream API は土台を共用しつつも基本的に独立したライブラリであり、競合を完全に回避できているかは怪しいところです。

また、本体の Twitter API に関しても、既に使えなくなった API を含んでいたり、動画のアップロードができなかったりします。既に非推奨となった機能を使っていることも問題です。とにかく、ただ bot を作るという程度のことに何度もソースを見ることになりました。

最初に書いた通り、twitter4j の更新はバージョン 4.0.6 を最後に半年停止しています。数多くの Pull request も放置されたままです。Twitter 公式からリンクされているとはいえ、もはや今後使うことが推奨できるものではないでしょう。とはいえ、Python などで提供されている多くのライブラリに比べると、やや複雑ではありますが、元の Twitter API にかなり忠実で、使える機能は大体使えるというメリットがあります。当然、Java の API を使うことも可能です。

twitter4j の機能を使うには、万が一の場合に備えて、スレッドセーフにする必要がありました。しかし、Scala 標準の並列処理のサポートははっきり言って貧弱です。そのため、まともにやろうとすれば Java の面倒な方法を使わざるを得ないのです。そこでアクターモデルを使うことを考えました。

もうひとつというより当初の目的は、bot ツイートのスケジューリングを行うためというのもありました。Scala にはスレッドスケジューラというものがなく、java.util.concurrent.ScheduledThreadPoolExecutor というやたらめったら長いクラスを使う必要がありました。これを回避するためには、akka のスケジューラを使用するのが手っ取り早いと考えたわけです。

最後に

以上、長々と述べてきましたが、私が言いたいことは、『あんハピ♪』『天之御船学園 放送部♪』『天之御船学園 新聞部♪』をよろしくお願いします、ということです。

なお、新聞部は anhpms.org にて活動予定ですが、諸般の事情により創刊が7月30日・土曜日になりました。今はリンクを切ってありますが、発行時点で @anhpms で告知いたします。

天之御船学園・放送部♪
天之御船学園・新聞部♪
顧問

azapen6

*1:アニメでは女子校、原作では共学だが男子はモブ

*2:なぜか Scala からのみアクセスできないメソッドがあるので、少しだけ Java コードを書いています。

動的言語がつらくなってきた

タイトルそのまんまです。

少し前の記事で Java をそのまま埋め込むことができる動的言語 Groovy についてお話しました。結論としては Groovy は Java を簡単に書ける Java Script である、と書いたのですが、その使用についていくつか思うところがあったので、さくっと書いておきます。これは他の動的言語、例えば PythonRuby などにも当てはまることです。

まず、動的言語の利点については多くの人がそれを享受していることでしょう。基本的に変数宣言がいらない、多くは REPL シェルを備えているので、1行だけ実行してすぐに結果を確認できる、また、結果を見て様々な処理を試してみるのに適している、などでしょう。このような特徴は、ちょっとした計算から画像処理や科学技術計算まで、やってみて結果を見ながら考える、という用途に適しています。概ね MATLAB などのように使えるといえば早いでしょう。無論、スクリプトによるバッチ処理も簡単なものであればすぐに書けてしまいます。

一方で、動的言語には弱点もある、ということを前に少し書きました。今回の話はそういうことです。

動的言語で 30 行以上書くのは厳しい

30 という数字は、エディタでひとつの画面に全文を表示できる程度、という意味で決めました。私としては 20 行でも少しつらいかなーといったところです。

で、具体的に何が問題なのかと言うと、

実行してみないとエラーが出ない

ということに尽きます。もちろん例外はありますが、多くの処理系ではコンパイル時に検出できるようなミスさえ実行時にならないと分かりません。というのも、変数に入るオブジェクトが文脈に依存して変わることがあるので、それに何の操作が適用可能かが実行時にならないと分からないためです。人の3倍はミスをする筆者にとっては由々しき問題と言わざるを得ません。

そのような性質から、動的言語で何百行〜も書くのは相当神経を使う作業になるでしょう。動的言語は実験には向くが開発には向かない、というのが筆者の感想です。というわけで、そろそろつらくなってきたので、ある程度大きめの開発には、型推論の強い静的言語、例えば Rust などを使うことにします。実行速度的にも有利ですし。ちなみに、あんハピ♪宣伝用の天之御船学園放送部♪bot @anhpms は現在 twitter4j を用いて Scala で書いています。直接の Scala 用ラッパーが見あたらないので、五合目くらいから作ってみようかと考えています。Java とは並行処理のモデルが違うのでコアの部分にもある程度手をつける必要もあるでしょう。

さて、おそらく 30 行にも達していないでしょうが、もう限界です。さようなら。