sealed abstract case classがすごい

sealed abstract case classなるものがある。別のことを調べてたらこちらの記事(のコメント欄)で見掛けて感動してしまった。

軽く触れると、newできなくなり、case classによって自動で生成されるcopyメソッド、そしてコンパニオンオブジェクトのapplyメソッドが生成されなくなる、というもの。その上でcase classの他の特性を持つ。

これ、インスタンスの生成に関する手段のみが綺麗に潰されているので、バリデーションを挟むファクトリだったりを強制できるのが嬉しい。

私自身は今まで以下のようにしていた。

trait Person {
  def name: String
  
  def age: Int
}

private case class PersonImpl(name: String, age: Int) extends Person

object Person {
  def apply(name: String, age: Int): Person = {
    // ...
    PersonImpl(name, age)
  }
}

こんな風にtraitでインタフェースのみ定義し、その実装をcase classにする。これでequalsメソッドなどは自動で定義してくれる。で、traitの方のコンパニオンオブジェクトで生成してあげる。もちろん実装であるcase class自体は隠しちゃう。

しかしこれ、unapplyメソッドなどは自前で定義しなくちゃならないし、toStringメソッドは実装クラス(この場合はPersonImpl)のものなので、想定した結果にならないと、微妙なとこもあった。

ので、まあケースに応じはするわけだが、sealed abstract case classは有用そうだなと思う。

Scalaマクロのerrorとabortの違い

Scalaのマクロで、コンパイルエラーにしてしまうときは Context.error を使う。

def someMethod_impl(c: Context)(arg: c.Expr[String]): c.Expr[Unit] = {
  // ...

  if (cond) {
    c.error(c.enclosingPosition, "a message")
  }

  // ...
}

でもこれが微妙に使いづらくて、なぜなら戻り値が Unit だから。Unit だと:

val _arg = arg.tree match {
  case Literal(Constant(value: String)) =>
    value
  case _ =>
    c.error(c.enclosingPosition, "a message")
}

とかが出来ません。この場合変数 _argString になってほしいのですが、実際は StringUnit の共通の親である Any になってしまうわけです。

で、登場するのが Context.abort 。こちらは戻り値が Nothing になっているので、上記のようなケースで上手く動作します。

val _arg = arg.tree match {
  case Literal(Constant(value: String)) =>
    value
  case _ =>
    c.abort(c.enclosingPosition, "a message")
}

// 変数 `_arg` はちゃんと `String`

ケースに応じて使い分ければ良さそうですね。

Scala dynamics + macros

Type Dynamic

Scalaはメソッドなどの有無をコンパイルの時点で解決しますが、Dynamic traitを使うことによって動的にすることが出来ます。

// Dynamic traitを使うために必要。コンパイラオプションに `-language:dynamics` を渡してもOK
import scala.language.dynamics

object Adder extends Dynamic {
  // ...
}

こうするだけ。で、オブジェクト(この場合は Adder オブジェクト)に存在しないフィールドやメソッドを指定すると、特定のメソッドが呼ばれます。特定のメソッドは4種類あり、ケースに応じて呼び出されるメソッドが変わります。

  • フィールドの参照: selectDynamic
  • フィールドへの代入: updateDynamic
  • メソッド呼び出し: applyDynamic
  • 名前付き引数を伴ったメソッド呼び出し: applyDynamicNamed

という具合。試しに applyDynamic メソッドを定義してみます。

object Adder extends Dynamic {
  def applyDynamic(name: String)(value: Int): Int = {
    if (!increments.isDefinedAt(name)) {
      throw new IllegalArgumentException(s"$name is not supported")
    }

    val increment = increments(name)

    value + increment
  }

  val increments: Map[String, Int] = Map(
    "one" -> 1,
    "two" -> 2,
    "three" -> 3
  )
}

で実際に使ってみるとこんな感じになる。

println(Adder.one(100))   // => 101
println(Adder.two(100))   // => 102
println(Adder.three(100)) // => 103

本来 Adder オブジェクトには one メソッドなどが定義されていないわけですが、Dynamic traitを使用しているので、applyDynamic メソッドが呼ばれ、その中の処理が実行されるわけです。

先に書いたとおり、他にもメソッドがあるわけですが、これについては割愛。Dynamic traitのドキュメント読むだけでもなんとなく分かるんじゃないかなあと。

マクロ

Type Dynamic便利ですね。しかしこのケースにおいては Adder.four(100) などと呼ぼうものならエラーになります。実行時エラーです。Type Dynamic使わなければコンパイルエラーなのに。

だからこそType Dynamicとも思うのですが、場合によってはコンパイルエラーに出来ます。マクロと併用します。順を追って Adder.four(100)コンパイルエラーになるようにしてみましょう。

まず Adder.applyDynamic メソッドがマクロの呼び出しになるように書き換えます。

import scala.language.dynamics

// `macro` キーワードを使うために必要。コンパイラオプションに `-language:experimental.macros` を渡してもOK
import scala.language.experimental.macros

object Adder extends Dynamic {
  def applyDynamic(name: String)(value: Int): Int = macro AdderMacro.applyDynamic
}

