Hatena::ブログ(Diary)

cooldaemonの備忘録 RSSフィード

2012-05-31

転職します

本日(2012年5月31日)をもって、現在お世話になっている会社を退社し、明日から別の会社に入社します。

他所様に伝えるべき何かを持ち合わせてはいないのですが、私に職を紹介してくれたN氏、現職の方々、次職の方々への私信という事で慣れない筆を執りました。

現職について

参入障壁が高く競合他社が少ない、安定した収入を確保できる職場を去るにあたり、様々な葛藤がありましたが、解くべき問題の難易度が低いという理由だけで転職を決めました。ありがちな理由で簡単に詳細を予測できる内容ではありますが、少しだけ補足します。

勤続7年(グループ会社通算で10年)*1の中で、サービスを安定稼働させるために様々な試行錯誤を重ねてきました。あまり詳しい話は守秘義務があるためできませんが、俗にいう上流から下流工程まで様々な事に手を入れてきました。しかし、人間には趣味嗜好があり、私という人間がプログラミングに喜びを感じる性格である都合上、ついプログラミングに比重を置いてしまう傾向があります。過去、本ブログに書き綴った内容も8割以上がサービスの安定稼働という一念あって書き綴ったものですが、ほぼプログラミングに特化した内容となっています。そうやってプログラミングに偏重していくと「現状」のサービスの安定稼働という目標を達成するための十分な技術を獲得しているにも関わらず、プログラミング技術への枯渇感が満たされないので、更にプログラミング技術を追い求め、いつしか「難しい」問題は、社内外の政治的な問題だけになっていました。現行のサービスの限界を越える案件が発生した際、現行のサービスの同等品を Erlang を用いて一人/二ヶ月でフルスクラッチで構築する段階になると、かなり傲慢な考え方ですが、現職に留まる限り、プログラミングで解決するべき難しい問題には出会えない…という考えに確信を持つに至りました。勿論、自ら問題を創出するという方法もあるとは思いますが、その場合、地位*2か企業内起業のどちらかが必要となるので、難しい問題を抱えている職場を探した方が早いだろうと判断しました。

勿論、世の中、きれいごとばかりではありませんし、在職中に不愉快な思いをした事もあります。しかし、今回の転職に前述以外の他意はありません。それが引き金だったのではないかと心配されている方々もいらっしゃるでしょうが、その程度の事で職を放棄したりしません。良い上司・同僚・部下に恵まれ、素晴らしい環境の中で仕事に集中できた事に関しては疑う余地がありません。もし関係があるとしたら、政治的な理由でプログラミングの時間を削られてしまったという箇所のみです。とは言え、辞めるのは確かであり、社内外問わず関係者の方々に多大なるご迷惑を掛けました事を、ここにお詫び申し上げます。誠に申し訳ございませんでした。

また、多々ご迷惑を掛けたにも関わらず、現職の社長が次職の社長に対して、私の事をよろしく頼むという一報を入れてくれたり*3、10年間、本当の意味で私と苦楽を共に*4してきた取締役が、私を修行に出すだけというスタンスで送り出してくれた事により、私の中の罪悪感を打ち消してくれたりと…、転職が決まった後も様々な形でご支援頂き、現職の方々に対しては、本当に感謝の念に堪えません。今まで、本当にありがとうございました。

次職について

N氏の Erlang 雇用を創出するという陰謀に加担する予定です。希望としては、(人的にもサーバ的にも)少ないリソースで大量トラフィックを捌くサービス基盤やログ解析基盤を構築する事に注力したいと考えていますが、現行サービスとの折り合いを見つつ臨機応変に対応したいと考えています。とは言え、研究投資とは名ばかりのお荷物確定の働きっぷりでは信用を得られないので、いきなり (」・ω・)」Erlang!(/・ω・)/Scala!*5 と騒ぐつもりは無く、現職同様に少しずつ侵略していければ良いと考えています。そんな呑気な事では給料を払う意味が無い!と次職の上司にお叱りを受けそうで怖いのですが…、そこは、お手柔らかにお願い申し上げます。

さて、明日の初出社に備えて寝ます。おやすみなさい。

*1:その前の職は勤続5年なので、現職が一番長く努めた職場です

*2:一応、中間管理職ですが…

*3:現職の社長と次職の社長が知人同士だったらしく…

*4:真夜中のシンガポールで倉庫に潜って棚卸しをしたりw;

*5:あれ?w;

2012-05-14

Scala で Android アプリ開発(NDK 編)

下準備

始めに /path/to/hello-world/project/build.scala を次のように修正する。

// ..snip..

object AndroidBuild extends Build {
  lazy val main = Project (
    "Hello World",
    file("."),
    settings = General.fullAndroidSettings ++
               AndroidNdk.settings // これを追加
  )

  // ..snip..
}

次に /path/to/hello-world/src/main/jni/ を作成する。

$ make -p /path/to/hello-world/src/main/jni/

ラッパークラスを作る

ものは試しにファイルの情報を取得するラッパ /path/to/hello-world/src/main/scala/FileStat.scala を作成する。

package com.github.cooldaemon.HelloWorld

