ラベル scala の投稿を表示しています。 すべての投稿を表示
ラベル scala の投稿を表示しています。 すべての投稿を表示

2014年7月3日木曜日

PlayFramework 2.3.1 with Scala 2.11.1 へプロジェクトをアップグレードした時のメモ

最近、ScalaもPlayもいろいろアップデートしていて速くなったとか(Scala)、速くなったとか(Play)言っているから、
{まあ、入れてみるか}
とやってみたら、以前のプロジェクト(Play2.2.x with Scala2.10.x)をアップグレードしようとした時いろいろややこしかったので、別のプロジェクトをやるときに絶対忘れそうなのでメモっておく。

まず、新しいPlayとScalaを公式ページでdownloadしようとすると、activatorなるものをインストールするように誘導された。
{activatorってなんじゃい}
完全に浦島太郎状態。どうやら、Scala, Play, Akkaやらサンプルやらwebエディターやらwebデバッガーやらセットになっていて、これ入れれば全部OKみたいなものらしい。
しかもplayコマンドとかsbtコマンドとか、全部 activator コマンドでやれということらしい。
で、activatorインストール後、今までのPlayのprojectフォルダに行って、
activator compile
とかいきなりやったけど、エラーの山。
{ま、そりゃそうだよな。しかたないか。}
なので、projectのファイルを色々変更した。

まず、project/plugin.sbt のplayのバージョンを以下のように変更
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.1")
次に、project/build.propertiesのsbtバージョンを以下のように変更
sbt.version=0.13.5
そんで、project/Build.scalaをこのサイトのSbt sampleを参考にこんな感じ変えてみた。
import sbt._
import Keys._

object ApplicationBuild extends Build {

  val appName         = "xxxxx"
  val appVersion      = "1.0-SNAPSHOT"

  val main = Project(appName, file(".")).enablePlugins(play.PlayScala).settings(
    version := appVersion,
    scalaVersion := "2.11.1",
    libraryDependencies := {
   CrossVersion.partialVersion(scalaVersion.value) match {
     // if scala 2.11+ is used, add dependency on scala-xml module
     case Some((2, scalaMajor)) if scalaMajor >= 11 =>
       libraryDependencies.value ++ Seq(
         "org.scala-lang.modules" %% "scala-xml" % "1.0.1",
         "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1",
         "org.scala-lang.modules" %% "scala-swing" % "1.0.1",
         "com.github.scala-incubator.io" %% "scala-io-file" % "0.4.3-1")
     case _ =>
       // or just libraryDependencies.value if you don't depend on scala-swing
       libraryDependencies.value :+ "org.scala-lang" % "scala-swing" % scalaVersion.value
   }
 },   
 scalacOptions += "-feature"
  )

}
参考ページのコードに追加したのは、scalaxのscala-io-fileの部分、モジュール名とかバージョンとかわからなかったけど、MVNRepositorysというサイトから持ってきた。このサイトは知らなかったけど、こういうときはメチャメチャ便利なところ。

これでとりあえず動くようになった。

2014年4月11日金曜日

[scala][akka] microkernelを使って独立したプロセスに立てたRemote Actorとの通信サンプル

自分は、akkaの「Let it crash」というという発想が大好きなんです。
初めて「Let it crash」というスローガンを見たとき
{だよね。それでいいんだよね。}
と感激しました。

ただ、crashさせるためのSupervisor(docサンプル)の仕組みは、Actorはcrashさせるけど、jvmまでは殺してくれない。自分の場合、ヘビーな計算をscalaからloadLibraryしたc++のモジュールをJNA経由で使ってやっているので、jvmごとcrashさせたいんです。(外部libraryのunloadはできないようなので・・・)

そこで、登場するのがakkaのmicrokernelで、こいつを使うと簡単に独立したプロセスのjvmでActorを立てることが出来る。
必要に時は、こいつをjvmごとkillして復活させればいい ← もっとスマートな仕組みがきっとある気がする