使うのは macro キーワードで、このあとにマクロの実態となるメソッドの識別子を渡します。元のメソッド名と合わせる必要は特になく、macro Nyan.nyan とかでも大丈夫です。

で、実態側を定義する。

import scala.reflect.macros.blackbox.Context

object AdderMacro {
  def applyDynamic(c: Context)(name: c.Expr[String])(value: c.Expr[Int]): c.Expr[Int] = {
    import c.universe._

    val Literal(Constant(numName: String)) = name.tree

    if (!increments.isDefinedAt(numName)) {
      c.error(c.enclosingPosition, s"$numName is not supported")
    }

    val increment = Literal(Constant(increments(numName)))

    c.Expr[Int](q"$value + $increment")
  }

  val increments: Map[String, Int] = Map(
    "one" -> 1,
    "two" -> 2,
    "three" -> 3
  )
}

AdderMacro.applyDynamic がマクロの実態なわけですが、まずシグネチャをマクロ用のものにする必要があります。ひとつ目の引数リストで scala.reflect.macros.{blackbox,whitebox}.Context を受け取るようにし、あとは元のメソッドと同じ引数リストとなります。ただし、c.Expr で包んであげます(または c.Tree にする)。

これで Adder.one(100) を呼び出すと、Type Dynamicによって Adder.applyDynamic("one")(100) になり、マクロを通じてコンパイル時に 100 + 1 に置き換えられます。なので全体的にASTを操作する処理になります。

で、ポイントは c.error のところ。これでコンパイルエラーになります。実際に sbt console で試してみます。

[info] Starting scala interpreter...
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_131).
Type in expressions for evaluation. Or try :help.

scala> Adder.one(100)
res0: Int = 101

scala> Adder.four(100)
<console>:12: error: four is not supported
       Adder.four(100)

意図した通りにコンパイルエラーになっています。と、まあこんな感じでType Dynamicしつつも、マクロを介して妥当かどうかをチェックしてあげられます。

ちなみにマクロの実態の方を先にコンパイルしてあげる必要などがあるので、sbtではプロジェクトを分けたりする必要があります。詳細はサンプルを参照してみてください。

実例

ScalikeJDBCのコードに良い実例があります。

import scalikejdbc._

case class PersonRecord(name: String, age: Int)

object PersonTable extends SQLSyntaxSupport[PersonRecord] {
  // ...
  
  // `column` がType Dynamicになっており、`SQLSyntaxSupport` に与えたクラスの
  // プライマリコンストラクタの引数のみ許可している。よって以下はコンパイルが通る
  column.name
  column.age
  
  // これは通らない。以下のようにコンパイルエラーとなる。
  //   PersonRecord#birthday not found. Expected fields are #name, #age.
  column.birthday
  
  // ...
}

こんな感じで型パラメータに与えられたクラスの情報を元にチェックできたり。ソースは下記の模様。

比較的安全に悪そうなこと出来たりしますが、乱用するとパッと見でどんなコードが生成されるか分かりづらいので、お約束ながら用法用量を守っていきたいですね。

パッケージの依存を単方向にするための拡張メソッド

DDDの話をするとしよう*1

IDDD 第9章 モジュールにおいて、モジュール(Scalaだとパッケージ)の依存は極力減らせ。現実的には難しいが、単方向にしたり、親子関係にあるならば双方向を許可する、ということが述べられている。

しかしこれめっちゃ難しい。集約が別集約のファクトリになる場合、単方向という制限はだいたい破綻する。

// userモジュールの集約
class User(id: UserId) {
  def createTask(): Task = // Taskを返すため、taskモジュールに依存
    new Task(TaskId.generate(), id)
}

// taskモジュールの集約
class Task(id: TaskId, ownerId: UserId) // ownerIdとしてUserIdを持つため、userモジュールに依存

そもそもこういった関係のものにファクトリを持たせるな、という話な気もしてくるが、だからといって避けては通れないときもあると思う。ならば親子関係にすれば良いのか、というとそうでもない。そうしちゃうとほとんどがuserモジュールの子になってしまう。たぶん。

そういうときに拡張メソッドを使うことも出来るな、という話をしたいわけです。

さきほどの例だと user.User をこうする。

class User(id: UserId)

要はファクトリを削る。これでtaskモジュールへの依存はなくなる。

で、taskモジュールに以下のようなのを書く。

import user._

package object task {
  implicit class TaskCreator(user: User) {
    def createTask(): Task =
      new Task(TaskId.generate(), user.id)
  }
}

user.User から task.TaskCreator への変換を定義し、そこに拡張メソッドとしてファクトリを持たせてあげる。こうするとファクトリのある場所がtaskモジュールに移動するので、依存が単方向に収まる。

ちなみに使うときは:

import user._
import task._

val user = createUser()
val task = user.createTask() // import task._ をしていないとcreateTaskメソッドが使えない

のように、両方のモジュールをimportしてあげる必要がある。が、むしろこれはtaskモジュール中のオブジェクトを使いますよ、という意味になるので、むしろ好都合だと思ってる。

実践で用いてはいないのだけど、「あれ、こうすればいいんじゃ?」と思った話でした。デメリットは実践してないから知らない。