import _root_.java.io.File
import _root_.java.util.{Map => JMap}
import _root_.scala.collection.JavaConversions._

object extendFileStat {
  implicit def fileToFileStat(file: File): FileStat = new FileStat(file)
}

class FileStat(file: File) {
  System.loadLibrary("file_stat")

  @native private[this] def getStat(name: String): JMap[String, Long]
  def stat: Map[String, Long] = mapAsScalaMap(getStat(file.toString)).toMap
}

C 関連のファイルを用意する

.class ファイルを作るため一度コンパイルし、その後、javah でラッパクラスから C ヘッダファイルを作成する。

$ cd /path/to/hello-world
$ sbt
> compile
> exit
$ javah -o ./src/main/jni/file_stat.h -classpath ./target/scala-2.9.1/classes com.github.cooldaemon.HelloWorld.FileStat

sbt 経由で javah を実行する方法は試していない。

Makefile として /path/to/hello-world/src/main/jni/Android.mk を作成する。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := file_stat
LOCAL_SRC_FILES := file_stat.c

#LOCAL_C_INCLUDES += $(SBT_MANAGED_JNI_INCLUDE)

include $(BUILD_SHARED_LIBRARY)

最後にファイル本体 /path/to/hello-world/src/main/jni/file_stat.c を作成する。

#include <sys/stat.h>
#include "file_stat.h"

jobject NewLong(JNIEnv* env, jlong value)
{
    jclass longClass = (*env)->FindClass(env, "java/lang/Long");
    jmethodID init = (*env)->GetMethodID(env, longClass, "<init>", "(J)V");
    jobject longObj = (*env)->NewObject(env, longClass, init, value);
    (*env)->DeleteLocalRef(env, longClass);
    return longObj;
}

JNIEXPORT jobject JNICALL Java_com_github_cooldaemon_HelloWorld_FileStat_getStat
  (JNIEnv *env, jobject obj, jstring name)
{
    jclass mapClass = (*env)->FindClass(env, "java/util/HashMap");
    jmethodID init = (*env)->GetMethodID(env, mapClass, "<init>", "(I)V"); 
    jobject mapObj = (*env)->NewObject(env, mapClass, init, 1);

    jmethodID put = (*env)->GetMethodID(env, mapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

    jboolean iscopy;
    const char *mfile = (*env)->GetStringUTFChars(env, name, &iscopy);    

    struct stat finfo;
    lstat(mfile, &finfo);

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "dev"),
        NewLong(env, finfo.st_dev)
    );

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "ino"),
        NewLong(env, finfo.st_ino)
    );

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "mode"),
        NewLong(env, finfo.st_mode)
    );

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "nlink"),
        NewLong(env, finfo.st_nlink)
    );

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "uid"),
        NewLong(env, finfo.st_uid)
    );

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "gid"),
        NewLong(env, finfo.st_gid)
    );

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "rdev"),
        NewLong(env, finfo.st_rdev)
    );

    (*env)->CallObjectMethod(env, mapObj, put,
        (*env)->NewStringUTF(env, "size"),
        NewLong(env, finfo.st_size)
    );

コンパイルしてみる。

$ sbt
> android:ndk-build

使用する

val f = new File(context.getFilesDir, "foo")
import extendFileStat._
f.stat foreach {case (k, v) => Log.d(k + ":" + v.toString)}

Scala で Android アプリ開発(AlertDialog 編)

毎回、AlertDialog を作るのは面倒なので、次のようなオブジェクトを作っておく。

package com.github.cooldaemon.HelloWorld

import _root_.android.content.Context
import _root_.android.app.{Dialog, AlertDialog => AAlertDialog}
import _root_.android.content.DialogInterface

object AlertDialog {
  def create(message: String)(f: () => Unit)(implicit c: Context): Dialog = {
    (new AAlertDialog.Builder(c))
      .setMessage(message)
      .setCancelable(true)
      .setPositiveButton("はい", new DialogInterface.OnClickListener() {
        override def onClick(dialog: DialogInterface, id: Int) {
          f.apply()
          dialog.dismiss()
        }
      })
      .setNegativeButton("いいえ", new DialogInterface.OnClickListener() {
        override def onClick(dialog: DialogInterface, id: Int) {
          dialog.cancel()
        }
      })
      .create()
  }
}

次のように onCreateDialog 内で使用する。

class FooActivity extends TypedActivity {
  // ..snip..

  object DialogID extends Enumeration {
    val WIFI,BLUETOOTH = Value
  }

  override protected def onCreateDialog(id: Int, args: Bundle): Dialog = id match {
    case id if id == DialogID.WIFI.id =>
      AlertDialog.create("Wi-Fi設定を行う") { () =>
        startActivity(new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS))
      }
    case id if id == DialogID.BLUETOOTH.id =>
      AlertDialog.create("Bluetooth設定を行う") { () =>
        startActivity(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS))
      }
  }

  // ..snip..
}

Scala で Android アプリ開発(AsyncTask 編)

ScalaでAndroidアプリ作成時、AsyncTaskの可変長引数メソッドが使えないことへの対策とサンプル ― Gist を丸パクリ。

