読者です 読者をやめる 読者になる 読者になる

@tako_programingの忘備録とか

@tako_programingの忘備録とかです。技術系の話が多くなるのかも。

playのHTTP routingをクソ和訳

完全な自分用メモです。翻訳がおかしければ最終的な日本語もおかしいHTTP routingの和訳です。


ビルトインHTTPルーター

このルーターは、各HTTPリクエストをActionに変換する役割を果たすコンポーネントです。

HTTPリクエストはMVCフレームワークで、イベントとして認識されます。イベントには以下の2つの主要な情報が含まれています。

  • (e.g. /clients/1542, /photos/list)のようなクエリ文字列を含むリクエストパス

  • (e.g. GET, POST, …)のようなHTTPメソッド

ルーティングは、conf/routesファイルで定義されコンパイルされます。つまり、ブラウザ上にそのままエラーが表示されます。

Dependency Injection(依存性の注入)

playでは二種類のルーターの生成をサポートしています。1つは依存性注入ルータ、もう1つは静的ルーターです。標準では依存性注入ルーターが使われます。playでは依存性注入コントローラーを使用することを推奨していますので、Activatorのテンプレートでも依存性注入コントローラーを使用するようになっています。静的コントローラを使用する必要がある場合は、build.sbtに以下のように記述します。

routesGenerator := StaticRoutesGenerator

playのドキュメントのコードサンプルは、注入されたルータージェネレーターを使用することを前提としています。これを使用しない時は、ルートコントローラの呼び出しの先頭部に@シンボルをつけたり、各コントローラをclassではなくobjectとして宣言することで静的ルートジェネレータに自由に変更することが可能です。

ルーティングファイルの文法

conf/routeは、ルーティングに使用される設定ファイルです。このファイルには、playアプリケーションの全てのルートが一覧で表示されます。各ルートはHTTPメソッドとURIパターンで構成され、どちらもActionジェネレータへの呼び出しに関連付けれらます。

ルート定義は次のように記述します。:

GET   /clients/:id          controllers.Clients.show(id: Long)

ルートはHTTPメソッドで初まり、その後にURIパターンが続きます。最後の要素は呼び出し定義です。

また、ルートファイルには、#シンボルから始まる文字列でコメントを追加することができます。

# Display a client.
GET   /clients/:id          controllers.Clients.show(id: Long)

特定のプレフィックスの下にある別のルータを使用するには->の後ろに与えられた接頭辞を使用します。

->      /api                        api.MyRouter

この方法は、SIRDルーティングとも呼ばれるString Interpolationg Routing DSLと組み合わせたり、複数のルートファイルを使用してルーティングするSub Projectを処理する場合に便利です。

HTTPメソッド

HTTPメソッドは、HTTP(GETPATCHPOSTPUTDELETEHEAD)で、サポートされている有効なメソッドのうちどれでも構いません。

URIパターン

URIパターンはルートのリクエストパスを定義します。リクエストパスの一部は動的であっても構いません。

静的なパス

たとえば、GET /clients/allリクエストに対応させるためには、次のようなルートを定義します。

GET   /clients/all          controllers.Clients.list()

動的なパス

IDでクライアントを取得するようなルートを定義する場合は、動的なパーツを追加する必要があります。

GET   /clients/:id          controllers.Clients.show(id: Long)

注: URIパターンは複数の動的部分を持つこともできます。

動的部分のデフォルトのマッチングでは、正規表現[^/]+で定義されます。つまり,:idで定義された動的部分は、正確に1つのURIパスセグメントに一致します。他のパターンタイプとは異なり、パスセグメントはルートに自動的にURIデコードされた後、コントローラに渡され、逆ルートでエンコードされます。

複数の動的部分 /

動的部分が複数のURIパスセグメントをスラッシュで区切って取得するようにするには、*id構文(ワイルドカードパターンとも呼ばれる*id正規表現を使用すます。)を使って動的パターンを定義できます。