*1:FGOやったことないです

Y8 2017 spring in Shibuyaでお話した #y8spring

久し振りに登壇しました。

内容はメディロムで開発しているウェブAPI*1を、どんな風にScalaで書いているか、どんな判断を下しているか、などといったものです。今の仕組みもそろそろこなれてきたし、次(イベントソーシング、CQRSとかな!)に行く前にいっちょまとめとくか、的なノリです。

登壇自体は午後からでしたが、午前の開会式から居たので、すべてのトークを聞いていたのですが、ベストトーカー賞でもあったid:motchangさんのお話は過去似たようなことをやった身としては気が気じゃない感じで、おもしろつらくて楽しかったですね。他の方のトークも学びや再確認できることがあって、楽しかったなーーー。

こんな楽しい機会を企画/運営してくれたスタッフのみなさん、会場を提供してくれた方々にありがとうのお気持ちをですね、お伝えします。また参加したいですねー。

*1:スライド中には明記していませんが、iOS/Android/ウェブブラウザアプリのためのJSONをおしゃべりするAPIのことです

ScalaのJSONライブラリcirceの使い方

ScalaJSONライブラリと言うと、json4sあたりが有名なのかと思います(私感)。

が、json4sはリフレクションを使うので、何かと避けたい方も多いかと思う。

ということで、circeを使ってみましょう。

circe概要

circeはScalaJSONライブラリです。

読み方

公式サイトに書かれています。

たしかにサーシーと読めるが、circeで調べるとWikipediaにヒットするので、キルケー呼びを推したい所存。通じれば何でもいいと思う。

特徴

元々はArgonautをforkして作られたものなので、それに似た特徴を持つ。

  • 純粋関数型のライブラリ(リフレクションを使っていない)
  • catsを内部的に使用
  • shepelessによるJSONオブジェクト ⇔ データ型の相互変換
  • Scala.js対応
  • Argonautにある複雑なオペレーター(--\とか)はすべて削除されている

各モジュールの用途

circeはモジュールに分かれている。よく使うモジュールはcirce-core, circe-parser, circe-genericの3つ。

circe-core

コアなクラス(Json, Encoder, Decoder, etc…)が含まれている。よく使うパッケージはio.circeとio.circe.syntaxのふたつ。

circe-parser

文字列をパースしてJSONオブジェクトに変換してくれるやつ。パーサーはjawnを使用していて、実態はcirce-jawnモジュールにある。

io.circe.parserパッケージのみがあり、parse関数やdecode関数を提供している。

circe-generic

よく使うパッケージはio.circe.generic.autoパッケージ。これを取り込んでおくと、JSONオブジェクト ⇔ case classおよび主要な型(String, Int, etc…)の相互変換を可能にしてくれる。

このモジュールによる変換は、変換が可能な場合コンパイルを通してくれる優れもの。反対に言うと、コンパイルが通らない場合は変換できない型が含まれている。その場合はその型のEncoder/Decoderを定義しておけばちゃんとcirce-genericモジュールで変換が可能になる。

よってコンパイルエラーとなった場合、ちゃんとすべての型が変換できるか確認すると良さ気。

その他モジュール

circe-opticsモジュールはJSONオブジェクトの操作(深い階層にある値を書き換えたりできる)を行えたりと、その他いろいろある。が、今回は割愛。

エンコード

JSONライブラリの主要な使い道はJSON文字列からデータ型への変換と、データ型からJSON文字列の変換を行うという2つだと思う。よってその2つの変換の方法を紹介。

データ型 → JSONオブジェクトの変換はエンコードと呼び、asJsonメソッドで行う。

asJsonメソッドの戻り値はio.circe.Jsonのオブジェクトとなり、このオブジェクトのnoSpacesメソッドやspaces4メソッドなどで文字列に変換できるので、HTTPのレスポンスなどとして返す場合はそのメソッドを用いる。

import io.circe.syntax._       // これがないと`asJson`メソッドが使えない
import io.circe.generic.auto._ // これがないと`io.circe.Encoder[Person]`を自動定義してくれなくて、`asJson`メソッドに渡すEncoderがないと怒られる

case class Person(name: String)

val person = Person("takkkun")

val json = person.asJson

println(json.noSpaces) // => {"name":"takkkun"}

asJsonメソッドはio.circe.Encoder[A]をimplicit parameterで受け取るようになっている。circe-genericモジュールのとこでも書いたように、case classと主要な型であればio.circe.generic.autoパッケージを取り込むだけでそれらの型のEncoderが使えるようになるので、上記のコードはちゃんと動作する。

もし定義されていない型のEncoderが必要になる場合は、自前で定義する。

import io.circe._
import io.circe.syntax._
import io.circe.generic.auto._

import org.joda.time.LocalDate

case class Person(name: String, birthday: LocalDate)

val person = Person("takkkun", new LocalDate(1987, 1, 15))

// `io.circe.Encoder[org.joda.time.LocalDate]`が定義されていないのでコンパイルエラー
person.asJson

implicit val localDateEncoder = new Encoder[LocalDate] {
  final def apply(a: LocalDate): Json =
    Json.fromString(a.toString("yyyy-MM-dd"))
}