Scala は、可変長引数を持つメソッドを上書きできないので、可変長引数を上書き済みの /path/to/hello-world/src/main/java/com/github/cooldaemon/HelloWorld/AsyncTask.java を作成する。

package com.github.cooldaemon.HelloWorld;

public abstract class AsyncTask<Params, Progress, Result> extends android.os.AsyncTask<Params, Progress, Result> {
   @Override
   protected Result doInBackground(Params... params) {
      return doInBackground(params.length > 0 ? params[0] : null);
   }

   abstract protected Result doInBackground(Params param);

   @Override
   protected void onProgressUpdate(Progress... values) {
      onProgressUpdate(values.length > 0 ? values[0] : null);
   }

   protected void onProgressUpdate(Progress value) {}

   @SuppressWarnings({"unchecked"})
   protected final void publishProgress(Progress value) {
      super.publishProgress(value);
   }
}

用意した AsyncTask を継承して、別スレッドで処理したいタスクを記述する。

//可変長引数を使えない分は、case class で対応する
case class FooParam(foo: String, bar: String)

//  AsyncTask から Activity を操作する(doInBackground は除く)ために Activity のインスタンスを渡しておく
class FooTask(val activity: FooActivity) extends AsyncTask[FooParam, Int, Either[Throwable, Unit]] {
  override protected def onPreExecute() {
    // Activity に変化を加える。例えばダイアログの表示など
  }

  // doInBackground だけ別スレッドで動作する
  // doInBackground 内で publishProgress を使うと UI のスレッドに Int の値を送れる
  // 送った Int の値は onProgressUpdate で受け取れる
  override protected def doInBackground(param: FooParam): Either[Throwable, Unit] = {
    for {
      _     <- ham(param).right
      chick <- egg(param).right
      _     <- spam(param, chick).right
    } yield ()
  }

  override protected def onProgressUpdate(progress: Int) {
    // プログレスの更新
  }

  // AsyncTask の cancel メソッドを使用されると onCancelled が呼ばれる
  override protected def onCancelled() {
    // doInBackground の停止を促す。例えば doInBackground で参照しているフラグを onCancelled で更新するなど
  }

  override protected def onPostExecute(result: Either[HttpClientErrorResult, Unit]) {
    // Activity に変化を加える。例えばダイアログを消すなど
  }
}

このコードは、画面の回転に対応していない。

画面が回転すると Activity のインスタンスは置き換えられるが AsyncTask は残り続けるので AsyncTask が保持する Activity のインスタンスを置き換える必要がある。

また、画面の回転と ProgressDialog を組み合わせる場合、画面回転後に ProgressDialog を再表示する必要があり、onCreateDialog に頼る事になる。

ProgressDialog を再表示する場合、以前の ProgressDialog 進行状況を引き継いだり、キャンセル処理を正常に動作させる必要があるので、なるべく画面が回転しないように固定しておいた方が良い。

もし、どうしても AsyncTask + ProgressDialog + 画面回転に対応したいのであれば、Activity から操作できる AsyncTask と ProgressDialog を管理する object を用意する。

この辺りは、ダウンローダ編で詳しく解説する。

Scala で Android アプリ開発(Log 編)

ログを出力する際、ログ出力位置の情報も一緒に出力する。

package com.github.cooldaemon.HelloWorld

import _root_.android.util.{Log => ALog}

object Log {
  val TAG = "HelloWorld"
  
  def e(m: String) = printlog(ALog.e, m)
  def w(m: String) = printlog(ALog.w, m)
  def i(m: String) = printlog(ALog.i, m)
  def d(m: String) = printlog(ALog.d, m)
  def v(m: String) = printlog(ALog.v, m)
  
  private def printlog(f: (String, String) => Int, m: String): Int = {
    val st = ((new Throwable()).getStackTrace).apply(2)
    f(TAG, "%s.%s(%s:%s): %s".format(
      st.getClassName, st.getMethodName, st.getFileName, st.getLineNumber, m
    ))
  }
}

製品出荷時には、ログを出力しないよう注意。

2012-05-10

Scala で Android アプリ開発(ActionBarSherlock 編)

ActionBar は使いたいけれど、ターゲットの API Level が低い場合に重宝する ActionBarSherlock を Scala から使う。

sbt プロジェクト設定

まずは依存するライブラリを指定する。準備編を参考に作成したディレクトリ /path/to/hello-world の直下に main.sbt を作成する。

import sbt._
import Keys._
import AndroidKeys._

libraryDependencies ++= Seq(
  "com.actionbarsherlock" % "library" % "4.0.0-SNAPSHOT" artifacts(Artifact("library", "apklib", "apklib")),
  "android" % "compatibility-v4" % "r3-SNAPSHOT"
)

次に依存するライブラリの位置を指定する。/path/to/hello-world/project/build.scala を次のように修正する。

import sbt._

import Keys._
import AndroidKeys._

