コミケ告知

サークル活動の詳細は circle タグの記事へ。
2015年9月13日日曜日

tryする末尾再帰関数が普通には書けないScala

TL;DR

try~catch内で値を返す再帰関数を書くには、scala.util.Tryを使う。

説明


Scalaでは末尾再帰関数は最適化してくれるし、それをtailrecアノテーションでチェックすることができます。(最適化が効かない場合にコンパイルエラーが出るようになる)
@scala.annotation.tailrec
def f(n: Int): Int = {
  ...  // (略)
  f(n - 1)
}
ただこれ、try~catchを付けて、そこで値を返そうとするとうまくいかないんですよね~
@scala.annotation.tailrec
def f(n: Int): Int = {
  try {
    ...  // (略)
    f(n - 1)
  } catch {
    // どの場合も返す型は正しいものとする
  }
}
scalac Main.scala
Main.scala:3: error: could not optimize @tailrec annotated method f: it contains a recursive call not in tail position
  def f(n: Int): Int = {
エラーメッセージも正しくなくてなんだこりゃって感じですが、stackoverflowに回答がありました。

何度も回答がEditされていますが、その一番下。try~catch節ではなく、scala.util.tryを用いることで回避できるようです。
@scala.annotation.tailrec
def f(n: Int): Int = {
  scala.util.Try(例外の出る処理内容) {
    case Success(処理の返り値) => ...
    case Failure(e: 例外の型) =>
    case Failure(e: 例外の型) =>
  }
}

知っていて最初からこれで書けば面倒くさいことはないんだけど…Scala、あちこち珍妙なとこあるよな。

2015年9月1日火曜日

Akkaのsenderは値ではない

久々に使うと忘れがちなのでメモ。

AkkaのActorは、メッセージ受信時にはその送信元を示すActorRefをsenderで取れます。メッセージを送り返したり、デバッグ時に表示させたり、何かと便利です。
def receive = {
  case x => sender ! x  // echo
}
しかしこのsender、Futureで使おうとすると望み通りの挙動をしません。表示させてみると、送信元とは違う値になっています。(deadLetters宛になる)
Future {
  sender ! myFunc(x)  // NG
}
こういうときは、一時的に別の定数に入れて使うか…
val dst = sender
Future {
  dst ! myFunc(x)  // OK
}
最後に結果を返したいだけなら、pipeToを用いるか。
Future {
  myFunc(x)
} pipeTo sender

senderとは何なのか

senderはcontext.senderを呼び出すようになっていて、さらに追っていくとActorCellの実装に辿り着きます。
  final def sender(): ActorRef = currentMessage match {
    case null                      ⇒ system.deadLetters
    case msg if msg.sender ne null ⇒ msg.sender
    case _                         ⇒ system.deadLetters
  }
これは変数ではなくメソッドであるので、sender と記述したところに制御が到達したタイミングで、初めて処理が行われます。ちなみにvar currentMessageを弄っているのも同じActorCell内で…
  final def invoke(messageHandle: Envelope): Unit = try {
    currentMessage = messageHandle
    cancelReceiveTimeout() // FIXME: leave this here???
    messageHandle.message match {
      case msg: AutoReceivedMessage ⇒ autoReceiveMessage(messageHandle)
      case msg                      ⇒ receiveMessage(msg)
    }
    currentMessage = null // reset current message after successful invocation
  }
  //  以下略…
まずcurrentMessageがセットされたのち、システム側で処理されるAutoReceivedMessage以外であれば、receiveMessageからActorのおなじみreceiveメソッドの処理へ。終わったら戻ってきてnullに戻します。(普通にnull使うんだ… 初期値が _ だからnullで統一してるんだな)
Futureの処理がどこかで走るときには、このメッセージ受信による処理駆動パスとは異なるので、 currentMessage == null であり、 sender()はdeadLettersを返すのですね。

カッコ省略のデメリット?

仕組みが分かっていない段階で誤った使い方をしてしまった原因のひとつが、引数なしメソッドのカッコが省略されていることでした。サンプルでも基本的に省略されているから、そもそも最初はメソッドだと思っていなかったよ…(´・ω・`)
記述が簡潔!の思想に走りすぎるとデメリットもありますよ、ということで。