技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Scala2.10.3 から Redis 弄ってみた

scala-redis

https://github.com/debasishg/scala-redis

Maven のURLも無いし、ドキュメントはそのままビルドしてそうな感じだったので、clone して publpish-local して使った。

Apache2 ライセンスなので、ソース取り込むのが正解かもしれない。jar 配布って出来たかは知らんけど。

import com.redis._

object Sample extends App {
  val client = new RedisClient("localhost", 6379)
  val isSet = client.set("SampleString", "SampleMessage")

  println(isSet)

  val result = client.get("SampleString")

  println(result)
}

まず、set の内部実装が

  def set(key: Any, value: Any)(implicit format: Format): Boolean =
    send("SET", List(key, value))(asBoolean)

なので、おおよそ Format を実装すれば複雑な型もいけると推測できる

これのデフォルト実装が

object Format {
  def apply(f: PartialFunction[Any, Any]): Format = new Format(f)

  implicit val default: Format = new Format(Map.empty)

  def formatDouble(d: Double, inclusive: Boolean = true) =
    (if (inclusive) ("") else ("(")) + {
      if (d.isInfinity) {
        if (d > 0.0) "+inf" else "-inf"
      } else {
        d.toString
      }
    }

}

class Format(val format: PartialFunction[Any, Any]) {
  def apply(in: Any): Array[Byte] =
    (if (format.isDefinedAt(in)) (format(in)) else (in)) match {
      case b: Array[Byte] => b
      case d: Double => Format.formatDouble(d, true).getBytes("UTF-8")
      case x => x.toString.getBytes("UTF-8")
    }

  def orElse(that: Format): Format = Format(format orElse that.format)

  def orElse(that: PartialFunction[Any, Any]): Format = Format(format orElse that)
}

なので、Format(f:Any => String) なシリアライザを書けば割とどうとでもなると。っていうか Generics 使いなさいよと思わなくない。

で、 get は

  def get[A](key: Any)(implicit format: Format, parse: Parse[A]): Option[A] =
    send("GET", List(key))(asBulk)

で、parse が入ってくるわけだが、Parse の定義は下記

object Parse {
  def apply[T](f: (Array[Byte]) => T) = new Parse[T](f)

  object Implicits {
    implicit val parseString = Parse[String](new String(_, "UTF-8"))
    implicit val parseByteArray = Parse[Array[Byte]](x => x)
    implicit val parseInt = Parse[Int](new String(_, "UTF-8").toInt)
    implicit val parseLong = Parse[Long](new String(_, "UTF-8").toLong)
    implicit val parseDouble = Parse[Double](new String(_, "UTF-8").toDouble)
  }

  implicit val parseDefault = Parse[String](new String(_, "UTF-8"))

  val parseStringSafe = Parse[String](xs => new String(xs.iterator.flatMap{
    case x if x > 31 && x < 127 => Iterator.single(x.toChar)
    case 10 => "\\n".iterator
    case 13 => "\\r".iterator
    case x => "\\x%02x".format(x).iterator
  }.toArray))
}

class Parse[A](val f: (Array[Byte]) => A) extends Function1[Array[Byte], A] {
  def apply(in: Array[Byte]): A = f(in)
}

バイナリで返ってくるんですね。

いずれにせよバイト配列を、デシリアライズできればよい。

配列的に突っ込むのは別IFらしく

  client.lpush(key, value1, value2, ...)(implicit Format)
  client.rpush(key, value1, value2, ...)(implicit Format)
  client.lpop(key)(implicit Format, Parser)
  client.rpop(key)(implicit Format, Parser)
  

もう突っ込まないよ

object Sample extends App {
  val client = new RedisClient("localhost", 6379)

  import serialization._
  import Parse.Implicits._

  client.hmset("hashMapSet", Map("field1" -> 32, "field2" -> 64))

  println(
    client.hmget[String, Int]("hashMapSet", "field1", "field2")
  )
}

ただし、Map[String, Any] はうまく走ってくれなかった。まぁ仕方ないか、、、。

非同期取得もあるけど、基本そのまま動く。

scala-redis-nb

何かまだアルファ版もいいとこらしい。

https://github.com/debasishg/scala-redis-nb

name := "RedisSample"                                                             

version := "1.0"

scalaVersion := "2.10.3"

resolvers += "spray" at "http://repo.spray.io/"

libraryDependencies ++= Seq(
  "net.debasishg" %% "redisreact" % "0.3",
  "io.spray" %%  "spray-json" % "1.2.5"
)

として、公式ほぼ写経で

import akka.actor.ActorSystem
import akka.util.Timeout
import com.redis.RedisClient
import scala.concurrent.duration._

case class User(id:Int, name:String, age:Int)

case class Group(id:Int, name:String, users:List[User])

object Sample extends App {
  implicit val system = ActorSystem("redis-client")
  implicit val executionContext = system.dispatcher

  val client = RedisClient("127.0.0.1", 6379)

  import spray.json.DefaultJsonProtocol._
  import com.redis.serialization.SprayJsonSupport._

  implicit val childClass = jsonFormat3(User)
  implicit val groupClass = jsonFormat3(Group)

  val sampleValue = User(2, "delick", 28)

  client.set("key", sampleValue)

  import scala.concurrent.Await
  implicit val timeout = Timeout(5000)

  val result = Await.result(client.get[User]("key"), 5 seconds)
  println(result)

  system.shutdown()
}

としてみた所、Nullpo落ち。

[error] (run-main) java.lang.NullPointerException
java.lang.NullPointerException
        at akka.pattern.AskableActorRef$.ask$extension(AskSupport.scala:136)
        at com.redis.api.StringOperations$class.set(StringOperations.scala:23)
        at com.redis.RedisClient.set(RedisClient.scala:26)
        at Sample$delayedInit$body.apply(Sample.scala:24)
/* 中略 */
[trace] Stack trace suppressed: run last compile:run for the full output.

呼び出しに関して、明らかに素の Akka にないような呼び出ししてるし、import も書かれてないから呼び出し方が間違ってるのかもしれない。

いずれにせよ、情報無さすぎで使わない判断。