object General {
  val settings = Defaults.defaultSettings ++ Seq (
    name := "Hello World",
    version := "0.1",
    versionCode := 0,
    scalaVersion := "2.9.1",
    platformName in Android := "android-15", // android-10 から android-15 に変更
    resolvers += "ActionBarSherlock snapshots" at  "http://r.jakewharton.com/maven/snapshot/" // 新規追加
  )

// 以下、変更が無いので省略

最後に依存ライブラリを取得する。

$ cd /path/to/hello-world
$ sbt
> update
// 省略
[info] Resolving com.actionbarsherlock#library;4.0.0-SNAPSHOT ...
[info] Resolving android#compatibility-v4;r3-SNAPSHOT ...
// 省略
[info] Done updating.
[success] Total time: 4 s, completed 20XX/XX/XX XX:XX:XX
> exit

API Level の修正

AndroidManifest.xml 内の <uses-sdk> タグを次のように修正する。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.drops_market.Downloader">

<!-- 変更が無いので省略 -->

  <!-- android:minSdkVersion="10" から変更 -->
  <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15"/>
</manifest>

Activity から使用する

package com.github.cooldaemon.HelloWorld

import _root_.android.app.Activity
import _root_.android.os.Bundle
import _root_.android.content.Context

// 新規に追加
import _root_.com.actionbarsherlock.app.SherlockActivity
import _root_.com.actionbarsherlock.view.{Menu, SubMenu, MenuItem}

// Activity を SherlockActivity に変更
class MainActivity extends SherlockActivity with TypedActivity {
  implicit lazy val c: Context = this
  
  override def onCreate(bundle: Bundle) {
    setTheme(R.style.Theme_Sherlock) // テーマを設定
    super.onCreate(bundle)
    setContentView(R.layout.main)

    findView(TR.textview).setText("hello, world!")
  }
  
  object MenuID extends Enumeration {
    val FOO,BAR,BAZ = Value
  }

  // 試しにメニューを追加してみる
  override def onCreateOptionsMenu(menu: Menu): Boolean = {
    menu
      .add(0, MenuID.FOO.id, Menu.NONE, "Foo")
      .setIcon(android.R.drawable.ic_menu_view)
      .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
    
    val subMenu = menu.addSubMenu(0, MenuID.BAR.id, Menu.NONE, "Bar")

    subMenu.getItem
      .setIcon(android.R.drawable.ic_menu_more)
      .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)

    subMenu
      .add(0, MenuID.BAZ.id, Menu.NONE, "Baz")
      .setIcon(android.R.drawable.ic_menu_add)
      .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)

    true
  }

  override def onOptionsItemSelected(item: MenuItem): Boolean = {
    item.getItemId match {
      case id if id == MenuID.FOO.id => AtomicToast.show("Selected Foo")
      case id if id == MenuID.BAR.id => AtomicToast.show("Selected Bar")
      case id if id == MenuID.BAZ.id => AtomicToast.show("Selected Baz")
      case _ =>
    }
    true
  }
}

Toast 編で作成した AtomicToast を利用している。

テーマとして何が使えるか?SHOW_AS_ACTION_IF_ROOM って何よ?という疑問をお持ちの方は Google 先生にお尋ねください。

/path/to/hello-world/main.sbt に次のような設定していると…

javacOptions ++= Seq("-Xlint:unchecked")

scalacOptions ++= Seq("-verbose", "-unchecked", "-deprecation")

ものすごい数の ActionBarSherlock 関連の警告が出力されるが、ご愛嬌という事で。

Scala で Android アプリ開発(Toast 編)

Toast 表示中に Toast を表示すると問題があるらしい*1ので、次のようなラッパーオブジェクトを用意する。

package は、準備編で用意したもの。

package com.github.cooldaemon.HelloWorld

import _root_.android.content.Context
import _root_.android.widget.Toast

object AtomicToast {
  private[this] var toast: Toast = null

  def show(message: String)(implicit c: Context) = synchronized {
    if (toast != null) toast.cancel()
    toast = Toast.makeText(c, message, Toast.LENGTH_LONG)
    toast.show()
  }
}

これを Activity から使うには、次のようにする。

package com.github.cooldaemon.HelloWorld

// 省略

class MainActivity extends Activity with TypedActivity {
  implicit lazy val c: Context = this

  // 省略

  // 何らかのメソッドの中で…
    AtomicToast.show("Selected Foo")

  // 省略
}

implicit で Context を渡すのが嫌な方は適宜修正してご利用ください。

Scala で Android アプリ開発(準備編)

備忘という名の引き継ぎ的なアレ。Mac OSX 10.7 上で開発する事を前提とする。

sbt の準備

この辺りを見ながら sbt-launch.jar をダウンロードし、/usr/local/bin 配下に sbt-launch-0.11.3.jar という名前で保存する。

このままだと使い難いので、下記の Shell Script を用意する。

$ cat /usr/local/bin/sbt
java -Dhttp.proxyHost=XXX.XXX.XXX.XXX -Dhttp.proxyPort=XXXX -Xmx512M -Dfile.encoding=UTF-8 -jar `dirname $0`/sbt-launch-0.11.3.jar "$@"
パラメータ説明
-Dhttp.proxyHost=XXX.XXX.XXX.XXX -Dhttp.proxyPort=XXXXProxy 設定。Proxy を経由せずに外部と http/https 接続できるのであれば不要
-Xmx512M最大ヒープサイズ。環境に合わせて適度な値を…
-Dfile.encoding=UTF-8これを入れないと sbt console 上で日本語が使えない