GET   /files/*name          controllers.Application.download(name)

GET /files/images/logo.pngのようなリクエストの場合、動的部分nameimages/logo.pngを取得します。

複数の/にまたがる動的部分は、ルータによってデコードされないまたは、逆ルータによってエンコードされることに注意する必要があります。生のURIセグメントを検証するのはあなたの責任です。逆ルータは文字列の連結のみを行うため、結果のパスが有効であることを確認する必要があります。例えば、複数のスラッシュやASCII以外の文字は含まれません。

動的部分にカスタム正規表現を使用する

$id <regex>構文を用いて動的部分に独自の正規表現を定義することができます。

GET   /items/$id<[0-9]+>    controllers.Items.show(id: Long)

ワイルドカードのルーティングと同様に、パターメタはルーターによっってデコードされないまたは、逆ルーターによってエンコードされません。それが正しいかどうかを検証する必要があります。

ActionGeneratorメソッドの呼び出し

ルートの定義に最後の部分はActionGeneratorメソッドの呼び出しです。この部分では、play.api.mvc.Action型の値を返すメソッドへの有効な呼び出しを定義する必要があります。これは通常、ControllerのActionメソッドになります。 メソッドがパラメータを定義していない場合は、メソッドの完全修飾名を指定してください。

GET   /                     controllers.Application.homePage()

Actionメソッドが1つ以上のパラメータを定義する場合、これらのパラメータはすべてリクエストURI、またはクエリ文字列から検索されます。

# Extract the page parameter from the path.
GET   /:page                controllers.Application.show(page)

または、

# Extract the page parameter from the query string.
GET   /                     controllers.Application.show(page)

下に、対応するControllerのめメソッド定義を示します。

def show(page: String) = Action {
  loadContentFromDatabase(page).map { htmlContent =>
    Ok(htmlContent).as("text/html")
  }.getOrElse(NotFound)
}

### パラメータの型 ルート定義にあるActionGeneratorメソッドの引数がString型の場合、型を明示する必要はありません。ですが、playでリクエストパラメータを他の特定のScalaタイプにする必要がある場合は、引数の型を明示する必要があります。 scala GET /clients/:id controllers.Clients.show(id: Long) また、Controllerの対応するshowメソッドの定義でも同様に型を明示する必要があります。 scala def show(id: Long) = Action { Client.findById(id).map { client => Ok(views.html.Clients.display(client)) }.getOrElse(NotFound) } ### パラメータの値の固定 リクエストパラメータに値が存在しない場合に使用するデフォルトの値を設定することもできます。 scala # Pagination links, like /clients?page=3 GET /clients controllers.Clients.list(page: Int ?= 1) ### オプションパラメータ 全てのリクエストに対して必ずしも必要とは限らないオプションのパラメータを指定することも可能です。

# The version parameter is optional. E.g. /api/list-all?version=3.0
GET   /api/list-all         controllers.Api.list(version: Option[String])

ローティングの優先順位

多くのルーティング定義がたくさんのリクエストに合致してしまう場合が存在します。その場合は、宣言順で使用されます。

逆ルーティング

ルーターは、逆にScalaプログラムからURLを生成するためにも使うことができます。この機能によって全てのURIパターンを単一の設定ファイルに集中させられるので、リファクタリングが楽になります。

ルーターはルートファイルで使用される各Controllerに対し、routesパッケージ内にreverse controllerを生成します。これは、ルートファイルで使われるメソッドと同一のシグネチャを持ちますが、play.api.mvc.Actionではなくplay.api.mvc.Callを返します。

たとえば、下のようなControllerを定義したとします。

package controllers

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

class Application extends Controller {

  def hello(name: String) = Action {
    Ok("Hello " + name + "!")
  }

}

これに対応するようにconf/routesにルーティングを記述すると

# Hello action
GET   /hello/:name          controllers.Application.hello(name)

次に以下のようにして、controller.routes.Applicationリバースコントローラーを使って、helloアクションメソッドを使うことができます。

// Redirect to /hello/Bob
def helloBob = Action {
  Redirect(routes.Application.hello("Bob"))
}

注: 各コントローラにはroutesサブパッケージが存在します。そのため、コントローラcontrollers.admin.Application.helloアクションは、controllers.admin.routes.Application.hello(ルートファイルconf/routesに、Scalaプログラム内の逆ルーティングで生成されたパスと一致する他のルートがない場合に限り)逆ルーティングを行うことが可能です。

Scalaプログラム内から呼ばれた逆Actionメソッドはとても単純に機能します。このメソッドは、パラメータをとり、ルートパターンに戻します。パスセグメント(:fooのような形をしたもの)は、値は実際に置き換えが行われる前の時点でエンコードされます。正規表現ワイルドカードを用いたルーティングパターンの場合は、値が複数のセグメントにまたがる可能性があります。そのため、そのような値を逆ルーティングに用いる場合には、必要に応じてこれらの値をエスケープして、未検証のユーザー入力を渡さないように注意してください。

デフォルトのコントローラ

playには、いくつかの便利なActionが定義されているDefault Controllerが含まれています。これらはルートファイルconf/routesから直接呼び出すことが可能です。

# Redirects to https://www.playframework.com/ with 303 See Other
GET   /about      controllers.Default.redirect(to = "https://www.playframework.com/")

# Responds with 404 Not Found
GET   /orders     controllers.Default.notFound

# Responds with 500 Internal Server Error
GET   /clients    controllers.Default.error

# Responds with 501 Not Implemented
GET   /posts      controllers.Default.todo

上記の例では、GET /のリダイレクトで、外部サイトに飛ぶようになっていますが、別のアクション(例では/ postsなど)にリダイレクトすることも可能です。

カスタムルーティング

playには、String Interpolating Routing DSL、通称sirdと呼ばれる組み込みルータを定義するためのDSLを定義されています。このDSLには、軽量のplayサーバーの組み込み、通常のplayアプリケーションへの高度なルーティング機能の提供、テスト用RESTモックサーバーを作成するなどの用途に使用できます。

詳しくはString Interpolationg Routing DSL

playのActions, Controllers and Resultsをてきとうに和訳

実はplayロクに触ったことがないので、ちゃんと勉強してみようかなぁという試みです。


Actionって?

playアプリケーションの受け取るほとんどのリクエストは、Actionによって処理されます。play.api.mvc.Actionは、基本的にリクエストを処理し、クライアントに送信する結果を生成する(play.api.mvc.Request => play.api.mvc.Result)型の関数です。

def echo = Action { request =>
  Ok("Got request [" + request + "]")
}

Actionは、Webクライアントに送信するHTTPレスポンスを表すplay.api.mvc.Result値を返します。この例では、Okはtext/plainを含む200 Okレスポンスを生成します。

Actionの生成

play.api.mvc.Actionコンパニオンオブジェクトは、Action値を構築するためのヘルパーメソッドをいくつか提供します。

まず最初に紹介するのは、引数として、Resultを返す式ブロックを取ります。

Action {
  Ok("Hello world")
}

これはActionを作成する最も簡単な方法ですが、受け取ったrequestへの参照がありません。このActionを呼び出すHTTPリクエストにアクセスすると便利なことがよくあります。 そのため、引数としてRequest => Resultをとる別のActionビルダーが存在します。

Action { request =>
  Ok("Got request [" + request + "]")
}

implicitキーワードを用いて、リクエストパラメータを暗黙的にマークすると、それを必要とする他のAPIによって暗黙的に使用されることがあります。

Action { implicit request =>
  Ok("Got request [" + request + "]")
}

最後に紹介するActionビルダーは、追加のBodyParserを指定するものです。

Action(parse.json) { implicit request =>
  Ok("Got request [" + request + "]")
}

BodyParserについての詳細はこのマニュアルの後半で説明します。今のところ、Action値を作成する他のメソッドはデフォルトのAny contetn body parserを使用していることを知っておいてください。

ControllerはActionを生成するオブジェクトです。

ControllerはAction値を生成するオブジェクトにすぎません。Controllerは、Dpendency Injenctionを利用するクラスとして定義することも、オブジェクトとして利用することもできます。 注:Controllerをobjectとして定義することは、playでは将来的にサポートされなくなります。クラスでのControllerの使用が推奨されます。 Action値のジェネレータを定義する最も簡単な方法は、Action値を返す、引数を持たないメソッドです。

package controllers

import play.api.mvc._

class Application extends Controller {

  def index = Action {
    Ok("It works!")
  }

}

もちろん、Actionジェネレータメソッドは引数を持つことも可能であり、これらの引数はクロージャとして使うことができます。

def hello(name: String) = Action {
  Ok("Hello " + name)
}

シンプルなresults

いまのところ私達は、ステータスコードなどのHTTPレスポンス、HTTPヘッダーのセット、およびWebクライアントに送信される文章などのシンプルな結果に興味があります。これらのシンプルな結果はplay.api.mvc.Resultによって定義されています。

import play.api.http.HttpEntity

def index = Action {
  Result(
    header = ResponseHeader(200, Map.empty),
    body = HttpEntity.Strict(ByteString("Hello world!"), Some("text/plain"))
  )
}

もちろん、上記のサンプルでOkのResultのような一般的な結果を作成するためのヘルパーがいくつか存在します。

さまざまな結果を表示するためのいくつかの例を下に示します。

val ok = Ok("Hello world!")
val notFound = NotFound
val pageNotFound = NotFound(<h1>Page not found</h1>)
val badRequest = BadRequest(views.html.form(formWithErrors))
val oops = InternalServerError("Oops")
val anyStatus = Status(488)("Strange response type")

なお、これらのヘルパーはすべてplay.api.mvc.Resultsのトレイトとコンパニオンオブジェクトに定義されています。

リダイレクトもシンプルなresultsです。

ブラウザを新しいURLにリダイレクトすることは、種類のことなるシンプルなresultsに過ぎません。ただ、これらの結果はリクエストボディを使用しません。 リダイレクトのためのResultを作成するには、いくつかのヘルパーが利用できます。

def index = Action {
  Redirect("/user/home")
}

標準のままだと、303 SEE_OTHERレスポンスタイプを使用しますが、必要に応じてより具体的なステータスコードを設定することができます。

def index = Action {
  Redirect("/user/home", MOVED_PERMANENTLY)
}

TODO ダミーページ

TODOとして定義された空のAction実装を使用することが可能です。そのresultは標準のNot implemented yetのページになります。

def index(name:String) = TODO

playのJSON Basicsをてきとうに和訳

Scalaの有名なウェブアプリケーションフレームワークにplayというものがあります。そのplayのJSONの扱いについて描かれているJSON Basicsをてきとうに和訳しました。


最近のウェブアプリケーションはよくJSONのデータをパースしたり作ったりする必要があります。PlayはJSONの扱いをJSON Libraryを経由してサポートしています。JSONとはは以下のような軽量なデータ・フォーマットです。

{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}

JSON自体について更に詳しく知りたい場合はjson.orgを参照してください。

The Play JSON Libray

play.api.libs.jsonパッケージには、JSONデータを表すデータ構造と、そのデータ構造と他のデータ表現を変換するユーティリティが含まれています。これらはそのパッケージの機能の一部です。

  • Automatic conversion 最小の定型文を用いて、ケースクラスとの間で自動的な変換を行います。最小限のコードですぐに実行したい場合は、おそらくここから始めることになるでしょう。

  • Custom validation パース中のカスタムバリデーション

  • Automatic parsing リクエストボディでのJSONの自動解析で、コンテンツを解析できないまたは正常でないコンテンツタイプのヘッダが提供されたとき、エラーを自動生成します。

  • これらの機能はスタンドアローンのライブラリとしてplay以外でも使用することができます。そのためには、build.sbtlibraryDependencies + = "com.typesafe.play" %% "play-json"%playVersionを追加する必要があります。 このパッケージには、以下のようなタイプがあります。

    JsValue

    これはJSONの値を扱うトレイトです。このJSONライブラリは各JSONのタイプに対応するcase classが存在します。

  • JsString

  • JsNumber

  • JsBoolean

  • JsObject

  • JsArray

  • JsNull

これらのJsValueを利用することで、任意のJSON構造を作ることができます。

JSON

JSONオブジェクトは、主にJsValueとの変換用ユーティリティを提供します。

JsPath

これは、XMLXPathによく似たもので、JsValue構造へのパスを表します。これはJsValue構造をトラバースするために暗黙の変換のためのパターンで使用されます。

Converting to a JsValue

String型を使ってパースします。

import play.api.libs.json._

val json: JsValue = Json.parse("""
{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}
""")

JsValueの各case classを用いてJSONを作成します。

val json: JsValue = JsObject(Seq(
  "name" -> JsString("Watership Down"),
  "location" -> JsObject(Seq("lat" -> JsNumber(51.235685), "long" -> JsNumber(-1.309197))),
  "residents" -> JsArray(Seq(
    JsObject(Seq(
      "name" -> JsString("Fiver"),
      "age" -> JsNumber(4),
      "role" -> JsNull
    )),
    JsObject(Seq(
      "name" -> JsString("Bigwig"),
      "age" -> JsNumber(6),
      "role" -> JsString("Owsla")
    ))
  ))
))

Json.objJson.arrを用いることで、JSONオブジェクトやJSON配列をもう少し簡潔に構築することができます。ほとんどの場合において、JsValueクラスで明示的にラップする必要がないことに注意しましょう。これらのファクトリメソッドは暗黙的に変換します。(詳細は後述)

val json: JsValue = Json.obj(
  "name" -> "Watership Down",
  "location" -> Json.obj("lat" -> 51.235685, "long" -> -1.309197),
  "residents" -> Json.arr(
    Json.obj(
      "name" -> "Fiver",
      "age" -> 4,
      "role" -> JsNull
    ),
    Json.obj(
      "name" -> "Bigwig",
      "age" -> 6,
      "role" -> "Owsla"
    )
  )
)

Writes convertersを使用する

ScalaからJsValueへの変換は、ユーティリティメソッドJson.toJson[T](T)(implicit writes: Writes[T])によって実行されます。このメソッドは、TはJsValueに変換するWrites[T]型のコンバータに依存します。 Play JSON APIは、Int, Double, String, Booleanなどのほとんど全てのプリミティブ型のimplicit Writesを提供します。また、Writes[T]が存在する任意の型TのコレクションのためのWritesをサポートします。

import play.api.libs.json._

// basic types
val jsonString = Json.toJson("Fiver")
val jsonNumber = Json.toJson(4)
val jsonBoolean = Json.toJson(false)

// collections of basic types
val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))

独自のモデルをJsValueに変換するには、implicit Writesコンバータを定義し、それらをスコープで指定する必要があります。

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._

implicit val locationWrites = new Writes[Location] {
  def writes(location: Location) = Json.obj(
    "lat" -> location.lat,
    "long" -> location.long
  )
}

implicit val residentWrites = new Writes[Resident] {
  def writes(resident: Resident) = Json.obj(
    "name" -> resident.name,
    "age" -> resident.age,
    "role" -> resident.role
  )
}

implicit val placeWrites = new Writes[Place] {
  def writes(place: Place) = Json.obj(
    "name" -> place.name,
    "location" -> place.location,
    "residents" -> place.residents)
}

val place = Place(
  "Watership Down",
  Location(51.235685, -1.309197),
  Seq(
    Resident("Fiver", 4, None),
    Resident("Bigwig", 6, Some("Owsla"))
  )
)

val json = Json.toJson(place)

また、コンビネータパターンを用いてWritesを定義することも可能です。 注:コンビネータパターンについてはJSONReads/Writes/Formats Combinatorsで詳しく説明しています。

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationWrites: Writes[Location] = (
  (JsPath \ "lat").write[Double] and
  (JsPath \ "long").write[Double]
)(unlift(Location.unapply))

implicit val residentWrites: Writes[Resident] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "age").write[Int] and
  (JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))

implicit val placeWrites: Writes[Place] = (
  (JsPath \ "name").write[String] and
  (JsPath \ "location").write[Location] and
  (JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))

JSON Value構造をトラバースする

JsValue構造体をトラバースして特定の値を抽出することができます。その構文と機能はScalaXML処理ににています。 注:以下の例は、前の例で作成されたJsValue構造体に適用されます。

Simple path \

\演算子をJsValueに適用すると、これがJsObjectであると仮定して、フィールド引数に対応するプロパティを返します。

val lat = (json \ "location" \ "lat").get
// returns JsNumber(51.235685)

Recursive path \

\\演算子を適用すると、現在のオブジェクトとそのすべての子孫のフィールドが検知されます。

val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))

Index lookup (for JsArrays)

インデックス番号の適用演算子を使用してJsArrayの値を取得できます。

val bigwig = (json \ "residents")(1)
// returns {"name":"Bigwig","age":6,"role":"Owsla"}

JsValueから変換する

文字列ユーティリティの使用 縮小版:

val minifiedString: String = Json.stringify(json)
{"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197},"residents":[{"name":"Fiver","age":4,"role":null},{"name":"Bigwig","age":6,"role":"Owsla"}]}

可読版:

val readableString: String = Json.prettyPrint(json)
{
  "name" : "Watership Down",
  "location" : {
    "lat" : 51.235685,
    "long" : -1.309197
  },
  "residents" : [ {
    "name" : "Fiver",
    "age" : 4,
    "role" : null
  }, {
    "name" : "Bigwig",
    "age" : 6,
    "role" : "Owsla"
  } ]
}

バリデーションを使う

JsValueから他の型に変換するための好ましいのは、validateメソッド(引数はReads型)を使用する方法です。これにより、検証と変換の両方が実行され、JsResult型が返される。JsResultは次の2つのクラスで実装されています。

  • JsSuccess: 検証と変換が成功したことを表し、結果をラップします。

  • JsError: 検証と変換の失敗を表し、検証エラーのリストを含んでいます。 検証結果を処理するために様々なパターンを適用することができます。

val json = { ... }

val nameResult: JsResult[String] = (json \ "name").validate[String]

// Pattern matching
nameResult match {
  case s: JsSuccess[String] => println("Name: " + s.get)
  case e: JsError => println("Errors: " + JsError.toJson(e).toString())
}

// Fallback value
val nameOrFallback = nameResult.getOrElse("Undefined")

// map
val nameUpperResult: JsResult[String] = nameResult.map(_.toUpperCase())

// fold
val nameOption: Option[String] = nameResult.fold(
  invalid = {
    fieldErrors => fieldErrors.foreach(x => {
      println("field: " + x._1 + ", errors: " + x._2)
    })
    None
  },
  valid = {
    name => Some(name)
  }
)

JsValueから独自モデルへ

JsValueから独自のモデルに変換するには、implict Reads[T]を定義する必要があります。ここでは、Tはモデルのタイプである。 注:Readおよびカスタムバリデーションの実装に使用されるパターンについては、JSON Reads / Writes / Formats Combinatorsで詳しく説明しています。

case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val locationReads: Reads[Location] = (
  (JsPath \ "lat").read[Double] and
  (JsPath \ "long").read[Double]
)(Location.apply _)

implicit val residentReads: Reads[Resident] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "age").read[Int] and
  (JsPath \ "role").readNullable[String]
)(Resident.apply _)

implicit val placeReads: Reads[Place] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "location").read[Location] and
  (JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)


val json = { ... }

val placeResult: JsResult[Place] = json.validate[Place]
// JsSuccess(Place(...),)

val residentResult: JsResult[Resident] = (json \ "residents")(1).validate[Resident]
// JsSuccess(Resident(Bigwig,6,Some(Owsla)),)

と、まあ自らの理解を深めるためにてきとうに和訳してみたので、明らかに意味が異なる場合なんかがあったら指摘してください。

# Scalaの型パラメータの非変、共変、反変

前回の記事を書きながら、「そういや型パラメータについて超ざっくり説明してんなぁ」と思ったので。


前回の記事の説明だと型パラメータがあたかも関数特有の機能のように聞こえるかもしれませんが、型パラメータはJavaジェネリクスのようにクラスなどでも使うことができます。というか、Scalaを使ったことがあれば型パラメータを用いて作られているデータ構造を使用しているはずです。ListやArrayなどのコレクションライブラリは全てそれです。型パラメータの使い方は基本的に関数の時と同様です。

class Example[T](a: T) {
  def toList(): List[T] = List(a)
}
val example = new Example[Int](3)
example.toList //=> List(3)

ExampleクラスのtoList関数はメンバーaをただListにつつんで返すだけです。


ここまでだったらクラスでも使えるよーと言えばいい話なのですが、Scalaの型パラメータには反変、共変という性質があり、前回それに全く触れなかったので、ここで。 まず非変、非変は型パラメータA,Bがあったとき、例えば

val: G[A] = G[B]

の代入式が、AとBが完全に等しい場合にのみ成り立つ性質です。 それに対して、共変は

val G[B] = G[A]

がAがBの親クラスであるときに限り成り立つ性質であり、反変は

val G[A] = G[B]

が、AがBの親クラスである場合にのみ成り立つ性質です。Scalaでは標準で非変になります。ですが、Javaでは共変なので、Javaを使ったことのある人は少し違和感を感じるかもしれません。実際自分もちゃんと調べるまで完全にScalaでも共変だと思ってました。いまから1つ例を提示します。AnimalクラスがCatクラスをサブクラスに持つと仮定します。そのときJavaでは

Animal[] animals = new Animal[1];
animals[0] = new Cat();

が成り立ちます。これはJavaが標準で共変であるため、CatスーパークラスであるAnimal型の配列にCat型の値を入れようとしてもちゃんと通ります。ですが、反対にScalaは標準では非変なので、

val animals: Array[Animal] = new Array[Cat](new Cat())

に対してtype mismatchでエラーを吐きます。Scalaでは標準では非変なのですが、もちろん共変や反変にすることもできます。共変にするには型パラメータを[+A]のように定義します。また、反変にするには型パラメータを[-A]のようにします。

Scalaの多相関数のお勉強

Scalaでのプログラミングを学ぶ場合、教材は良いものが結構あって、「Scalaスケーラブルプログラミング第三版」や「Functional Programming in Scala」の和訳本などが有名です。また、ドワンゴさんの新人研修テキストがネット上で無料で読めたはずなので、お金を払って書籍を買うほどでもないという人は最初に読んでみるといいかもしれないです。今回の記事は関数型プログラミング全般において重要な要素の1つでもある関数型プログラミングについてScalaで勉強したときのメモです。


多相関数とは、1つのデータ型に対してのみ操作を行う単相関数に対して、いかなる型に対しても操作を行うことのできる関数のことをいいます。Scalaの標準ライブラリには、たくさんの多相関数が存在します。たとえば以下に示す関数はString型の文字列の先頭文字を返す単相関数です。

def returnHead(str: String): Char = str.head

この関数はString型の値しか引数にもらうことができません。単一の文字であるChar型の値しか返すことができません。これを全てのいかなる型のリストでも引数としてうけとることができ、そのリストにつつまれている型の値を返り値として返すことができる多相関数として定義しなおしてみましょう。

def returnHead[T](arg: List[T]): T = arg.head

これであらゆるT型のリストを引数としてうけとり、その先頭要素を返り値として返すことができるようになります。(ただ、この関数にStringの値を直接渡すとtype mismatchでエラーが起こります。この関数のままで対応するにはStringの値にtoList関数を使いChar型のリストに変換する必要があります。)ためしに使ってみましょう。

scala> returnHead(List(1, 2, 3, 4, 5))
res: Int = 1

と、こうなります。 一応このreturnHead関数の説明をしておきます(定義のままですが)。まず、最初の方は通常の単相関数と変わりありません。そのあと[T]がでてきます。この[T]は型パラメーターと呼ばれます。型パラメータは[A, B, C]のように区切って複数持たせることができます。また、この名前は通常の変数や引数と同じようにどのような名前をつけても機能しますが、慣例として大文字1つの名前をつけることが多いようです。 また、この型パラメータの一つずつを型変数といい、型シグネチャの他の箇所から自由に参照することができます。今回定義した関数では、arg: List[T]のように関数のパラメータリストのなかで一度参照しています。今回の関数では一箇所からしか型変数を参照していませんが、複数箇所から参照することが可能です(というかむしろそういう使い方をするのが普通な気がします)。

def example[T](arg1: T, arg2: T => List[T]): List[T] = arg2(arg1)

上の例の関数は、型変数Tを関数の引数リストで二度参照しています。第二引数の関数に第一引数のT型の値を適用した値を返します。試しに使ってみると以下のようになります。

scala> example(4, (x: Int) => { List(x, x+1, x+2) })
res1: List[Int] = List(4, 5, 6)

ここまでくれば説明するまでもないとは思いますが、example4(x: String) => { List(x + "0", x + "1", x + "2") }でも渡そうものならtype mismatchで怒られてしまいます。Tは1つの関数の中で一貫して同じ型のことを示します。


これらのような多相関数は色々な動作をより抽象的に表現することが可能になります。抽象化することで再利用性が向上しますし、より関数型プログラミングのスタイルに近づくということみたいです。

Java初心者にScalaを勧める記事

LiTでJavaに慣れた人へScalaのススメ

LiTで、Androidコースやマインクラフトコースをやった人は必然的にJavaを触ることでしょう。そしてJavaを書いてる途中で色々と思うことがあるんじゃないでしょうか。めんどくせえな、と。行末にセミコロンを必ずつけなくてはいけないとかインスタンスを生成するときにコンストラクタの引数がなくても括弧をつけなくちゃいけないとかListのheadとtailを取ろうとするとクソめんどくさいとか関数合成がめんどくさいとか。そんな悩みをある程度解決してくれるのがScalaという言語。

Scalaって?

Java仮想環境上で動作する強い静的型付けの関数型オブジェクト指向プログラミング言語です。Javaに比べて簡潔で、関数型的な記述が可能です。また、Javaと同じオブジェクト指向言語でもありますし、それっぽい書き方がどうしてもできない時はJavaのような命令型っぽい書き方もできます。

環境構築から

OS Xならhomebrewですぐ入ります。

$ brew update
$ brew install scala
$ brew install sbt

3行目でインストールしているsbtというのはScalaのビルドツール(JavaでいうMavenとかGradleとかみたいな)です。sbtではJavaも使えます。また、pluginを入れることでKotlinやfrege等他の言語も動くみたいです(確認はしてません)。

REPL

ScalaにはREPL(Read-Eval-Print-Loop)があります。REPLは、ターミナルで

$ scala

と入力するだけで起動します。さっそく使ってみましょう。

scala> 1 + 2

と入力してみてください。

res0: Int = 3

と出力されるはずです。これの動きは見たまんまなのでわかりますね。1と2を足してるんです。こんな感じでREPLはちょっとしたコードの動きを確かめたりするのに向いています。とりあえず今回は、このREPLを使ってScalaに軽く触れてみましょう。

変数

Scalaでは変数の宣言と代入はこのように行います。

val n: Int = 3

これはJavaだと

final int n = 3

このようになります。Scalaではval or var+ 変数名+:+型名+=+という形で宣言と代入ができます。ここでまず意味がわからないのはvalでしょう。その後のJavaの例でわかったかもしれませんが、valには宣言した変数が再代入不可になるという意味があります。試しにvalで宣言した変数に再代入してみましょう。

scala> val n: Int = 3
n: Int = 3
scala> n = 4
<console>:12: error: reassignment to val
       n = 4
         ^

エラーが起きていますね。再代入不可だからです。ここで、もう一つ不思議に思った点があるんじゃないでしょうか。なんでこんな序盤でJavaではあまり使わないfinal修飾子のようなものを例として使ってきたかという点です。Scalaでは基本的に変数の再代入を行いません。valの代わりにvarを使うことで、再代入を許すこともできますが、valを使う方が推奨されています。これはなぜかというと、再代入が可能だと状態の管理が難しくなってしまうからです。コードの中のどこで変数の中の値が変わっているかわからない状態というのはとても恐ろしいです。思わぬ例外を引き起こす可能性があります。例えばどこかでnullでも入ってたらJavaを書いたことがある人ならよく目にするであろうjava.lang.NullPointerExceptionが連発することでしょう。しかもIDEは例外が起こった場所は示してくれるかもしれませんが、変数を再代入した場所はしめしてくれないでしょう。そういった理由でScalaでは変数は極力valで宣言し、再代入を禁止することが推奨されているのです。

関数

次は関数です。ではまず簡単な関数を1つ書いてみましょう。

scala> def add3 (x: Int): Int = x + 3

この関数は引数として整数を1つとりそれに3を足して返す関数です。Scalaで関数を定義する時はdef+関数名+引数リスト+:+返り値型+=+処理となります。ではこの関数を使ってみましょう。

scala> add3(1)
res0: Int = 4

Scalaで関数を使う時はJavaと同じように関数名の括弧の中に引数をいれます。ちなみに引数のない関数は括弧を省略できます。

scala> def printHello ():Unit = println("HELLO")
printHello: ()Unit

scala> printHello
HELLO

ここで、関数の返り値に指定されてるUnitというのはJavaでいうvoidのようなものです。次は複数の引数を持つ関数を定義しましょう。基本的にはなにも変わらないです。,で引数を区切るだけです。

scala> def add (x: Int, y: Int): Int = x + y
add: (x: Int, y: Int)Int

scala> add (1, 3)
res2: Int = 4

こんな感じです。ちなみに関数が複数行にまたがる時は波括弧で囲います。

def add(x: Int, y: Int): Int = {
  x + y
}

ここまできてJavaプログラマーは「あれ?」と思った所があると思います。そうです、returnがないんです。Scalaでは関数の最後の結果が自動的に返り値になります。もちろんreturnで明示的に返り値を指定することもできますが、普通はつけなくてよい所ではreturnは付けないようにします。

関数の変数

Scalaでは、関数を第一級オブジェクト(ファーストクラスオブジェクト)として扱っているので、変数への代入が可能です。

scala> def add3 (x: Int): Int = x + 3
scala> val f: Function[Int, Int] = add3 _

これで、変数fにadd3が代入されました。実行してみましょう。

println(f(3)) //=> 6

6が出力されると思います。ちなみにScalaではFunction0~Function22というトレイト(Javaでいうインターフェース)が用意されているので、22を超える引数の関数を代入しようとするとエラーが起こります。また、関数を変数に代入する時には_が必須です。Scalaでは関数が普通のオブジェクトなので、他の関数の引数に関数を渡せたり、関数が関数の返り値にできたりします。このような関数をScalaに限った話ではないですが、高階関数と呼びます。

関数の部分適用

Scalaでは2つより多い引数を持つ関数にその引数の数より少ない数の引数を与えると、残りの引数を引数にとる関数を返す機能があります。何を言ってるのかわからないですね。下の例をみてください。

scala> def add (x: Int, y: Int): Int = x + y

scala> val f = add(3, _: Int)
  f: Int => Int = <function1>

scala> f(4)
  res0: Int = 7

これには関数の部分適用が使われています。まず3行目でadd関数に3と_プレースホルダー)を与えます。プレースホルダーは未確定の変数を意味します。すると機能的には、先程から使っているadd3関数と同等の関数がfに代入されます。そしてfは普通の関数のように残りの引数を与えて呼び出すことができます。

関数合成

ScalaではJavaと同様に関数合成をすることができます。

scala> def add3(x: Int): Int = x + 3

scala> def add5(x: Int): Int = x + 5

scala> add3(add5(1))
  res0: Int = 9

scala> def f (x: Int): Int = add3(add5(x))

愚直に書くとこうなります。意味はわかりやすいと思います。add5に1を渡して6が返ってきますね、その6をadd3に渡すと9が返ってきます。それをそのまま1つの関数にすることもできます。でも次は少しスマートに書いてみましょう。それにはFunctionオブジェクトのcomposeメソッドを使います。

scala> val add8 = (add3 _) compose (add5 _)

scala> println(add8(0))
8
Function<Int, Int> add3 = x -> x + 3
Function<Int, Int> add5 = x -> x + 5
Function<Int, Int> add8 = add3.andThen(add5)

このすぐ上のコードは(わかるとは思いますが)Javaで同じことをしたものです。可読性で言えば大差ないかもしれないですが、Scalaの場合はComsumerやPredicateのようないくつかの標準関数型インターフェースがあるわけではなく言語自体が関数をオブジェクトとしてサポートしているので、複雑になっていけばより(Javaと比較して)コードが読みやすいものになってくると思います。あと今回は両方とも足し算するだけの関数なので順番はあまり関係ないのですが、順番が関係ある場合、composeメソッドだと呼び出すときに適用する順番と逆の順番で書くことになると思います。それを避けて順序どおりに書きたいときは(JavaのFunction同様に)andThenメソッドをcomposeの時と同じようにに使うといいです。

再帰関数

話を少し変数の所に戻します。先程も言ったとおりScalaの変数は基本的にvalを使います。じゃあこんなJavaArrayListの総和を求めるコードをScalaでvarを使わずに書きたいときはどうしますか?

public int accumulate( ArrayList<Integer> arrayListA ){
        int sum = 0;
        for( Integer tmp : arrayListA )
            sum += tmp;
  
        return sum;
}

少し悩むと思います。そしてここで登場するのが再帰関数です。再帰関数とは関数の中で自分を呼ぶ関数のことです。さきほどの配列の総和を求める例で見てみましょう。

def total(List[Int]: xs): Int = if (xs.isEmpty) 0 else xs.head + total(xs.tail)

あら簡単。一行で書けちゃいました。ちなみにxs.headでListの先頭要素を、xs.tailで、Listの先頭以外の要素を取得します(これ自体もJavaでやろうとすると結構めんどくさいですよね)。このように再帰関数を使うと簡単に綺麗に関数が書けるようになります。

終わりに

今回はJavaとくらべてScalaが良いぞって話だったので、Javaと書き方に大きな差のない基本構文(ifなど)は紹介しませんでした。なので、この記事を読んでいきなりScala書けやと言われても到底ムリな話です。ですが、この記事を読んだLiT出身のJavaプログラマーJavaの、主に関数周りの煩わしさから開放される1つの手段としてScalaのことを考えてくれるようになったのであればそれは自分にとってすごく嬉しいことです。Scalaは今は主にPlayFrameWorkやScalatraといったフレームワークWebのサーバーサイドで使われている言語ですが、Androidアプリ開発もできないことはありませんし、Scala.jsというライブラリでJavaScriptコンパイルができるのでAltJSとしてWebフロントエンドの開発に貢献できるようになるかもしれません。(Androidアプリ開発をするならKotlinという言語を断然オススメします。)

AWS Mobile Analyticsのイントロダクションとかをてきとうに和訳

バイト先でAWS Mobile Analyticsを使うことになって調べていたのを英語が読めないので和訳したのでメモ的に貼っておきます。

AWS Mobile Analytics

公式のGetting Started

  • 1 : AWSアカウントでサインアップする

  • 2 : 以下の手順に従ってアプリを追加する。

    • 次のリンクからAmazon Mobile Analyticsコンソールを開きます
      Amazon Mobile Analytics Console
    • 初めての利用の場合は画面の指示に従ってアプリを追加します。
    • すでにアプリを追加している場合は設定アイコンを選びます。
    • 「Manage Apps」で、「Add App」を選択します。
    • 指示に従います。
  • 3 : Mobile Analyticsを自分のアプリに結合します。

    • Mobile Analyticsを結合するには、アプリに適したプラットフォームのSDKを使用します。(以下のプラットフォームのSDKがあります)
      • Android app
      • iOS app
      • JavaScript app(*僕が今回使ったのはこれなのでこれだけリンクがはってあります。)
      • Unity app
      • Xamarin app
  • 4 : Amazon Mobile Analyticsレポートのデータを確認する


aws-sdk-mobile-analytics(npm)

https://www.npmjs.com/package/aws-sdk-mobile-analytics

イントロダクション

JavaScript用Mobile Analytics SDKを使用すると、JavsScript対応アプリケーションは、AWSコンソールでの分析、S3およびRedshiftへの自動エクスポートによって、イベントを作成して送信することができます。ライブラリはブラウザのローカルキャッシュAPIを使用してデータのローカルキャッシュを作成し、アプリケーションがオフラインのときでもWebアプリケーションでイベントをバッチおよび記録できます。

Setup

  <script src="/js/aws-sdk.min.js"></script>
  <script src="/js/aws-sdk-mobile-analytics.min.js"></script>

使い方

  • 1 : Amazon Mobile Analytics管理コンソールにログインし、新しいアプリを作成する。App IdとCognito Identity Pool Idに注意する。
  • 2 : Cognito Identity Pool IDを使用して、クレデンシャルプロバイダを初期化します。 これは、AWS SDKAmazon Mobile Analytics REST APIへの認証を管理するために必要です。
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: COGNITO_IDENTITY_POOL_ID   //Required e.g. 'us-east-1:12345678-c1ab-4122-913b-22d16971337b'
});
  • 3 : 上記の手順1で生成したアプリIDを含め、MobileAnalyticsManagerをインスタンス化します。セッションイベントは自動的に記録され、クライアントは10秒ごとにAmazon Mobile Analyticsにイベントを送信します。
var options = {
    appId : MOBILE_ANALYTICS_APP_ID   //Required e.g. 'c5d69c75a92646b8953126437d92c007'
};
mobileAnalyticsClient = new AMA.Manager(options);

手動でイベントの送信を強制するには次の呼び出しを行います。

mobileAnalyticsClient.submitEvents();

追加のオプション

カスタムイベント

オプションとして、カスタムイベントを追加して、追加情報をキャプチャすることができます。

mobileAnalyticsClient.recordEvent('CUSTOM EVENT NAME', {
        'ATTRIBUTE_1_NAME': 'ATTRIBUTE_1_VALUE',
        'ATTRIBUTE_2_NAME': 'ATTRIBUTE_2_VALUE'
        /* ... */
    }, {
        'METRIC_1_NAME': 1,
        'METRIC_2_NAME': 99.3
        /* ... */
    });