val json = person.asJson

println(json.noSpaces) // => {"name":"takkkun","birthday":"1987-01-15"}

Encoderの定義方法いろいろ。以下はすべてio.circe.Encoder[org.joda.time.LocalDate]を定義している。

// `io.circe.Encoder[A]`トレイトを継承してその場で定義
implicit val localDateEncoder =
  new Encoder[LocalDate] {
    final def apply(a: LocalDate): Json =
      Json.fromString(a.toString("yyyy-MM-dd"))
  }

// `io.circe.Encoder[A].contramap`を使用(Aの箇所は変換先のJSON型を指定)
implicit val localDateEncoder: Encoder[LocalDate] =
  Encoder[String].contramap(_.toString("yyyy-MM-dd"))

// `io.circe.Encoder.encodeString.contramap`を使用(Stringの箇所は変換先のJSON型を指定、encodeIntとか)
implicit val localDateEncoder: Encoder[LocalDate] =
  Encoder.encodeString.contramap(_.toString("yyyy-MM-dd"))

デコード

JSONオブジェクト → データ型の変換はデコードと呼び、parse関数およびio.circe.Jsonまたはio.circe.HCursor (io.circe.ACursor)のasメソッドで行う。JSONオブジェクトにせず、JSON文字列から直接データ型へ変換する場合はdecode関数を使う。

import io.circe.parser._       // これがないと`parse`関数および`decode`関数が使えない
import io.circe.generic.auto._ // これがないと`io.circe.Decoder[Person]`を自動定義してくれなくて、`as`メソッドに渡すDecoderがないと怒られる

case class Person(name: String)

val jsonString = """{"name": "takkkun"}"""

parse(jsonString).right.flatMap(_.as[Person]) match {
  case Right(person) => println(person) // => Person(takkkun)
  case Left(error)   => println(error)
}

decode[Person](jsonString) match {
  case Right(person) => println(person) // => Person(takkkun)
  case Left(error)   => println(error)
}

asメソッドおよびdecode関数はio.circe.Decoder[A]をimplicit parameterで受け取るようになっている。エンコードのときと同様、case classと主要な型であればio.circe.generic.autoパッケージを取り込むだけでそれらの型のDecoderが使えるようになるので、上記のコードはちゃんと動作する。

もし定義されていない型のDecoderが必要になる場合は、自前で定義する。

import io.circe._
import io.circe.parser._
import io.circe.generic.auto._

import org.joda.time.LocalDate

case class Person(name: String, birthday: LocalDate)

val jsonString = """{"name": "takkkun", "birthday": "1987-01-15"}"""

// `io.circe.Decoder[org.joda.time.LocalDate]`が定義されていないのでコンパイルエラー
decode[Person](jsonString)

implicit val localDateDecoder = new Decoder[LocalDate] {
  final def apply(c: HCursor): Decoder.Result[LocalDate] =
    c.as[String].right.map(LocalDate.parse)
}

decode[Person](jsonString) match {
  case Right(person) => println(person) // => Person(takkkun,1987-01-15)
  case Left(error)   => println(error)
}

Decoderの定義方法いろいろ。以下はすべてio.circe.Decoder[org.joda.time.LocalDate]を定義している。

// `io.circe.Decoder[A]`トレイトを継承してその場で定義
implicit val localDateDecoder =
  new Decoder[LocalDate] {
    final def apply(c: HCursor): Decoder.Result[LocalDate] =
      c.as[String].right.map(LocalDate.parse)
  }

// `io.circe.Decoder[A].map`を使用(Aの箇所は変換元のJSON型を指定)
implicit val localDateDecoder: Decoder[LocalDate] =
  Decoder[String].map(LocalDate.parse)

// `io.circe.Decoder.decodeString.map`を使用(Stringの箇所は変換元のJSON型を指定、decodeIntとか)
implicit val localDateDecoder: Decoder[LocalDate] =
  Decoder.decodeString.map(LocalDate.parse)

まとめ

circeによる相互変換はだいたい書いたやつで事足りる。と思う。少なくとも私が実際にjson4sからcirceに乗り換えたときには。

Argonautも使ってみたが、毎回すべての型に対してEncoder/Decoderを書かなければいけないので、なかなか面倒だった。その点circeは必要な分だけ書けばいいので、これが思いの外嬉しい。実際に書いたらよく分かった。

その上でリフレクション使っていないので、なんというか安心感があって助かる。今後はcirceを使っていこうかな。

Akka HTTPを使ってみる。Akka Streamsにもちょっと触れるよ

Akka HTTPとはAkkaの上でHTTPサーバーを実現したものです。HTTPクライアントとしての機能を持っていたり、WebSocketのサポートもしています。

sprayという似たものもありますが、spray/sprayには「長らくメンテしてないよ。Akka HTTPに置き換えらているよ」とアナウンスされています。ですので、素直にAkka HTTPを使います。

ふたつのAPI

Akka HTTPのサーバーサイドにはレベルの異なるふたつのAPIがあります。

低レベルAPILow-Level Server-Side API