giter8 の準備

まず cs コマンドをインストールする。

$ curl https://raw.github.com/n8han/conscript/master/setup.sh | sh

~/bin 配下にコマンドがインストールされるので ~/bin に PATH を通しておく。

次に g8 コマンドをインストールする。

$ cs n8han/giter8

Android SDK の準備

Android SDK | Android Developers から Mac OS X (intel) の android-sdk_r18-macosx.zip をダウンロードし、unzip で /usr/local 配下に伸張する。すると /usr/local/android-sdk-macosx が作成されるので下記の環境変数を設定する。

$ export ANDROID_SDK_HOME=/usr/local/android-sdk-macosx

zsh を利用しているなら .zshenv に上記を追加しておく。

また /usr/local/android-sdk-macosx/tools も作成されているので PATH を通しておく。

$ export PATH=$PATH:/usr/local/android-sdk-macosx/tools

zsh を利用しているなら .zshenv に下記を追加する。

path=(/usr/local/android-sdk-macosx/tools(N) $path)

次に /usr/local/android-sdk-macosx/tools/android を実行し Android SDK Manager を起動する。

$ android sdk

Proxy 設定が必要であれば、Android SDK Manager メニューから環境設定を選択し、Proxy settings の項目を穴埋めする。

Android SDK Manager 起動後、Tools フォルダ内の Android SDK Platforms-tools と Android X.X.X (API XX) の幾つか*2にチェックを入れ、下部の Install N package ボタンを押す。

すると、/usr/local/android-sdk-macosx/platform-tools と /usr/local/android-sdk-macosx/platforms/android-NN が作成される。platform-tools には adb コマンドが入っているので PATH を通しておく。

$ export PATH=$PATH:/usr/local/android-sdk-macosx/platform-tools

Android NDK の準備

Android NDK | Android Developers から Mac OS X (intel) の android-ndk-r8-darwin-x86.tar.bz2 をダウンロードし、tar jxf で /usr/local 配下に伸張する。すると /usr/local/android-ndk-r8 が作成されるので下記の環境変数を設定する。

$ export ANDROID_NDK_ROOT=/usr/local/android-ndk-r8

PATH も通しておく。

$ export PATH=$PATH:/usr/local/android-ndk-r8

準備した環境を試す

プロジェクトを作成する。

$ cd /path/to
$ g8 jberkel/android-app -b master

Template for Android apps in Scala

package [my.android.project]: com.github.cooldaemon.HelloWorld
name [My Android Project]: Hello World
main_activity [MainActivity]:
scala_version [2.9.1]:
api_level [10]:
useProguard [true]:

Applied jberkel/android-app.g8 in hello-world

コンパイルして実機にインストールする*3

$ cd /path/to/hello-world 
$ sbt
// メッセージ省略
> compile
// メッセージ省略
> android:install-device
// メッセージ省略
>

実機でアプリを起動すると hello, world! と表示される。


実践編へ続く。

*1:先に本記事のような対策をしてしまったので、困った経験がない

*2:実案件がターゲットとする API Level を選択する事

*3:USB デバックモードに設定した実機を Mac に接続する事を忘れないように…

2011-12-22

Scala で Iteratee と Enumerator を書く練習

とりあえず使い方だけメモ

package com.github.cooldaemon.scalaz_test

import scalaz._

object TryIteratee {
  import Scalaz._
  import IterV._

  def run {
    implicit val ListEnum = new Enumerator[Seq] {
      def apply[E, A](e: Seq[E], i: IterV[E, A]): IterV[E, A] = {
        e match {
          case Seq() => i
          case x::xs => i.fold(
            done = (_, _) => i,
            cont = k => apply(xs, k(El(x)))
          )
        }
      }
    }

    implicit val StreamEnum = new Enumerator[Stream] {
      def apply[E, A](e: Stream[E], i: IterV[E, A]): IterV[E, A] = {
        e match {
          case Stream() => i
          case x #:: xs => i.fold(
            done = (_, _) => i,
            cont = k => apply(xs, k(El(x)))
          )
        }
      }
    }

    val seq = Seq.range(1, 5)
    
    head(seq).run assert_=== Some(1)
    
    val m = for {
      x <- head[Int]
      y <- head[Int]
    } yield (x, y)

    m(seq).run assert_=== (Some(1), Some(2))

    def dropAndPrintAll[E] :IterV[E, Unit] = {
      def step(s: Input[E]) :IterV[E, Unit] = {
        s(
          el    = {e =>
            println(e)
            Cont(step)
          },
          empty = Cont(step),
          eof   = Done((), EOF[E])
        )
      }
      Cont(step)
    }
    println("--<自作 Iteratee>--")
    dropAndPrintAll(seq).run
    