で、HelloWorldのサンプルを書いてみた。
listenポートを可変にしたかったので、confファイルを使わない形で作ってみた。
application.confとかに設定が分かれてないほうがサンプルとしてもわかりやすいし。
まず、リモートで接続される側のmicrokernel
パラメータは、起動時に引数として渡せないので、起動する時は、
env AKKA_PORT=12345 akka hello.world.Sample.HelloLauncher
て感じで、環境変数経由にする。
あと、実行時にクラスが見つかんないみたいに怒られた時は、とりあえずjarに固めて、akkaのdeployフォルダに置けば見つかるようになります。
package hello.world.Sample

import akka.actor.{ Actor, ActorSystem, Props }
import akka.kernel.Bootable

class HelloLauncher extends Bootable {
    val port = System.getenv("AKKA_PORT")

    val conf = ConfigFactory.parseString(s"""
      akka.actor.provider = akka.remote.RemoteActorRefProvider
      akka.remote.netty.tcp.hostname = 127.0.0.1
      akka.remote.netty.tcp.port = ${port}
    """.stripMargin).withFallback(ConfigFactory.load());    
    val system = ActorSystem("HelloSystem", conf)

    def startup = {
        system.actorOf(Props[Hello], "hello")
    }

    def shutdown = {
        system.shutdown()
    }  
}

class Hello extends Actor {
  def receive = {
    case msg: String =>
      println("Hello: msg = " + msg)
  }
}
つぎに、Helloにメッセージを投げる側のサンプル
env AKKA_PORT=12345 akka hello.world.Sample.HelloLauncher
env AKKA_PORT=23456 akka hello.world.Sample.HelloLauncher
のように、別コンソールで2つのmicrokernelを起動した前提で動作させる。
object HelloCaller {

import akka.actor.{ Actor, ActorSystem, Props }
    
  def main(args: Array[String]) {
    System.setProperty("akka.actor.provider", "akka.remote.RemoteActorRefProvider")
    // このポートはリモート側でsenderにメッセージを戻す時に使用する
    System.setProperty("akka.remote.netty.tcp.port", "10000")
    val system = ActorSystem("CallerSystem")
    val actor1 = system.actorSelection("akka.tcp://HelloSystem@127.0.0.1:12345/user/hello")
    val actor2 = system.actorSelection("akka.tcp://HelloSystem@127.0.0.1:23456/user/hello")
    actor1 ! "Hello,"
    actor2 ! "Hello,"
    Thread.sleep(1000)
    actor1 ! "World."
    actor2 ! "Another actor."
    Thread.sleep(1000)
    actor1 ! "Yeaaahhh!!!"
    actor2 ! "Hooooooo!!!"
    Thread.sleep(1000)
    system.shutdown
  }

}
なお、mainから呼ぶ側のlibraryDependenciesには、akka-kernel, akka-actor, akka-remote を追加しています。

[sacla] akkaのmicrokernelに引数を渡す

akkaのmicrokernelは、Actorを別プロセスで簡単にバンバン立てられるので、便利なんですけど、今のバージョン(akka 2.3.2)だと、引数を渡せないので困った。

自分はlinuxのコマンドに不慣れで、解決法を忘れそうなのでメモ。

まず、akkaのbinにパスが通っているとして、普通のmicrokernelの起動は
akka com.xxx.packageName.ClassName
でやるんだけど、引数を渡したい時に、
akka com.xxx.packageName.ClassName arg1 arg2
とかやっても、Bootableにargsを渡してくれない。(そういうインターフェースになっていない)
なんでかというと、akkaコマンドが、複数のパラメータが
akka Class1 Class2 Class3
なんてあったときに、3つのBootableを一気に立ち上げるというふうに使っているから(参照)っぽい。

で、解決としては、linuxのenvコマンド(環境を変更してプログラムを実行する)を使用してmicrokernelを
env AHO=xxx BAKA=yyy akka com.xxx.packageName.ClassName
のように起動し、Bootableの中で
  val aho = System.getenv("AHO")
  val baka = System.getenv("BAKA")
とやって引数を取得しました。

2014年2月28日金曜日

[scala] PlayFrameworkでJson文字列の取得で一瞬あせった

Play 使っててJsonの文字列取りたい時、
val jsonObj = ........ とかなんとかあった場合
ドキュメントとか世のブログとかでは、
Json.stringify(jsonObj)
とやれとのことなんだけど、何も考えずに
jsonObj.toString
ってやってて、後で、これでいいの?だめなの?
と疑問に思った。
ちょっと調べたらどうやらjavascriptでは違うらしいので、一瞬
{げっっ!!}
となったが、playのソース(JsValue)を見ると