Akka HTTPはAkka、もっと言うとAkka Streamsを利用して書かれているのですが、低レベルAPIはそのAkka Streamsが剥き出しになっており、よりHTTPに近い方(コネクションとか)にも触れられます。こちらを利用することによってWebSocketによるアクセスを捌くことも出来ます。

Akka Streamsが剥き出しにはなっていますが、それらのあれこれをやってくれて、ちょっと短く書けるように、bindAndHandleSyncというメソッドがあります。そのメソッドを使うと以下のように書けます。

import akka.actors.ActorSystem
import akka.streams.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse, Uri }
import akka.http.scaladsl.model.HttpMethods._

implicit val system = ActorSystem()

implicit val materializer = ActorMaterializer()

val handle: HttpRequest => HttpResponse = {
  case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
    HttpResponse(entity = "pong")
}

Http().bindAndHandleSync(handle, "localhost", 8080)

bindAndHandleAsyncメソッドもありますが、こちらはHttpRequest => Future[HttpResponse]と、Futureを使うようになります。

高レベルAPIHigh-Level Server-Side API

高レベルAPIは低レベルAPIをラップし、ルーティングの機能を備えたものです(よく見るとこのページのURLも"routing-dsl"になっていますね)。その分低レベルAPIに比べて細やかなことは出来ません。

高レベルAPIでは、akka.http.scaladsl.server.Directivesのメソッド群と、bindAndHandleメソッドを使います。

import akka.actors.ActorSystem
import akka.streams.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._

implicit val system = ActorSystem()

implicit val materializer = ActorMaterializer()

val route =
  path("ping") {
    get {
      complete("pong")
    }
  }

Http().bindAndHandle(route, "localhost", 8080)

bindAndHandleメソッドの第1引数に渡すのはFlow[HttpRequest, HttpResponse, Any]という型の値で、このFlowというのはAkka Streamsのものです。ですので、高レベルAPIにおいてもAkka Streamsが隠蔽されるわけではないのですが、akka.http.scaladsl.server.Directivespath, get, completeはすべてこのオブジェクトのメソッド)を用いるので、低レベルAPIに比べるとほとんど意識しなくなります。

反対に、低レベルAPIにおいてFlowを用いる箇所ではakka.http.scaladsl.server.Directivesで組み立てたものを渡せたりもします。

低レベルAPIを使ってみる

高レベルAPIakka.http.scaladsl.server.Directivesが提供するメソッド群を使ったルーティングの記述はややクセがありますが(sprayに触れてた人はほぼ同じだから馴染みやすいらしい)、見て分からないほどのものではないと思います。が、これは他のウェブフレームワークでもやってくれそうなもの。というわけで、低レベルAPIの方を詳しく見ていきます。

その前に軽くAkka Streamsを説明。

  • Source: 何かしらの入力を発生させる。始端。ひとつの出力チャンネルを持つ
  • Sink: Sourceから発生した入力が最終的に行き着く先。終端。ひとつの入力チャンネルを持つ
  • Flow: 入力を受け取って、別の形に変換し出力する。入力チャンネルと出力チャンネルをひとつずつ持つ

より突っ込んだ話であれば:

あたりが詳しかったです。

Akka HTTPの低レベルAPIはこのAkka Streamsを利用しているので、それをわざとらしく使用したコードが以下です。

import akka.http.scaladsl.Http
import akka.stream.scaladsl.Sink

val source = Http().bind("localhost", 8080)

source.runWith(Sink.foreach { connection =>
  println(s"New connection is accepted: ${connection.remoteAddress}")
  // handle connection
})

bindAndHandleSyncメソッドやbindAndHandleメソッドではなく、bindメソッドを使用すると、戻り値はSource[Http.IncomingConnection, Future[Http.ServerBinding]]となります。ポイントはSourceを返している点です。

このSourceはHttp.IncomingConnectionを発生させるSourceです。ですので、このあとにFlowやSinkを繋げばHttp.IncomingConnection、つまりHTTPによってアクセスされた場合のコネクションを得ることが出来るわけです。

source.runWithメソッドにSinkを渡すと実行されるので、HTTPコネクションを受け取るSinkを渡してあげればOKです。

ただ、Sinkは入力の行き着く先ですので、これだけではHTTPコネクションを受け取って終わりになってしまいます。何の処理もしていません。8080ポートで接続を受け付けるようになっただけです。

ですので、connection.handleWithメソッドを使います。

import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse, Uri }
import akka.http.scaladsl.model.HttpMethods._
import akka.stream.scaladsl.{ Sink, Flow }

val source = Http().bind("localhost", 8080)

source.runWith(Sink.foreach { connection =>
  println(s"New connection is accepted: ${connection.remoteAddress}")

  connection.handleWith(Flow.fromFunction {
    case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
      HttpResponse(entity = "pong")
  })

  // Http().bindAndHandleSync(...) の第1引数に渡すものと一緒
  // connection.handleWithSyncHandler(handler)

  // Http().bindAndHandleAsync(...) の第1引数に渡すものと一緒
  // connection.handleWithAsyncHandler(handler)
})