    def peekAndPrintAll[E] :IterV[E, Unit] = {
      def step(initS: Input[E])(s: Input[E]): IterV[E, Unit] = {
        s(
          el    = {e =>
            println(e)
            Cont(step(initS))
          },
          empty = Cont(step(initS)),
          eof   = Done((), initS)
        )
      }
      Cont({s => step(s)(s)})
    }

    val m2 = for {
      _ <- peekAndPrintAll[Int]
      x <- head[Int]
    } yield x

    println("--<Iteratee の合成>--")
    m2(seq).run assert_=== Some(1)
    
    println("--<Enumerator を後から追加>--")
    val m3 = dropAndPrintAll(seq)
    val m4 = m3(Stream.range(5, 10)) // Seq に Stream を追加しても良い
    m4.run
  }
}

解説は後で。例とは言え Iteratee の中に副作用が存在する所がダサい。

疑問点その1

下記を末尾再帰にするには…

      def apply[E, A](e: Seq[E], i: IterV[E, A]): IterV[E, A] = {
        e match {
          case Seq() => i
          case x::xs => i.fold(
            done = (_, _) => i,
            cont = k => apply(xs, k(El(x)))
          )
        }
      }

下記のようにする必要がある。何でだろー?

      @annotation.tailrec def apply[E, A](e: Seq[E], i: IterV[E, A]): IterV[E, A] = {
        val next: Option[(Seq[E], IterV[E, A])] = e match {
          case Seq() => None
          case x::xs => i.fold(
            done = (_, _) => None,
            cont = k => some((xs, k(El(x))))
          )
        }
        next match {
          case None => i
          case Some((es, is)) => apply(es, is)
        }
      }

疑問点その2

Haskell みたいに Enumerator 同士を合成する方法が解らない

参考

2011-12-14

Scalaz.concurrent

この記事は、Scala Advent Calendar JP 2011 の14日目です。無理を言って、二日間も枠を頂いてしまいました。

今回は、Scalaz 6.0.3 の concurrent について解説を行います。個人的には Scalaz.concurrent の主役は Promise だと思っているので、早速、その解説から行いたいのですが、Promise を理解する為には、Promise と同一パッケージ内に存在する Strategy, Effect, Actor の理解が不可欠であるため、その解説から順番に行っていきます。とは言え、これらは非常にコード量が少ないので簡単に理解できると思います。

御託はいいから、さっさとコード読ませろ!という方は、こちらをご確認ください。これを読んで意味が理解できる方は、以下の解説を読む必要はありません。

以降、掲載する動作確認用のコードには、全て下記が import されている事とします。説明の都合上、Scalaz.concurrent のコードも併記していきますが、そちらとは無関係です。

import scalaz._
import scalaz.concurrent._
import Scalaz._
import java.lang.Thread.currentThread

Strategy

まずは、Strategy の定義から眺めていきます。下記をご覧ください。

trait Strategy {
  def apply[A](a: => A): () => A
}

abstract class StrategyLow {
  /**
   * A strategy that evaluates its argument in the current thread.
   */
  implicit val Sequential: Strategy = new Strategy {
    def apply[A](a: => A) = {
      val v = a
      () => v
    }
  }

//..snip..

object Strategy extends StrategyLow {

//..snip..

scalaz.concurrent.Strategy のソースコードから Strategy Trait と、その実装の中から一番単純なものを抜粋しました。

Strategy Trait だけ眺めると意味が解りませんが、その実装である Sequential を眺めると、apply は、受け取った関数を評価し、その結果を返す無名関数を返すものだと解ります。

続けて同じソースコード内の他の Strategy Trait の実装も見てみましょう。

  implicit val Naive: Strategy = new Strategy {
    import scala.concurrent.ops.future
    def apply[A](a: => A) = future {a}
  }

受け取った関数を future 内で評価…即ち、毎回、新規に Thread を起動し、その上で関数を評価しています。

試しに使ってみましょう。

import Strategy.Sequential
val seq = Sequential {currentThread.getName}
println("seq = " + seq())

import Strategy.Naive
val fut = Naive {currentThread.getName}
println("fut = " + fut())

上記を REPL 等に入力すると、下記のような結果が得られます。

seq = run-main
fut = Thread-4

scalaz.concurrent.Strategy 内には、Sequential や Naive の他に、幾つか有益な Strategy Trait の実装があるので、是非、ご確認ください。ちなみに、一番お世話になるのは下記でしょうか。

  import java.util.concurrent.ExecutorService

  implicit def Executor(implicit s: ExecutorService) = new Strategy {
    import java.util.concurrent.Callable
    def apply[A](a: => A) = {
      val fut = s.submit(new Callable[A] {
        def call = a
      })
      () => fut.get
    }
  }

使う場合は下記の通りです。

import Strategy.Executor
import java.util.concurrent.{TimeUnit, Executors}

implicit val executorService = Executors.newFixedThreadPool(2)
val fut = Executor.apply {currentThread.getName}
println("fut = " + fut())

executorService.shutdown
executorService.awaitTermination(60L, TimeUnit.SECONDS)

出力結果は下記の通りです。

fut = pool-4-thread-1

Executor を利用する際、上記のような苦労をせずとも scalaz.concurrent.Strategy には下記の通り DefaultStrategy が定義されているので、通常はそちらを使う事になると思います。

object Strategy extends StrategyLow {
  lazy val DefaultExecutorService: ExecutorService = {
    import Executors._
    newFixedThreadPool(Runtime.getRuntime.availableProcessors, new ThreadFactory {
      def newThread(r: Runnable) = {
        val t = defaultThreadFactory.newThread(r)
        t.setDaemon(true)
        t
      }
    })
  }