override def toString = Json.stringify(this)

だってさ。取れるものは同じということで、安心したぜ。

2014年2月14日金曜日

[Java] マイナスの16進数文字列をLongに戻したい

javaで、マイナスの16進数文字列を数値に戻したい。ただそれだけなのに、はまってしまった。信じられん。ということで、運良く解決したのでメモ。
これ、絶対はまるやつ結構いると思うんだけどなぁ。javaめ。

まず、説明のため順序的にLong->16進数ね。
Long l = new Long(-1);
String s = Long.toHexString(l);
これ、当然sが"ffffffffffffffff"。 fが16個。これが欲しいのでOK。
で、今回のお題は、16進数->Longなので、とりあえずこうするわね。
Long rev = Long.parseLong(s, 16);
しかし、ここで実行時エラー。
{なぬーっ。そんなアホなーーっ。}
どうやら、parseLongは、sの中身を正の値だと決めてかかっているようで、"7fffffffffffffff"より大きい文字列を入れるとエラーになる仕様。
Long.decodeも試してみたが、同仕様。
{どうしよう。とほほ。}
ちなみに、
Long ll = Long.parseLong("-ff", 16);
とやると、-255になるんだそうな。誰が喜ぶんだそんな仕様・・・
で、なぜかいろいろググっても解決できなかったのだが、コードをごねごねいじってたらラッキーにも打開策を発見した。
答えは、
Long rev = (new java.math.BigInteger(s, 16)).longValue();
一度、BigIntegerにしてから、それをLongにする。見事revが-1になった。
ナイス、BigInteger.longValue()!!
こいつの仕様に助けられた。
あとは、勝手にlongValue()の仕様が変らないことを祈るだけだ。

2013年9月12日木曜日