handleWithメソッドは第1引数にFlow[HttpRequest, HttpResponse, Mat]Matの説明は割愛)を受け取ります。想像に難くないかもしれませんが、HttpRequestを入力として受け取り、HttpResponseを出力として返すFlowのことであり、そのFlowでHTTPリクエストを処理してHTTPレスポンスを返してやってね、ということです。

これでHttp.IncomingConnectionを受け取った後、あらたにHttpRequestを受け取りHttpResponseを返すというふたつのストリームによって、HTTPサーバーを実現します。

ちなみにコメントでも書きましたが、Http.IncomingConnectionにはhandleWithSyncHandlerメソッドとhandleWithAsyncHandlerメソッドも定義されていて、これはHttp()で使えるbindAndHandleSyncメソッドおよびbindAndHandleAsyncメソッドにて実現できることをHttp.IncomingConnectionにて実現するものです。

とまあこんな風にHTTPリクエストなどなどを扱えます。高レベルにHTTPを隠蔽するなら他の選択肢もありますが、HTTPコネクションのレベルで何かしてやる場合、Akka HTTPはひとつの選択肢だなーと思いました。

型推論ふりかえり

型推論、よく聞きますね。よく聞くのでWikipediaにも型推論のページがあります。

この仕組みの恩恵は案外自然と享受しているものですが、機能を提供することライブラリの開発においては知らないと始まらない前提のようなものであると最近思います。が、自然と享受しているので「あーはいはい」となんとなくの理解だったりします。主に私が。

なのでどう考えればいいかを順に辿っていきます。

  • 型推論アルゴリズムの話はしません。人間がどう考えるかをなぞるだけです
  • コードはすべてScalaですし、型推論の考え方も私がScalaを通じて学んだものです。他の言語では保証できません

型推論をしないケース

型推論の話をする前に、まったく型推論しないケースを考えてみます。以下のようなケースはそうです。

val name: String = "takkkun"

変数nameの型はStringで、String型の値である"takkkun"を代入しているので、まあ普通のコードです。ただやたら丁寧なだけですね。

型推論とは「型に関する情報が欠けている箇所を補うもの」である

私は特別型推論に詳しいわけではないのですが、型推論が何をしてくれるものかと言うと、型に関する情報が欠けている箇所を補ってくれるものだと思っています。例えば以下のケース。

val name = "takkkun"

型推論をしないケースと比べると、変数nameの型が指定されていません。つまり型に関する情報が欠けています。ですので型推論が働き、右辺の"takkkun"String型の値であるので、変数nameString型だな、となります。難しい話じゃないですよね。

型の情報が欠けるケースは代入先だけかと言われるとそんなことはありません。代入元(右辺)に型の情報がないこともありえます。型パラメータを使っている場合です。以下はまたまた型推論しないケース。

val lover: Option[Person] = Option.empty[Person]

型推論を使うと:

val lover = Option.empty[Person]

とも書けるし:

val lover: Option[Person] = Option.empty

とも書けます*1

前者は変数name"takkkun"を代入するのと同じケースです。後者について詳しく見ていきます。まずOption objectのemptyメソッドの定義を見てみます。

def empty[A] : Option[A] = None

型パラメータAを取っていますね。なのでOption.emptyと呼び出すと、やはり型の情報が欠けることになるわけです。というわけで型推論の出番。以下のように考えると分かりやすいかなーと思います。

  1. 代入先の変数loverの型はOption[String]である
  2. empty[A]メソッドの戻り値はOption[A]である
  3. empty[A]メソッドの戻り値をOption[String]にすると型が一致するので、AStringとする

おお、AStringと解決できましたね。こんな風に求める側の型(変数の型, 関数の仮引数の型, 関数の戻り値の型, etc…)をJavaではターゲット型と呼ぶそうですが、そのターゲット型から推論するケースですね。

具体例

以下は型推論が働いていて「おお、ナイス」と個人的に思ったケースです。他にもきっとあります。

関数を代入するケース

val intToString: Int => String = { i =>
  i.toString
}

変数側にInt => String型を指定しない場合、仮引数iの型が不明となるため、val intToString = { i: Int => ... }と書かなければならくなります。「まあ別にそれでもいいじゃん」と言ってしまえばそうです。が、このケースでは仮引数であるiに対してtoStringメソッドを呼んでいるだけですので:

val intToString: Int => String = _.toString

と書けます。この書き方にすると、そもそもiという仮引数が現れなくなるため、代入先の方に型を指定して推論させる必要が出て来ます。

当然ですが:

val lover: Option[Person] = Option.empty

lover.foreach { person =>
  // do something
}

というケースでも、仮引数personに型を指定しなくても大丈夫なのは、Option[Person]型のforeachメソッドがPerson => Unit(正確にはUnitじゃありませんが割愛)となり、そこから型推論できるためです。「変数への代入」も「実引数として与える」のも同じ推論が働くと思っていいと思います。

ScalikeJDBC

RDBとあれこれする場合はよくScalikeJDBCを使っているんですが、このときもよくこんな書き方(というかscalikejdbc-mapper-generatorによって生成される)をします。

