play2でフィルター処理っぽく書きたい

ソースも全然できてませんが、play2でActionを実行する前に色々とフィルター処理がかけたらいいなーと思い、色々と試行錯誤してます。

イメージ的には、こんな感じ。(フィルター処理は適当)

def index =
  Filter { req =>
    req.getQueryString("key1") match {
      case Some(_) => continue // continueで次のフィルターに処理が移る。
      case None    => BadRequest // continue以外のResultの場合はそれが返る。
    }
  } >> Filter { req =>
    req.getQueryString("key2") match {
      case Some(value) => continueWith(value) // continueWithで渡した値は次のFilter/Actionに渡る(出来ればタプルで)
      case None        => BadRequest
    }
  } >> Action { (value2) => request =>
    Ok("key2 is %s" format value2)
  }

実現できないかなぁ・・・。

Play2のevolutionスクリプトのコメントを表示するプラグインを書いた

この記事は Play or Scala Advent Calendar 2012 12/8 分の記事です。

前は @lyrical_logical さんの View/Context Bounds の制約 です。
次は @gakuzzzz さんです。


Play2には「evolutions」という、DBスクリプトを管理してくれる機能があります。他の仕組みでDDL等を管理していないのであれば、これを利用しているのではないでしょうか。

スクリプトには、スキーマの変更情報をSQL直で記述できるので、RoRのmigrationよりもいいなーと思っているのですが、ファイル名が気になっていました。

migrationでは、ファイル名を201212080149_create_users_schema.rbのように自由に決めることができ、ファイル名からある程度どのようなスクリプトかが判断できます。

しかし、evolutionsでは、ファイル名が1.sql, 2.sqlというように固定となっており、ファイル名からスクリプトの中身を判断できません。

そのため、Playプラグイン(sbtプラグイン)の勉強をかねて、evolutionsスクリプトに記載されているコメントを一覧で表示するプラグインを書いてみました。

rabitarochan/play2-evolist-plugin

どんなもの?

playコンソールでevolistというコマンドを実行すると、スクリプトのコメント部分を列挙するプラグインです。

スクリプトには#で始めるとコメント行として認識されるため、スクリプトからその行を取得し、一覧として表示しています。

例として、以下のようなスクリプトがあったとします。

(1.sql)
# Person schema

# !Ups
create sequence person_id_seq;
create table person (
  id integer not null default nextval('person_id_seq'),
  name varchar(40)
);

# !Downs
drop table person;
drop sequence person_id_seq;
(2.sql)
# Group schema with add column to User schema

# !Ups
create sequence group_id_seq;
create table group (
  id integer not null default nextval('group_id_seq'),
  name varchar(40)
);
alter table user add column group_id integer;

# !Downs
alter table user drop column group_id;
drop table person;
drop sequence person_id_seq;

この場合、スクリプトを実行すると以下のような出力が得られます。

[testapp] $ evolist
database: default
1: Person schema
2: Group schema with add column to User schema

もしコメントが長い場合、次の行に行った場合に適切なインデントを入れてあげるようにしています。( 現在はコンソールが 80 文字と決め打ちをしています。この部分はちゃんと実装します・・ )

[testapp] $ evolist
database: default
1: Person schema
2: Group schema with add column to User schema
3: This script is fix to the user join to some groups. Define relation table for
    user and group.

あ、一文字ズレてる!!

また、以下のようにDB名を指定することもできます。

[testapp] $ evolist -db test
database: test
...

まとめ

なにもまとまりませんが・・・。

ネタを探しつつ、ふと前から不便に思っていた部分を実装してみました。実装を初めてまだ時間が経ってなく、絶賛実装中です。ドキュメントもテストも整備されていません。

evolutionスクリプトを使ってらっしゃる方で同じようなことを思っていた方は、一度試していただき、いろいろとツッコミを入れていただければと思います。

sbtプラグインについても少しずつ分かってきたので、ネタが見つかったらプラグインの作り方を書こうと思います。

evolutionsスクリプト、ファイル名じゃ何がなんだか分からないからプラグインを書いてみたよー。というお話でした。

ScalaでSPI(サービスプロバイダインターフェース)を使う

Scalaでも、JavaのSPI(サービスプロバイダインターフェース)はもちろん使えます。

SPIは、AsakusaFrameworkで知って、Javaではいろいろ遊んでみたのですが、Scalaでは試してなかったので、試してみました。

ソースはこちら。
https://github.com/rabitarochan/scala-spi-test.git

インターフェースは?

Javaでは、インターフェースの実装クラスを [ META-INF/services/${インターフェースのFQCN} ] というファイルに書きます。

Scalaでは、インターフェース名の部分をトレイト名にしてあげることで、同じように使えました。

呼び出し方

ほぼJavaと同様ですが、Service.providers で得られるIteratorジェネリクスでないため、キャストが必要なようです。

package com.rabitarochan.parent

import java.util.Iterator
import scala.collection.JavaConversions._
import sun.misc.Service

object Main extends App {
  val services = Service.providers(classOf[PrintTrait]).asInstanceOf[Iterator[PrintTrait]]

  val message = "Hello world"

  services.foreach(p => p.print(message))

}

Scala版SPI?