scalaで調子に乗ってfuture{でスレッド量産してたらはまった

注・この記事は、scala2.10を使用しています。

scalaで、
import ExecutionContext.Implicits.global
とfuture使ってて、
「あれ? futureで作った別スレッドの処理がなんか始まらないことがあるんだけど・・・」
ということがある人のための記事です。

scala使ってて便利なのにスレッドを超簡単に作れるってのがあるんだけど、
import ExecutionContext.Implicits.global // どこか上の方でimport

future {
・ なんかいろいろ
・ なんかいろいろ
}
こんな感じね。

ただ単に、future{やると、エラーが出るんで、いろんなサイトのサンプル(こことかこことかここ。みんな良サイト) を参考に、先にimportしてscalaが持ってるデフォルトのExecutionContextであるExecutionContext.Implicits.global を利用するようにしてた。

ところが、調子に乗ってメインのスレッドでやりたくないことをfuture{でがんがん切り離して処理したんだけど、そのプログラムをコア数1のマシンで動かそうとしたら動かない。コア数2のマシンで動かしたらなんか動くけど、プログラムがよく固まる。

固まった状態ではほとんどCPUは食っていない。
ちなみに自分の開発マシンは、4core&ハイパースレッドで8スレッドだったのでその現象は出なかった。
{うげー、マルチスレッド周りの問題は追いかけにくくて嫌だなー・・・}
と思ったのだが、printlnとか入れてみるとどうやら処理がfutureの中身に来ていないようだ。

仕方ないのでサンプルを書いて自機(8スレマシン)で実験してみた。
import scala.concurrent._
import ExecutionContext.Implicits.global

object ThreadSample {
  def main(args: Array[String]) {
    val startTime = System.currentTimeMillis()

    for (idx <- 1 to 24) {
     future {
      println(f"idx = $idx%2d, time = ${System.currentTimeMillis() - startTime}%4d ms")
      Thread.sleep(1000)
     }
    }
    Thread.sleep(3000)
    println(s"end of main.     ${System.currentTimeMillis() - startTime} ms")
  }
}
実行結果
idx =  1, time =   73 ms
idx =  3, time =   73 ms
idx =  8, time =   73 ms
idx =  2, time =   73 ms
idx =  7, time =   73 ms
idx =  4, time =   73 ms
idx =  5, time =   73 ms
idx =  6, time =   73 ms
idx = 13, time = 1099 ms
idx = 16, time = 1099 ms
idx = 15, time = 1099 ms
idx = 10, time = 1099 ms
idx =  9, time = 1099 ms
idx = 14, time = 1099 ms
idx = 11, time = 1099 ms
idx = 12, time = 1099 ms
idx = 21, time = 2100 ms
idx = 23, time = 2100 ms
idx = 20, time = 2100 ms
idx = 19, time = 2100 ms
idx = 18, time = 2100 ms
idx = 17, time = 2100 ms
idx = 22, time = 2100 ms
idx = 24, time = 2100 ms
end of main.     3074 ms
{なんじゃこりゃ、8個ずつしか動いてないじゃん。}
sleepしたからって、次のthreadを処理してくれる訳じゃなく御丁寧にsleepが終わるまで次のスレッドを処理しないで待ってるではないか!
で、どうやらこの同時に動けるfutureの数がマシンのコア数によって変るので、コア数の少ないマシンで自分のプログラムを動かしてた時にfutureで切り離したthreadが実行されなかった模様。

何か他のやり方ないかな、と探していたら、spawnというものがあるようなのでこんなふうに変えてみたら
import scala.concurrent._
import scala.concurrent.ops.spawn

object ThreadSample {
  def main(args: Array[String]) {
    val startTime = System.currentTimeMillis()

 for (idx <- 1 to 24) {
     spawn {
      println(f"idx = $idx%2d, time = ${System.currentTimeMillis() - startTime}%4d ms")
      Thread.sleep(1000)
     }
    }
    Thread.sleep(3000)
    println(s"end of main.     ${System.currentTimeMillis() - startTime} ms")
  }
}
実行結果
idx = 16, time =   56 ms
idx = 23, time =   57 ms
idx =  7, time =   55 ms
idx =  8, time =   55 ms
idx = 21, time =   56 ms
idx = 12, time =   56 ms
idx = 14, time =   56 ms
idx =  5, time =   55 ms
idx = 15, time =   56 ms
idx = 20, time =   56 ms
idx = 19, time =   56 ms
idx = 11, time =   56 ms
idx =  3, time =   55 ms
idx = 13, time =   56 ms
idx =  2, time =   55 ms
idx = 10, time =   55 ms
idx =  1, time =   55 ms
idx = 17, time =   56 ms
idx =  4, time =   55 ms
idx = 18, time =   56 ms
idx = 24, time =   57 ms
idx =  9, time =   55 ms
idx = 22, time =   57 ms
idx =  6, time =   55 ms
end of main.     3058 ms
欲しかった結果になった。しかしコンパイル時に、こんな警告が出た。
[warn] /Users/xxx.scala:97: object ops in package concurrent is deprecated: Use `Future` instead.
[warn]   spawn {
[warn]   ^
{なにー、future使えだとー!?}
仕方ないのでもう少し調べてみると、ちゃんと理解せずに使っていた、
import ExecutionContext.Implicits.global
が元のスレッドの動作を制御していたようで、こいつを変更する必要があった。 しかし、ここで活躍するのが何とjavaのスレッド管理機能であるExecutorsってやつだった。(わかりやすい解説)
で、変更したコードがこれ
import scala.concurrent._
import java.util.concurrent.Executors

object ThreadSample {
  def main(args: Array[String]) {
    val startTime = System.currentTimeMillis()

    val pool = Executors.newCachedThreadPool()
    implicit val execctx = ExecutionContext.fromExecutorService(pool)
 for (idx <- 1 to 24) {
     future {
      println(f"idx = $idx%2d, time = ${System.currentTimeMillis() - startTime}%4d ms")
      Thread.sleep(1000)
     }
    }
    Thread.sleep(3000)
    println(s"end of main.     ${System.currentTimeMillis() - startTime} ms")
  }
}
実行結果
idx = 18, time =   60 ms
idx =  6, time =   59 ms
idx =  5, time =   59 ms
idx =  2, time =   59 ms
idx = 10, time =   59 ms
idx = 13, time =   60 ms
idx = 11, time =   60 ms
idx =  7, time =   59 ms
idx =  8, time =   59 ms
idx =  9, time =   59 ms
idx =  4, time =   59 ms
idx =  3, time =   59 ms
idx = 17, time =   60 ms
idx = 19, time =   60 ms
idx = 15, time =   60 ms
idx = 21, time =   60 ms
idx = 22, time =   61 ms
idx = 12, time =   60 ms
idx = 16, time =   60 ms
idx = 24, time =   61 ms
idx = 23, time =   61 ms
idx = 20, time =   60 ms
idx =  1, time =   59 ms
idx = 14, time =   60 ms
end of main.     3062 ms
で、うまくいった。
Executor辺りのことは、上で紹介したサイトにも書いてあったのだが、そこを読んだときは、ExecutionContext.Implicits.globalで動くんだからいいじゃん。とちゃんと理解してなかった。

ちなみにPlayFrameworkとかいじっててakkaが使える状態のときは、javaのExecutorsを呼ばずに
 val system = ActorSystem("xxxxx")
 import system.dispatcher

future {
・ なんかいろいろ
・ なんかいろいろ
}
で、ActorSystemのdispatcherをインポートしてあげれば、ExecutionContext.Implicits.globalを使わないでいい感じになった。

2013年6月4日火曜日

PlayFramework2.1.1環境で、ScalaからJNAへセットしたコールバックが戻ってこなくなる件

先日の投稿で、scalaからJNAのコールバックを設定するところを
object Tekito {
 lib.setFunction(idx, new JnaCallback {
  def onMessage(msg: String) = println("onMessage " + idx + ": " + msg)
 })
}
こんな感じで書いていたんだけど、実際の環境で上の書き方でコールバックをセットした後、しばらくすると不規則なタイミングでコールバックが返ってこなくなるケースが多発して困っていた。
で、なんとなく次のように変えてみる。
object Tekito {
 def callback = new JnaCallback {
  def onMessage(msg: String) = println("onMessage " + idx + ": " + msg)
 }
 lib.setFunction(idx, callback)
}
これでも状況は変わらず、しばらくするとコールバックが返ってこなくなる。
ただ、コールバックのセットをし直すと、コールバックがまた返ってくるようになることを発見したので、症状が出る前と後で
println(Tekito.callback)
してみると、
正常時
models.Tekito$$anon$1@5ca99bc
コールバックが返ってこなくなった後
models.Tekito$$anon$1@3833089c
{ぬおーっ!アドレス変ってるじゃないかー!}
c側でコールバックを保持しているのは、ただの関数ポインタなので、これではコールバックは返ってこない。
{これ、どうしてくれようか}
と思ったが、素直に下のようにしてみたら
object Tekito {
 val callback = new JnaCallback {
  def onMessage(msg: String) = println("onMessage " + idx + ": " + msg)
 }
 lib.setFunction(idx, callback)
}
こうしてみたら、Tekito.callbackのアドレスが固定されてコールバックが安定して返ってくるようになった。
私は、JVMやscalaのメモリ管理を詳しく知らないので、これで本当にアドレスが固定されるのか確信がないのだが、とりあえず今のところはアドレスが変ってしまうケースはなくなった。
なので、
「scalaからJNAのコールバックをセットする時は、コールバックをvalに入れておく」
をお勧めします。

追記 2013/06/19
後で気が付いたのですが、コールバックオブジェクトを変数に入れておかないと、そのオブジェクトはどこからも参照されていない状態になりGCが動いた時に掃除されてしまうので、コールバックが迷子になる。が正解っぽいです。
コールバックが迷子になるタイミングが不規則だったのは、GCが動くタイミングが不規則だからで、c側でコールバックを保持していてもJVMのメモリとは当然関係ないので、GCに片付けられてしまうということのようです。

2013年5月30日木曜日

PlayFramework2.1.1で動かすakka ActorとWebSocketのサンプル

scala、PlayFramework、akka、WebSocketと初めてづくしで、自分的に、はまりポイント満載だったので、サンプルをあげておきます。
akkaとWebSocketが両方入っていて、サンプルとしては焦点が絞れてないけれど、二つ記事を書くのが面倒なので、そのまんまです。

環境は、PlayFramework2.1.1でscalaもakkaもplayに付属のもの。

まず、初心者がscala、PlayFramework、akkaで一番はまるポイントは、これらは、現在バリバリ進化中の技術なので、ググった情報が古くなってしまっていることが多い、ということ。たった半年前の記事でさえ、古いこともあるので注意が必要です。もちろん、この記事もすぐに古くなりそう。

コードは、PlayのアプリケーションとActorを書いたやつの2ファイル。

・ Playのアプリケーション側
package controllers

import play.api._
import play.api.mvc._
import models._
import play.api.libs.iteratee.Concurrent.Channel
import play.api.libs.concurrent.Akka
import akka.util.Timeout
import akka.actor._
import akka.pattern.ask  // ? の使用に必要!!
import scala.concurrent._
import scala.concurrent.duration._  // 5 seconds の表現に必要
import ExecutionContext.Implicits.global // FutureのonSucceessを使うのに便利
import play.api.libs.iteratee._
import play.api.Play.current // Akka.system を使うのに必要


object AppHelloActor extends Controller {
 val actor = Akka.system.actorOf(Props[HelloActor], name = "helloactor")

 def index = Action {
  Ok(views.html.test("test page."))
 }
 
 def actorWebSocketsSample() = WebSocket.using[String] {
 request =>
 
  def onStart: Channel[String] => Unit = {
     channel => // このchannelを取っておくとclientにいろいろ送信できる
   println("actorWebSocketsSample unicast onStart")
   implicit val timeout = Timeout(5 seconds) // この下の?の隠れ引数のためにここで宣言?! こういう言語は初めてだぜ。
   val future = actor ? "world"
   //val future = actor ? 3.14159 // 上の行をコメントにして、こっちを生かすとonFailureでtimeoutを取れる
   //val future = actor.?("world")(Timeout(5 seconds))
   future.onSuccess {
   case res: Array[String] =>
    res.foreach(value => println("RESULT = " + value))
    channel.push(res.mkString(" ")) // clientにメッセージ送信
    channel.eofAndEnd()
   }
   future.onFailure {
   case e =>
    println("future.onFailure - " + e.getMessage())
    channel.eofAndEnd()
   }
  }

  def onError: (String, Input[String]) => Unit = {
     (message, input) =>
   println("actorWebSocketsSample unicast onError " + message)
  }
  def onComplete = println("actorWebSocketsSample unicast onComplete")
  
  val enumerator = Concurrent.unicast[String](onStart, onComplete, onError)
  
  val iteratee = Iteratee.foreach[String] {
  rcvData =>
   // 今回は、上のonStartで処理終了しちゃってるから何もしてないけど、
   // ここでWebSocketクライアントから受信したデータを処理する。
  } mapDone {_ => println("Disconnected")}
  
  (iteratee, enumerator)
 }
 
}
・ 次は、Actor側
package models

import akka.actor.Actor

class HelloActor() extends Actor {

 def receive = {
  case s: String =>
   sender ! Array(s"Hello, $s.", "Bye Bye.")
     case _ =>
      // timeoutさせたいので、何もしない
 }

}
以上でコードはおしまい。以下、解説、はまったポイント等をだらだらと・・・・

上のコード、importがだらだらとあって省略していないけど、実はここが今回の最重要ポイント。
「webのサンプルと環境が同じはずなのに、コンパイル出来ねぇぜ。大体、こんなメソッドはドキュメントにも載ってないぞ」
とかいうscalaでありがちな状況は、importが足らないのが原因ということが多い。

akkaのActorを使う上で、最初参考にしたのが、
Scalaで並行処理#2 – AkkaのActorを使う
のページで、とてもわかりやすいサンプルと解説の素晴らしい記事。
で、!(投げっぱなし) !!(同期) !!!(非同期)を駆使してやるぞと意気込んでみたものの、!!と!!!のメソッドが見当たらない。
おまけにActor側のself.replyも見当たらない。どうやら仕様変更があった模様。
{まじすか、勘弁してくださいよ先生・・・}
とめげかけるが、akkaの公式ページに解答を発見。(Use With Actorsの項)
どうやら、Actorから、もしくはActorへの通信は、!(返事いらない) と?(返事欲しい)の2種類になったっぽい。たしかにこの表記の方がシンプルでわかりやすい。
前の!!のように、同期で返事を待ちたい場合は、?でもらえるFutureを使って、
val result = Await.result(future, timeout.duration).asInstanceOf[String]
みたいにすれば、結果来るまで待ってられるし、前の!!!のように非同期で結果待ちの時は上のサンプルのように、onSuccessなりonCompleteなり使えばいい。
Futureの使い方についてとても参考になったのは、
Starlight -Little Programmer’s Diary-  Scala 2.10.0 Futures and Promises
のページです。サンプル多くてわかりやすいです。

あと、Actor側のself.replyもsender !になった。
{でも、!ってActorへのメッセージじゃなかったっけ?}
と思ったが、なんと?メソッドが、Actorにメッセージを投げる時に内部で一時的にActorを作ってくれるから、Actorからの戻しも!でいいとのこと。すげー。シンプルだ。
この、scalaとakka開発陣の仕様変えるな、こら」という声に負けない攻めの姿勢は大好きです。
scalaのことだから、前の書き方でも、たぶん何かimportするだけで動くんだと思う(知らないけど)、これができるのも仕様変更に踏み切れる理由なんだろう。implicit conversion恐るべし。

ただ、上の?メソッドも、ActorRefにも、ScalaActorRefにも見当たらない、これは、コード10行目の import akka.pattern.ask すると使えるようになる。
このimportで、ActorRefがAskableActorRefに、implicit conversionで変身するので?が使用可能になる。これは、確かにわかりにくいが、scalaの拡張性やコードの簡潔さを支えているのが、良くも悪くもimplicit conversionだということがよくわかった。

あと、はまったのが、?メソッドに必要な、隠し変数timeout。scalaには、implicit parameterといって、メソッドに必要な引数を普通に引数として渡すのではなく、周りのコードの引数と同じ型のimplicit宣言された値から取ってくるという、ぶっ飛んだ機能があって、こいつを初めて見たので何のことやらさっぱりわからんかった。
確かにこれなら、せっかく?メソッドが演算子的にかっこ良く書けるのに、timeoutをどう渡すんだよという難問を解決できる。
明示的にtimeoutを渡したい時は、
val  future = actor.?("world")(Timeout(5 seconds))
みたいにもできるが、見にくいし、かっこ悪い。
implicit parameterについては、ひしだまさんのページに書かれた一連のサンプルが非常にわかりやすいです。そのページに限らずひしだまさんのページは、考え抜かれた短いサンプルが大量にあって、scalaを使うには欠かすことができません。scalaの神様です。

で、疲れてきたんだけど、WebSocketにまだ触れてないなこれ。
参考にしたページは、
Auto-Saving ... Done  Play Framework 2.1のWebSocketで一対一通信

server側からclientに送信するには、onStartが呼ばれる時にやってくるchannelを使うので、WebSocketがつながっている間は、こいつを大事に取っておきましょう。
client側から何かを受信するたびに、Iteratee.foreach の中に入ってくるので、ここで簡単に受信データを処理できます。

scalaをいじっていて感じたのは、scala開発陣の、いかに簡潔なコードを作成可能な言語にするかという執念じみたものでした。その執念に、これからも期待したいです。

2013年5月24日金曜日

PlayFramework2.1.1環境で、ScalaからJNA経由でcを呼んだりコールバックセットしたりした

PlayFrameworkを使ったシステムをscalaで書いていて、cを呼びたくなった。
呼びたくなったというよりは、裏でc++でものすっごく激しい計算させるので呼ばざるを得ない。
google先生に聞いても、俺の検索力ではサンプルっぽいものは見つけられかった。
{くぅ。scalaも始めたばっかりで分けわからんし、どうしてくれようか・・・}
とコードをごねごねしていたらなんか動いたので、忘れる前に投稿してみた。

できてしまえば、かなりシンプルになった。もちろん一見シンプルに見えるのはscalaパワーのおかげ。
やりたかったことは、あるクラス(HelloData)の各インスタンスのもつクロージャ(?)をc側にコールバックとして登録して、それぞれのインスタンスからcのライブラリを呼んだ時に、ちゃんとコールバックが呼び出したインスタンスのクロージャに返ってくるようにするということ。

ソースのファイルは5つ
  • Application.scala Play側のmain的な入り口
  • HelloData.scala テキトーなクラス。こいつのインスタンスからcを呼び出したり、ここのコールバックに戻したり、いろんなデータを持ってみたりしてみたい。
  • HelloJna.scala JNAとの接合部分
  • index.scala.html web出力のview部分
  • JnaInterface.cpp c側のソース
以降の部分、ほとんどはコードですが、流れとしては、
Playからindexが呼ばれる→HelloDataのインスタンス作成→HelloDataのコンストラクタ内でコールバック登録→HelloDataのメソッドからcの関数呼び出し→c側からコールバック呼び出し→コールバック内でprintln→c側関数が文字列を戻して終了→webにメッセージ出しておしまい

じゃ、Application.scalaから
HelloDataのインスタンスを3つ作って、ちゃんと区別されて返ってくるか実験する。
package controllers

import play.api._
import play.api.mvc._
import models._

object Application extends Controller {
 def index = Action {
  val data = List(new HelloData(), new HelloData(), new HelloData())
  val msg = data.map(_.callJnaHello)
  
  Ok(views.html.index(msg))
 }
}

HelloData.scala
scala側とc側でデータの連携をとるために、ユニークなidxを持たせてある。
2013/6/4修正 callbackをvalに入れるようにしました。詳しくはこちらの投稿
package models

class HelloData() {
 private val lib = HelloJna.lib
 private val idx = HelloData.cnt;
 private val callback = new JnaCallback {
  def onMessage(msg: String) = println("onMessage " + idx + ": " + msg)
 }
 HelloData.cnt += 1
 
 lib.setFunction(idx, callback)
 
 def callJnaHello: String = lib.getHello(idx)
}

object HelloData {
 var cnt = 0;
}

HelloJna.scala
ライブラリファイルを変更してコンパイルし直しても、一度JVMを再起動(playを再起動すればいい)しないとライブラリをloadし直さないので注意(ここはまった)。JVMには、一度loadしたNativeのLibraryをunloadする機能はないらしい。←あったら誰か教えて
なお、途中のlibHelloJNA.soは、もっと下のJnaInterface.cppからつくったSharedObjectです。
package models

import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Callback

object HelloJna {
 val lib = Native.loadLibrary("/xxx/yyy/xxx/Debug/libHelloJNA.so", classOf[HelloJnaTrait]).asInstanceOf[HelloJnaTrait]
}

trait JnaCallback extends Callback {
 def onMessage(msg: String): Unit
}

trait HelloJnaTrait extends Library {
 def getHello(idx: Int): String
 def setFunction(idx: Int, callback: JnaCallback)
}

index.scala.html
cの関数から戻ってきた文字列を出すだけ
@(messages: List[String])

<!DOCTYPE html>
<html>
  <body>
 @for(msg <- data-blogger-escaped-messages="" data-blogger-escaped-p="">
 @msg 
} </body> </html>
JnaInterface.cpp
実験なのでidxが255超えたらどうすんのって突っ込みはなし
他の部分はc++で書いていてもここのところはcの関数的に宣言する必要あり(たぶん)。
あと、一番下にiostream閉じてる変なタグが見えたら、SystaxHilighterのバグなので気にしないこと。
#include 
using namespace std;

static int g_cnt = 0;
static char g_msg[256];
static void (*g_callback[256])(const char *msg);

extern "C" const char *getHello(int idx) {
 char callbackMsg[256];

 sprintf(callbackMsg, "msg to callback %d", idx);
 g_callback[idx](callbackMsg);

 sprintf(g_msg, "Hello JNA idx = %d, cnt = %d.", idx, ++g_cnt);
 return g_msg;
}

extern "C" void setFunction(int idx, void (*func)(const char *msg)) {
 g_callback[idx] = func;
}

下は、ブラウザに表示された結果
Hello JNA idx = 0, cnt = 1.

Hello JNA idx = 1, cnt = 2.

Hello JNA idx = 2, cnt = 3.

次は、printlnの出力
コロン前の数字の値は、各HelloDataインスタンス内にあるので、各インスタンス内のクロージャにちゃんとコールバックが戻っているのが確認できる。
onMessage 0: msg to callback 0
onMessage 1: msg to callback 1
onMessage 2: msg to callback 2