object Person {
  def apply(p: ResultName[Person])(rs: WrappedResultSet): Person =
    new Person(
      id    = rs.get(p.id),
      name  = rs.get(p.name),
      lover = rs.get(p.lover),
      ...
    )
}

rs.getの箇所ですが、このgetメソッドは型パラメータAを受け取り、それが戻り値となるようになっています。ですので、キーワード引数によって与えられる先の仮引数の型からAが何か推論されています。受け取る側の型を気にせずgetメソッドを並べれば良いので、すっきりしていますね。

で、さらにTypeBinder[A]が求まり、という話になってくるのですが、それは型クラスの話なので今回は割愛します。

私がナイスと思った具体例、代入先から代入元を推論するケースばかりですね。

まとめ

実際のところ型推論は便利です。記述がすっきりします。変数の型指定や無名関数の仮引数の型指定、関数が求める型パラメータの指定などを省略できます。

しかしどのケースにおいても重要なのは「型に関する情報が欠けている箇所を補う」ために型推論は働き、その推論の元となる型は必ずどこかに指定があります。推論は既にある事実がないことには始まりませんからね。

型推論は案外付きまとうものですが、人の頭でなぞれば分かるよ、怖くないよという気付きと振り返りでした。

*1:ちなみにこのケースだと、変位指定のおかげで代入先と代入元、どちらにも型の情報が無くてもコンパイルは通りますが、それはまた別のお話なので今回は端折ります

YAP(achimon)C::Asia Hachioji 2016 mid in Shinagawaに行ってきた

7月2日(土)、3日(日)の2日間開催されたYAP(achimon)C::Asia Hachioji 2016 mid in Shinagawa(以下ヤパチー)に行ってきました。

yapcasia8oji-2016mid.hachiojipm.org

トークした

実はこういうイベントに参加するの初めてで、まートークしたことも当然ないです。が、同僚のしんぺいさん(id:nkgt_chkonk)に誘われ、「応募する〜する〜」とか雑な返事をし、社内リポジトリに:

f:id:takkkun:20160705162740p:plain

こういうIssueが立てられ、「いっちょやってやるか」と雑な意識を奮い立たせてですね、応募しました。で、採択されました。

発表の内容はこんな感じ。

speakerdeck.com

普段会社でやっていることを発表しただけです。が、まとめるとなるとそれなりにパワーがいるもので、結構尻込みしたりするものです。そういう意味でしんぺいさんからの後押し(業務命令じゃないぞ!いやほんとうに)だったり、「楽しいことやるんだぞ!」という雰囲気があったのはとても良かったと思います。

なんというか、ややエモ話になるんですが、やったことないと大丈夫かなアハ〜ンとか思ったり、怯えたりとかで、見送る、見送り続けるってよくある話だと思うんです。ですが、同時に「このままでいいのかな」みたいな焦りもあるわけです。人と話すたびに「なんだこれ知らない……。知らなさすぎることがありすぎる……。大丈夫なのかこのままで私……」みたいなね。

ただそのまま何もしないと、そう思い続けるだけで。最悪なのは1, 2日経つとそんな感情まで忘れてしまうところですよ。適度に褒められずにやっていけるほどマッチョでもないし、かといって何もしないと褒められないしで、要は人に見える形で行動を起こせみたいな自己啓発本みたいなことを言うわけですが。

とまあそんな中途半端なところに火を点けられたので、よかったです。やってよかった!!!!!!ばんざい!!!!!!!これからも熱量は上げていきたい所存。

ちなみに発表の内容に対して、発表後や懇親会で聞かれたことのひとつに「これ後からフィールド(プロパティ)の内容書き換えたら意味なくね?(要約)」があるんですが、これ後でエントリーにします。このブログに書くかもしれませんし、社のブログに書くかもしれません。ここに書こうかと思ったが、ちょっと量が多くなりそうなんだ!

もうひとつトークした

当日はほとんど飛び入りトラックが行われる部屋に居ました。飛び入りトラックは当日までタイムテーブルが空っぽで、当日「これを喋るぞ!喋らせろ!」とかいう要請の元タイムテーブルを埋め、その時間が来たら発表してもらう、ってやつなんですが、そこでもトークしました。まあただの漫談なんですけど。なのでスライドはありません。

内容はしんぺいさんと「Scalaとか設計とか」について話すというもので、とか言いつつ大体は「immutableとmutableの差による設計への影響」みたいなところが焦点だった気がします。なんというか、いつも会社で話していることの延長線ですね。

スタッフした

ほとんどを飛び入りトラックの部屋で過ごしたのは、そこのスタッフだったからです。もちろんスタッフ経験も初めて。初めて尽くしですね。

他の部屋の雰囲気はそんな掴めていないのですが、飛び入りトラックの部屋はそれらに比べて大分規模が小さく、その分トークする人とそれを聞く人の距離が近くて良い雰囲気でした。特に id:kksg さんのErgoDoxの話は楽しかった。実際に触らせてもらえたし、触ったら全然今までの感覚が通用しないし、それが面白すぎるし、マウスのくだり(マウスからの入力に対してErgoDoxで「マウスのカーソル動かせますよ」「動かせんの!?」というやつ)はコントかよって感じで笑ったし、本人は訪問販売とか言ってましたが、まんまと欲しくなりました。いや、本当に買おうかな……。

