2013年9月5日木曜日

IntelliJでScalaの継続プラグインを設定

以下のScala実践プログラミングの限定継続のサンプルをIntelliJで動かす時にハマったのでメモ。

import scala.util.continuations._

object Main extends App {
  val answer = reset {
    val x = shift {k: (Int => Int) =>
      println("A")
      println("k(10) = " + k(10))
      println("B")
      8
    }
    println("x = " + x)
    x * 2
  }
  println("answer = " + answer)
}

普通にコンパイルすると以下のエラーとなる。

java.lang.NoSuchMethodError: scala.util.continuations.package$.shift(Lscala/Function1;)Ljava/lang/Object;

コンパイルするためには継続プラグイン(continuations.jar)の置き場所をIntelliJで指定しないといけない。

continuations.jarはbrewやsbt(ivy)で取ってきたディレクトリを探すと見つかると思います。

Project Structure > Modulesで対象モジュールのScalaオプションを選択すると以下の画面が表示されます。
Compiler Pluginsでcontinuations.jarを追加し、”Enable continuations”にチェックを入れるとScalaの限定継続のコードがコンパイルできるようになります。

2013年9月4日水曜日

iTunesのプレイリストをAndroidに同期する方法比較

MacのiTunesのプレイリストをAndroidに同期したかったので、方法を調査してみました。

現時点で以下の4つの方法を見つけたので、それぞれ比較してみます。

Google Play Music + Play Musicアプリ

Google Play MusicはPC上のライブラリを丸ごとGoogleのサーバに保存し、ブラウザやPlay Musicアプリからストリーミング再生が可能になる仕組みです。サーバには2万曲保存可能です。

iTunesとの連携も可能で、iTunesのプレイリストもそのまま同期されます。

しかし、スマートプレイリストは対象外です。

また、日本ではまだこのサーバに保存する機能は使えないらしいので、日本から使用するならプロキシ等必要になります。

Google Play Musicの詳しい説明は以下を参考にして下さい。

Google Play musicの使い方 - NAVER まとめ

Google Play Music - Google Play の Android アプリ

iSyncr + Rocket Music Player

本命。スマートプレイリスト、レーティング、歌詞の同期、と僕の要求に全て答えてくれました!

iSyncrはiTunesのプレイリストをAndroidに同期するソフトで、Rocket Music PlayerはiSyncrを開発しているとこと同じ所が開発している音楽プレイヤーです。

iSyncrで同期したプレイリストは、PowerampやDoubleTwistなど他のプレイヤーでも再生できるのですが、この場合iTunesのレーティングはプレイヤー側では表示されません。

レーティングも同期したければRocket Music Playerを使うしかなさそうです。

プレイヤーは無料版と有料版がありますが、普通に再生するだけなら無料版で十分だと思います。

iTunesの音楽をスマホで楽しもう!!(iSyncr編) | スマートフォンはgooスマホ部

JRT Studio’s iSyncr Syncs iTunes Libraries To Android Devices. | JRT Studio

ロケットミュージックプレイヤー - Google Play の Android アプリ

AirSync + doubleTwist

doubleTwistというPCの音楽プレイヤーにiTunesのライブラリをインポートし、それをAndroidのdoubleTwistプレイヤーと同期する方法もあります。

別にdoubleTwistという別のプレイヤーをPC上で使いたくもないし、AndroidのdoubleTwistは歌詞表示ができないようなので今回は試していません。

TuneSync

TuneSync Sync Android™ with iTunes over Wifi for Windows or OS X

iSyncrと類似のソフトです。

できることはiSyncrとほぼ同じですが、レーティングの同期ができませんでした。

これなら安いiSyncrでいいんじゃないかな?

まとめ

項目 Google Play Music iSyncr TuneSync
スマートプレイリスト同期
再生回数同期
レーティング同期
歌詞同期
価格 無料 $3.99 $5.99

僕のニーズだとiSyncr + Rocket Music Playerの組み合わせが現時点で最強。