  implicit lazy val DefaultStrategy: Strategy = Executor(DefaultExecutorService)
}

作成した Thread を Daemonize している所が特徴と言えば特徴です。

Strategy Trait の実装は簡単に定義できるので、例えば、Scala 標準の Remote Actor を利用し、物理的に離れたノード上で関数を実行する Strategy を新たに定義する事もできます。ご興味がある方はチャレンジしてみてください。

Effect

scalaz.concurrent.Effect のソースコードは、あまりにも短いので解説を省きます。

使い方は以下の通りです。

import Strategy.Naive
val ef1 = effect[String] { message =>
  println(currentThread.getName + " : " + message)
}
(1 to 3) foreach {n => ef1 ! "run my effect"}

implicit な Strategy が無ければ DefaultStrategy が使用されるのですが、確認用の出力が解り易いので Naive を利用しています。

出力結果は下記の通りです。

Thread-6 : run my effect
Thread-7 : run my effect
Thread-8 : run my effect

メッセージ送信の度に違う Thread 上で関数が評価されている事が解ると思います。

最後に Effect と関数の合成の例を挙げます。

val ef2 = ef1 contramap { (n: Int) =>
  println(currentThread.getName + " : " + n.toString)
  "run my effect " + n.toString
}
(1 to 3) foreach {n => ef2 ! n}

Effect[String] と Int => String を合成して Effect[Int] を作成しています。

出力結果は下記の通りです。

Thread-10 : 1
Thread-11 : 2
Thread-12 : run my effect 1
Thread-13 : run my effect 2
Thread-14 : 3
Thread-15 : run my effect 3

始めに Int => String が評価され、次に ef1 が評価されている事が解ると思います。

Actor

ちょっと息切れ*1してきたので scalaz.concurrent.Actor のソースコード解説は無しで…いきなり使い方の解説を行います。とは言え、scalaz.concurrent.Actor のソースコードも短いので、是非、ご一読ください。

使い方は下記の通りです。

import Strategy.Naive
val a1 = actor { (message: String) =>
  println(currentThread.getName + " : " + message)
}
(1 to 3) foreach {n => a1 ! "run my actor"}

出力結果は下記の通りです。

Thread-4 : run my actor
Thread-4 : run my actor
Thread-4 : run my actor

メッセージを一旦メールボックスに貯めているので、Naive を使用していてもタイミングによっては、同一 Thread 上で複数のメッセージが処理されます。

Scala 標準の Actor と異なり、リンクによる相互監視の機能等はありませんが、Actor 作成時にエラーハンドラを渡せます。

val a2 = actor(
  { (message: String) =>
    throw new Exception(currentThread.getName + " : " + message)
  },
  { (e: Throwable) =>
    println("exception: " + e.getMessage)
  }
)
(1 to 3) foreach {n => a2 ! "run my actor"}

出力結果は下記の通りです。

exception: Thread-11 : run my actor
exception: Thread-11 : run my actor
exception: Thread-11 : run my actor

Effect と同様に関数と合成が可能です。

val a3 = a1 contramap { (n: Int) =>
  println(currentThread.getName + " : " + n.toString)
  "run my actor " + n.toString
}
(1 to 3) foreach {n => a3 ! n}

出力結果は下記の通りです。

Thread-5 : 1
Thread-6 : run my actor 1
Thread-5 : 2
Thread-7 : run my actor 2
Thread-5 : 3
Thread-8 : run my actor 3

Promise

Promise は Scala 標準の Future と同じく未来の計算結果を表現するために使用するコンテナです。scalaz.concurrent.Promise のソースコード解説も無しで、使い方の解説だけ行います。

早速、下記をご覧下さい。相変わらず解りやすさ優先で Naive を使用します。

import Strategy.Naive

def m = {currentThread.getName}
val p = new Promise[String]()
p.fulfill(m)
println("p = " + p.get)

空の Promise を作成し、fulfill で Promise に関数を与え、get で fulfill で与えた関数の結果を取得しています。

get は、fulfill に与えた関数の評価が終了するまで処理をブロックします。

出力結果は下記の通りです。

p = Thread-4094

fulfill を使用せず、下記にように Promise 作成時に関数を与える事もできます。

val p1 = promise(m)
println("p1 = " + p1.get)

val p2 = m.pure[Promise]
println("p2 = " + p2.get)

出力結果は下記の通りです。

p1 = Thread-4095
p2 = Thread-4096

ここまで get で Main Thread をブロックしてきましたが、下記のように Effect や Actor に結果を引き渡す事もできます。*2

val a = actor[String] { message =>
  println("message = " + message + " : " + currentThread.getName)
}
promise(m) to a

出力結果は下記の通りです。

message = Thread-4096 : Thread-4098

to は、Main Thread をブロックしません。to は、以下で説明する map/flatMap の内部で利用されています。

さて、ここからが本番です。今までの解説は、全て以下を解説するために存在していたと言っても過言ではありません。

Promise の結合

Promise は、map/flatMap/join/filter 等が使えます。

map
val p = promise(m) map {message => message + " : " + currentThread.getName}
println("p = " + p.get)
p = Thread-13 : Thread-15
flatMap
val p = promise(m) flatMap {message => promise(message + " : " + currentThread.getName)}
println("p = " + p.get)
p = Thread-19 : Thread-21

ちょっと横道に逸れますが、関数は Promise を返す関数に変換できます。

def m(message: String) = {message + " : " + currentThread.getName}
val f = {m(_)}.promise
println("result = " + f("foo").get)
result = foo : Thread-24

それらを flatMap で繋ぐ事ができます。

val p = f("foo") flatMap f
println("p = " + p.get)
p = foo : Thread-25 : Thread-27

何か値を入れるとコンテナに包んで返す関数と言えば Kleisli ですよね。

val g = f >=> f >=> f
println("kleisli = " + g("foo").get)
Kleisli = foo : Thread-30 : Thread-33 : Thread-36
join
val p = promise(f("foo")).join
println("p = " + p.get)
p = foo : Thread-40
filter
val filterFun = {(message: String) =>
  println(message + " : " + currentThread.getName)
  message match {
    case "bar" => true
    case _ => false
  }
}

val p1 = promise("bar") filter filterFun
println("p1 = " + p1.get)

val p2 = promise("baz") filter filterFun
p2.break
try {p2.get} catch {case e => println("p2 = " + e.toString)}
bar : Thread-47
p1 = bar
baz : Thread-51
p2 = scalaz.concurrent.Promise$BrokenException
for

map/flatMap/filter が定義されているので for が使えます。

val p = for (
  m1 <- f("foo");
  m2 <- f(m1);
  m3 <- f(m2)
) yield m3
println("p = " + p.get)
p = foo : Thread-54 : Thread-56 : Thread-59

それぞれ別 Thread 上で評価されていますが、シーケンシャルに処理されている所がポイントです。例えば、f の処理時間が 1000ms であるなら、上記の for 全体の評価には 3000ms 必要となります。

applicative
val p = (f("foo") |@| f("bar") |@| f("baz")) {_ + " | " + _ + " | " + _}
println("p = " + p.get)
p = foo : Thread-69 | bar : Thread-70 | baz : Thread-71

for と異なり評価の順序を保証しないため、並行的に処理されます。例えば、f の処理時間が 1000ms であっても、上記の評価は 1000ms となります。*3

Promise にとって Applicative Style は、単に見た目の問題だけではなく、処理の効率を良くするためにも利用できるので、シーケンシャルな for*4 との使い分けをしっかり行う必要があります。

traverse
val seq: Seq[Int] = (1 to 3).toSeq

val p1 = seq traverse { n =>
  def m = {currentThread.getName + " : " + n.toString}
  promise(m)
}
println("p1 = " + p1.get)
p1 = List(Thread-92 : 1, Thread-96 : 2, Thread-106 : 3)

p1 の型が Promise[Seq[String となる所がポイントです。

Seq[Promise[String を Promise[Seq[String に変換する事もできます。

val ps: Seq[Promise[String]] = seq map {n =>
  def m = {currentThread.getName + " : " + n.toString}
  promise(m)
}
val p2 = ps.sequence
println("p2 = " + p2.get)
p2 = List(Thread-154 : 1, Thread-155 : 2, Thread-156 : 3)

traverse の亜種(?)で、渡す関数の戻り値の型が Promise でなくとも良い parMap もあります。

val p3 = seq parMap { n =>
  currentThread.getName + " : " + n.toString
}
println("p3 = " + p3.get)
p3 = List(Thread-218 : 1, Thread-217 : 2, Thread-216 : 3)

他に parBind や parZipWith もあります。

val p4 = seq parBind { n =>
  Seq(currentThread.getName + " : " + n.toString)
}
println("p4 = " + p4.get)
p4 = List(Thread-277 : 1, Thread-276 : 2, Thread-275 : 3)
val p5 = seq.parZipWith(seq) { (x, y) =>
  currentThread.getName + " : " + x.toString + " - " + y.toString
}
println("p5 = " + p5.get)
p5 = List(Thread-337 : 1 - 1, Thread-338 : 1 - 2, Thread-339 : 1 - 3, Thread-340 : 2 - 1, Thread-341 : 2 - 2, Thread-342 : 2 - 3, Thread-343 : 3 - 1, Thread-344 : 3 - 2, Thread-345 : 3 - 3)

いやー、本当に便利ですね。

その他

例の如く、突っ込み大歓迎です。

*1:早くモンハン3Gをプレイしたかったのです。ごめんなさい。

*2:to は、Effect 及び Actor を未来に送る…とも表現できますよね

*3:勿論、Strategy や様々な条件で、1000ms よりも遅くなります

*4:これはこれで、コールバック関数のネスト地獄を回避するために必要