まとめ

まとめというか、だいたい「圧倒的感謝」みたいな話になって、「おいおいあいつ多幸感に包まれてそのまま……」みたいに思っちゃうんですが、本当に感謝は尽きなくてですね。

主催のuzullaさん、会場を提供してくれたMicrosoftさん、スタッフさん、トークを聞きに来てくれた方々。そういう方々に感謝をお伝えしたいです。

それとお前好きすぎるだろと言う話なんですが、しんぺいさんにも感謝を伝えたいと思います。もしやこれはヤパチー行ってきたエントリーに偽装したラブレターかな?

とにかく!楽しかった!最高!また次(「の機会」という安全側に倒した表現はせず、是非やってほしい、手伝っていくぞ、というスタンス)も参加するぞ!

PlayStation 3が欲しかったのでその欲望を満たしてみたら想像以上に満たされた

PlayStation 3とか買ってもやりたいタイトルあるの?」と言っていたのは何年前か。多分PlayStation 3が発売された直後とかだと思います。今ではタイトルの本数もかなりの数に上るんじゃないでしょうか。だってPlayStation 4の時代ですもんね。

しかもBlu-ray Discも再生できるとか何だこれ最強だろって思います。

というわけでPlayStation 3を買いました。ついでにいろいろ揃えました。

ONKYO WAVIO アンプ内蔵スピーカー 15W+15W ブラック GX-D90(B)

ONKYO WAVIO アンプ内蔵スピーカー 15W+15W ブラック GX-D90(B)

【Amazon.co.jp限定】PLANEX ハイスピードHDMI Ver1.4ケーブル 1m (PS3/Xbox360) PL-HDMI01-EZ (FFP)

【Amazon.co.jp限定】PLANEX ハイスピードHDMI Ver1.4ケーブル 1m (PS3/Xbox360) PL-HDMI01-EZ (FFP)

PLANEX 光デジタル角型オーディオケーブル 1m PL-DA01

PLANEX 光デジタル角型オーディオケーブル 1m PL-DA01

Thunderbolt/HDMIケーブルは所有のMacBook Airと液晶ディスプレイを繋ぎたかったので、ついでに購入しました*1

で、届いたその日に各機器を繋いで、こんな感じに。

f:id:takkkun:20140114162641j:plain

スピーカーを置く専用の台が欲しいです。ぴったりなの探すより自作した方が早い気がする。まあそれは追い追い。

とりあえず早速念願であった大神 絶景版をプレイしてみました。いや、めっちゃ綺麗ですね。FF8で「うわー、すげー綺麗!」と感じた記憶が蘇えり、「これがHDMIの力か……」とかバカみたいなこと思いました(機器をHDMIで接続するのが初めてだった)。HDMIもあるでしょうが、昨今の技術やPlayStation 3の能力でしょうね。そして27インチにして良かったって思いました。今度はおおかみこどもの雨と雪や、ラブライブ!Blu-rayでも買いたいところです。

ひとしきり遊んだら今度はMacBook Airとディスプレイを繋いで、デュアルディスプレイを試してみました。とりあえずまだ観てなかったキルラキル13話を観て、正直PlayStation 3による体験より、こっちの体験の方が強烈なんじゃないかと思いました。ついでに買った割には予想外の充足を得られたThunderbolt/HDMIケーブル。

ちなみにオーディオは、PlayStation 3とスピーカーは光デジタルオーディオケーブルで繋いで直接出力し、ついでに液晶ディスプレイとスピーカーをアナログケーブルで繋いでMacBook Airからの音声は液晶ディスプレイ経由のスピーカー出力にしてみました。もう少し賢い方法があれば良いのだけど。

ひと通り堪能したので、あと何がいるかなと考えたところ、「あー、入力機器を切り替えるたびにHDMIのケーブルを抜き差しするの面倒」と思い至ったので:

iBUFFALO HDMI切替器HEAC対応2ポートBSAK202

iBUFFALO HDMI切替器HEAC対応2ポートBSAK202

も購入したのですが、これだけが充足を得られませんでした。

これのオスを液晶ディスプレイに差し、メス2口にPlayStation 3のとMacBook Airのを繋いで、切り替えを目論んだのですが、PlayStation 3の方は入力に問題ないものの、MacBook Airの方はノイズが乗り、見られたもんじゃない写り方になりました。このスイッチがThunderboltからの出力と相性が悪いのか、それともThunderbolt/HDMIケーブルの方がよろしくないのか、とりあえず購入した商品の組み合わせだと非常に都合が悪い感じです。「これなら写る!」って構成をご存じの方が居たら教えてほしいです。

とりあえず直に差す方向で乗り切りますが、基本的には非常に良い買い物をしたなあって思います。やっぱ新家電導入の体験はいつでもシビれるもんですね。

追記

接続が甘かったみたいで、先述のThunderbolt/HDMIケーブルとHDMI分配器でノイズが乗ることなく出力させることが出来ました。

*1:ちなみにHDMI(オスオス)ケーブルは液晶ディスプレイに付属してきたので、購入する必要ありませんでした