2013年9月3日火曜日

Scalaの継続モナド(Continuationモナド)を理解する

Scala実践プログラミングに出てきた継続モナド(Continuationモナド)を理解するに手こずったので、備忘録として簡単に説明を残します。該当ページはp.304,305です。

Scala実践プログラミング―オープンソース徹底活用

以下は継続モナドの定義です。

class Cont[A, B](m: (A => B) => B) {
   // for内包表記用のメソッド。mapとflatMap。
   def map[C](f: A => C): Cont[C, B] = 
  new Cont(k => m(x => k(f(x))))

   def flatMap[C](f: A => Cont[C, B]): Cont[C, B] = 
  new Cont(k => m(x => f(x).run(k)))

   // モナドを実行するメソッド
   def run(k: A => B): B = m(k)
 }

この継続モナドを使用したサンプル(ほぼ本のまま)は以下の通りです。本ではwork.run(u => u)の部分がありませんが、この行がないとモナドの中身が実行されません。

object Main extends App {
  type TClosable = { def close(): Unit }

  def using[C <% TClosable, T](h: C)(work: C => T): T = {
    try {
      work(h)
    } finally {
      h.close()
    }
  }

  def withFile(file: String): Cont[BufferedSource, Unit] = {
    new Cont(k => {
      using(Source.fromFile(file)) {r: BufferedSource => k(r)}
    })
  }

  val work =
    for {
      foo <- withFile("foo.txt")
      bar <- withFile("bar.txt")
    } yield {
      for(line <- foo.getLines.zip(bar.getLines)) println(line)
    }

  work.run(u => u)
}

Contクラス(継続モナド用クラス)では関数リテラルと型パラメータが多用されており、さらにContが再帰的に構築されるようになっているためかなり複雑で理解しづらいです。理解しなくても問題なく利用できますが、やっぱりちゃんとどういう仕組で動いているか理解したいですよね!

ということで以下では上記のサンプルがどういう仕組みになっているのかコードを追って確認したいと思います。

まずfor内包表記を変換規則に従って変換すると以下のコードになります。

val work = withFile("foo.txt").flatMap(foo =>
  withFile("bar.txt").map(bar =>
    for(line <- foo.getLines.zip(bar.getLines)) println(line)
))

ではコードを順に追っていきましょう。

まずwithFile(“foo.txt”)の部分でContのインスタンスが生成されます。
型推論で省略された型を明記すると、withFileはCont[BufferedSource, Unit]を返すことからnew Contした型はCont[BufferedSource, Unit]です。

new Cont(k => {
  using(Source.fromFile(file)) {r: BufferedSource => k(r)}
})

class Cont[A, B](m: (A => B) => B)なのでAがBufferedSource、BがUnitとなります。
Contのコンストラクタパラメータであるmはややこしいですが、A型を引数にとりB型を返す関数を引数に取りB型を返す関数です(日本語の方がわけわからないですね^^;)
従って、以下のkはBufferedSource => Unitとなる関数です。k(r)の戻り値はUnitとなるため、mの定義に合致しています。
mは(BufferedSource => Unit) => Unitとなる関数で、実体は以下のコードになります。

k => {
  using(Source.fromFile(file)) {r: BufferedSource => k(r)}
}

次にwithFile(“foo.txt”)で作成したContインスタンスに対してflatMapが呼ばれています。
flatMapの引数fに該当するのは以下のコードです。

foo =>
  withFile("bar.txt").map(bar =>
    for(line <- foo.getLines.zip(bar.getLines)) println(line)
  )

flatMapを呼び出したのはCont[BufferedSource, Unit]なので、fはBufferedSource => Cont[C, Unit]と推測できます。

Cは何型でしょうか?コードを追っていくとfor(line <- foo.getLines.zip(bar.getLines)) println(line)の行の型がCとなりそうです。従ってCはUnitでfはBufferedSource => Cont[Unit, Unit]となります。

flatMapの中でも新たなContインスタンスが生成されています。

