Scala書いた
こんにちは、びしょ〜じょです。 最近はヴァーチャルユーチューバーに脳を破壊されてしまってほぼ毎日観てます。 剣持力也くんが好きです。
1. はじめに
Scalaに入門するついでに、文献…というかPDFを管理するシステムを作った。
配色などはともかく、個人的には使いやすくなっとるんじゃないでしょうか。 もちろんボクが作ったので、どこを押すと何が起きるかは100%分かっているため、他の人にとって使いやすいかどうかはよくわかりませんが…。
2. Scala
case class
でADTのようなものを定義できる。
トレイトT
を継承すれば型T
のデータコンストラクタでまとめられるということでいいのかな。
trait T[A] {}
case class K1[A](a: A) extends T[A]
case class K2[A](a: A) extends T[A]
val s : Seq[T[Int]] = Seq(K1(0), K2(2))
パターンマッチの実態はクラスオブジェクトのunapply
メソッドで、カスタマイザビリティがなかなか高い。
case class
はapply
/unapply
メソッドが自動でクラスのメンバになる。
class K1[A](val a: A) extends T[A] {}
/*
Javaでいうところの
class K1<A> {
K1<A>(a: A) {
this.a = a;
}
}
でいいのか?
*/
object K1 {
def apply[A](a: A): K1[A] = new K1[A](a)
def unapply[A](k1: K1[A]): Option[A] = {println("match"); Option(k1.a)}
}
K1(0) match { case K1(x) => x } // prints "match" and returns 0
引数をクラスのメンバにしてくれたりもしているようだ。
またimplicit
に定義されたメソッドにより安全な暗黙の型変換を定義できるのも面白い。
object Conv {
implicit def fromStringToInt(s: String): Int =
s.toCharArray.toSeq.map(_.toInt).fold(0) { _ + _ }
def f(i: Int) = println(i * 10)
def run() = f("aiueo")
}
// convert "aieuo" to Seq(97, 105, 101, 117, 111) ~> (folded)
Conv.run // 5310
型変換が必要そうなところで型の合う直近のimplicit
メソッドを参照して変換する、のかな(わかってない)。
またスコープはメソッドの定義されたクラスを超えない。
ただし継承を使うと継承先のクラス内でも型変換が起こる。
Javaみもあって少々つらいときがある(String
集合にnull
が含まれたり、Javaいライブラリを使うとnull
が混入する)が、
概ねモダンでOOPとしつつFunctional Programmingもしっかりできる言語でなかなか良い。
ビルドツールのsbtもカスタマイザビリティが高く、開発もわりとスムーズにいく。
3. Play web framework
Java/Scalaで使えるWebフレームワーク。 結構面白いんじゃないでしょうか、Webフレームワークを触ったことがなかったので評価できませんが。
3-1. ハマりポイント
現在はv2.6が最新だが、2.4、2.5、2.6で非互換な部分がいくつかあるが、インタネッツの記事にバージョンの表記がなかったりして、メソッドがないやんとか型合わないやんとかがあった。
4. Pnyao
本題。 PDFのメタデータにはiTextを使っている。 最初は何も考えずPDFメタデータの読み書きだけを実装したのでプロジェクトが分かれている。
(root)
+ ...
+ (subprj)
のようなプロジェクトの構成になっとるとき、rootのbuild.sbtでサブプロジェクトをガバっとやる。
lazy val subprj = project in file("subprj")
lazy val root = (project in file("."))
.aggregate(subprj)
.dependsOn(subprj)
.dependsOn
でビルドの依存関係をやっておる。
他はなんか読んでください。
あとはサーバのイベントにアクションをフックするところが面倒だった。
クラスにDIしてApplicationLifecycle
をランタイムに突っ込んでいく。
@Singleton
class Pnyao @Inject()(lifeCycle: ApplicationLifecycle) extends PnyaoService {
このオブジェクトにPlayサーバのイベント時に発火するアクションをフックできる。
lifeCycle.addStopHook { () => Future.successful(work) }
sys.addShutdownHook {() => work}
JVMの終了にもフックしている。 Playサーバのイベントにフックしてたのは、sbtシェル上で起動/終了をしてたから必要であって、後述のとおりSystemdで起動/終了をまかせるようにしたのでもはや不要かも。
この場合JVMの終了かPlayの終了かどちらかだけでアクションを発火してほしいので、lazy val
として定義すればいい。
他はもうないな。 JSONを扱う部分があり、circeとPlayのJSONライブラリという2つの変換器が混在している。 これは先述の通りPDFメタデータ扱う部分だけ最初に実装したことに起因している。
5. アッピケーション化
sbt-assemblyでプロジェクトをjarに固めて実行するようにした。 さらにサーバをsystemd serviceとして起動/停止できるようにした。
6. 所感
楽しかった、が、Webブラウザでのクリックイベントなどはジャバスクを書かざるをえなかったのがつらい。 ScalaJSをPlayがサポートしてくれればいいのかもしれない、ScalaJS書いたことないんですけど。 Scalaはちゃんとかいたのがここ3、4週間くらいなので、もうすこしやっていきたい。
7. おわりに
人生おわった…。