セッションの設定

デフォルトの設定ではセッションは10分間続きますが、optionsオブジェクトにsessionLengthを含めることで、Mobile Analytics Managerを初期化するときにこのデフォルト設定を上書きすることができる。

var options = {
    appId : MOBILE_ANALYTICS_APP_ID, 
    sessionLength: 300000   //ミリ単位でのセッションの長さです。この場合は、5分になります。
};
mobileAnalyticsClient = new AMA.Manager(options);

セッションのタイムアウトは、セッションの継続を可能にするように更新できる。

//以下の記述で、現在のセッションが5秒語に期限切れになります。
mobileAnalyticsClient.resetSessionTimeout(5000); 
    
//以下の記述で、初期化中に指定された時間を使用して、現在のセッションの有効期限がリセットされます。 
//デフォルトの設定をそのまま使用するとセッションは10分後に期限切れになります。 
mobileAnalyticsClient.resetSessionTimeout();

アプリの詳細をイベントに追加する

SDKを初期化するときに、追加のアプリケーションの環境と詳細をoptionsオブジェクトに追加するとこができます。これらの詳細はキャプチャされ、すべてのイベントに適用され、自動エクスポートを使用してデータのカスタム分析を行う場合に使えます。

 var options = {
    appId : MOBILE_ANALYTICS_APP_ID,       //Required e.g. 'c5d69c75a92646b8953126437d92c007'
    appTitle : APP_TITLE,                  //Optional e.g. 'Example App'
    appVersionName : APP_VERSION_NAME,     //Optional e.g. '1.4.1'
    appVersionCode : APP_VERSION_CODE,     //Optional e.g. '42'
    appPackageName : APP_PACKAGE_NAME,     //Optional e.g. 'com.amazon.example'
    make : DEVICE_MAKE,                    //Optional e.g. 'Amazon'
    model : DEVICE_MODEL,                  //Optional e.g. 'KFTT'
    platform : DEVICE_PLATFORM,            //Optional valid values: 'Android', 'iPhoneOS', 'WindowsPhone', 'Blackberry', 'Windows', 'MacOS', 'Linux'
    platformVersion : DEVICE_PLATFORM_VER  //Optional e.g. '4.4'
};
mobileAnalyticsClient = new AMA.Manager(options);

バイスの詳細が特に指定されていない時は、AmazonMobileAnalyticsがUser-Agentヘッダー値に基いてこれらの値を予測し指定します。これらの値は、使用できる場合初期化中に指定したほうがよいです。

詳しいドキュメント

詳しいドキュメントと追加の設定は以下のリンクから参照してください。 https://aws.github.io/aws-sdk-mobile-analytics-js/doc/AMA.Manager.html

ネットワーク構成

Amazon Mobile Analytics JavaScript SDKは以下のエンドポイントにリクエストを送信します。 * イベントを使う場合 : https://mobileanalytics.us-east-1.amazonaws.com * Cognito認証の場合: https://cognito-identity.us-east-1.amazonaws.com - このエンドポイントは、アイデンティティが作成された領域に基いて、変更されることがあります。 * ほとんどのフレームワークは、*.amazonaws.comですべてのAWSエンドポイントをホワイトリストに登録することで両方のエンドポイントをホワイトリストに登録することができます。