def flatMap[C](f: A => Cont[C, B]): Cont[C, B] =
  new Cont(k => m(x => f(x).run(k)))

Contのコンストラクタパラメータとなるmの型から推論すると、kはC => B、xはAとなります。
新たに作られたContのmは関数で中身はk => m(x => f(x).run(k))です。右側のmはwithFile(“foo.txt”)で作られたContインスタンスのmです(中身はk => { using(Source.fromFile(file)) {r: BufferedSource => k(r)}})。

この時点では関数オブジェクト(mやf)が複数作られているだけで、中身は一切実行されていないことに注意して下さい。

workはflatMapで新たに作られた上記のCont[Unit, Unit]が代入されます。
そしていよいよモナドの実行です。

work.run(u => u)

runメソッドはk: A => B、この場合はUnit => Unitとなる関数を引数に取るので、何もしない関数を上記では渡しています。

runではm(k)が実行されます。
最初はworkに代入されたCont、つまりflatMapで作成されたContインスタンスフィールドのm関数(以降便宜上flatMap#mと呼びます)が、上記の何もしない関数kを引数に実行されます。
flatMap#mの中身は以下です。

k => m(x => f(x).run(k))

ここでようやく右側の関数定義部分が評価されます。
右側のmはwithFile(“foo.txt”)で作られたContインスタンスのm(以降便宜上foo#mと呼びます)であることに注意して下さい。

x => f(x).run(k)はfoo#mの引数となるのでBufferedSource => Unitという関数オブジェクトとなります。

関数本体に当たるf(x).run(k)の部分はこの時点では実行されません。

この関数を引数kとしてfoo#mが呼び出されます。
foo#mは以下の通りです。

k => {
  using(Source.fromFile(file)) {r: BufferedSource => k(r)}
}

ここでようやく”foo.txt”部分のusingが呼ばれ、中でBufferedSourceを引数としてk(r)が呼ばれます。

kは上記のflatMapで定義された以下の関数オブジェクトです。

x => f(x).run(k)

ややこしいですが右側のkは最初に渡した何もしない関数です。xはr: BufferedSourceに相当し、ようやくここでf(x)が評価されます。
fはflatMapに渡された引数(便宜上flatMap#f)で以下のコードであることを思い出して下さい。

foo =>
  withFile("bar.txt").map(bar =>
    for(line <- foo.getLines.zip(bar.getLines)) println(line)
  )

f(x)を評価するとさっきと同じようにwithFile(“bar.txt”)が実行されてさらに新たなCont[BufferedSource, Unit]が作られます。
その後今度はmapメソッドが実行されここでもflatMapと同様に新たなCont[Unit, Unit]が作られます。
mapの引数であるf(便宜上bar#f)の中身は以下のコードです。

bar => for(line <- foo.getLines.zip(bar.getLines)) println(line)

この後f(x).run(k)の評価に戻ります(このfはflatMap#f)。

f(x)の戻り値はCont[Unit, Unit]なので、このインスタンスに対してrun(k)が呼ばれます。

kは何もしない関数です。

run(k)を実行するとmapで作成したCont[Unit, Unit]のmが呼ばれます。従って、mは以下のコードです。

k => m(x => k(f(x)))

右側のmはwithFile(“bar.txt”)で作成したインスタンスのm(便宜上bar#m)です。

fはbar#fです。

さっきと同様x => k(f(x))という関数オブジェクトを引数にbar#mが呼ばれます。

bar#mは以下の通りです。

k => {
  using(Source.fromFile(file)) {r: BufferedSource => k(r)}
}

kはx => k(f(x))です(右側のkは何もしない関数)。
fはbar#fです。

従って”bar.txt”のBufferedSourceを引数としてやっと以下のbar#fが呼ばれます。

bar => for(line <- foo.getLines.zip(bar.getLines)) println(line)

kは何もしない関数なので、k(f(x))を実行しても何も起こりません。

これでwork.run(u => u)の呼び出しも終了です。

ふぅ、疲れた。。。