github内を[ META-INF/services ]で検索してみたところ、apache/activemq-apolloというプロジェクトで[ ClassFinder ]というソースを見つけました。

https://github.com/apache/activemq-apollo/blob/5b8879cd8a681bcd91c5b661d26ccd51783bd06b/apollo-util/src/main/scala/org/apache/activemq/apollo/util/ClassFinder.scala

SPIの内部もこのような実装になってるのかなと思いつつ、他にも同じようなものがないかをいろいろ探しています。

パーサコンビネータの処理が終わってくれない!!

パーサコンビネータの勉強も兼ねて、ScalaでWikiエンジンを作ろうと思ってます。

パーサコンビネータでいろいろ試していますが、以下のコードは、私の環境では処理が終わってくれません。
なんでだろう・・・。

import scala.util.parsing.combinator.RegexParsers

case class Indent(i: Int)
case class Text(s: String)
case class Line(i: Indent, t: Text)

object Parser extends RegexParsers {

  def indent: Parser[Indent] = """^\s*""".r ^^ {in => Indent(in.length)}
  def text: Parser[Text] = """[^\n\r]*""".r ^^ {s => Text(s)}
  def eol = """\n?\r?""".r

  def line: Parser[Line] = indent ~ (text <~ eol) ^^ {case in ~ te => Line(in, te)}

  def parse(s: String) = parseAll(rep(line), s)
 
}

object Main extends App {
  val s = """  hoge\n  fuga"""
  
  // 自分の環境では、この処理は終わらない。
  // 戻り値は List(Line(Indent(2), Text(hoge)), Line(Indent(2), Text(fuga))) を想定しているが・・・
  Parser.parse(s)
}

とりあえずやりたいことは、

  • indent で行頭のスペースの数を取得したい
  • text で本文を取得したい
  • eof で行を区切る

なのですが、うまく処理が終わってくれません。

Scalaはコンストラクタもカリー化できる!の続き

前回記事の続きです。

scalaはコンストラクタもカリー化できる! - rabitarochanの日記

ブログへのコメントや、Twitterのリプライでいろいろ教えていただきました。
@さん、@さん、ありがとうございました!

一部の引数を指定したコンストラクタ

一部の引数を指定したコンストラクタについては、前回失敗した書き方を少し変えることで可能でした。

scala> class Person(val name: String)(val age: Int)
defined class Person


// 失敗・・・
scala> val p1 = new Person("rabitarochan") _
<console>:8: error: missing arguments for constructor Person in class Person
       val p1 = new Person("rabitarochan") _
                ^

// 成功!!!
scala> val p2 = new Person("rabitarochan")(_)
p2: Int => Person = <function1>

_ と (_) って何が違うの!?という疑問については前回記事のコメントか、@kmizuさんのブログ、

_ と _ の違い - ((プログラミング | 形式) 言語) について書く日記

にかかれてます。

case class の場合

例では普通のクラスを利用していましたが、case class を利用した場合は、以下のとおりにインスタンス化できます。

scala> case class Person(name: String)(age: Int)
defined class Person

scala> val p1 = Person("rabitarochan") _
p1: Int => Person = <function1>

case class を定義すると、コンパニオンオブジェクトも自動的に定義されます。
この場合、コンパニオンオブジェクトの apply メソッドに対して部分適用された関数が返ります。

手動でコンパニオンオブジェクトを定義した場合でも、同じ動きを確認できました。

// コンパニオンオブジェクトの定義は :paste してから。
scala> :paste
// Entering paste mode (ctrl-D to finish)

class Person(val name: String)(val age: Int)
object Person {
  def apply(name: String)(age: Int) = new Person(name)(age)
}

// Exiting paste mode, now interpreting.

defined class Person
defined module Person

scala> val p1 = Person("rabitarochan") _
p1: Int => Person = <function1>

まとめ

new Hoge("hoge")(_) と記述することで、カリー化したコンストラクタについても、部分適用(というの??)した関数を取得することができました。カリー化した場合の制限だと思っていたものも解決しました。

あとは、@kmizuさんのブログを読んで理解するだけです。
言語仕様も読まないとなぁ・・・。

Scalaはコンストラクタもカリー化できる!

タイトル通りなのですが、Scalaではコンストラクタもカリー化できます。

scala> class Person(val name: String)(val age: String)
defined class Person

だからといって、一部の引数を指定したコンストラクタを取得する方法がない (見つけれていない。あったら教えてください!) ので、あまり使い道はない気がします・・・。

scala> val p1 = new Person("rabitarochan")(24)
p1: Person = Person@603d2b3

scala> val p2 = new Person("rabitarochan") _
<console>:8: error: missing arguments for constructor Person in class Person
       val p2 = new Person("rabitarochan") _

javapの結果は・・・

javap をしてみると、普通のコンストラクタとして定義されていました。

D:\>javap Person
Compiled from "Person.scala"
public class Pesron extends java.lang.Object{
    public java.lang.String name();
    public int age();
    public Person(java.lang.String, int);
}

だからといって、カリー化ではないコンストラクタではインスタンス化することはできません。

scala> val p3 = new Person("rabitarochan", 24)
<console>:8: error too many arguments for constructor Person: (name: String)(age: Int)Person
       val p3 = new Person("rabitarochan", 24)
                ^