Play Framework 2.9.0 に送ったPull Request
そろそろPlay Framework 2.9.0が出そうですね。ということで自分の送ったPull Requestをまとめてみました。
大きいところだとTwirlのScala 3対応ですね。いい感じにエラーメッセージ出すのにdottyとかsbtまで調べることになって沼でしたがたぶんなんとかなりました。
playframework/playframework
- Let the Java Form validator use the locale selected by Play's i18n. #11736 https://github.com/playframework/playframework/pull/11736
- Update caffeine, jcache to 3.0.1 #11444 https://github.com/playframework/playframework/pull/11444
playframework/twirl
- Added support for switching the default "import" between Scala 2 and Scala 3 #613 https://github.com/playframework/twirl/pull/613
- Handle an exception from scalameta when parsing template args fail #512 https://github.com/playframework/twirl/pull/512
- Call toIndexedSeq explicitly to reduce warnings #487 https://github.com/playframework/twirl/pull/487
- Add a testcase for By-name parameters #485 https://github.com/playframework/twirl/pull/485
- CI with GitHub Actions (using playframework/.github) #483 https://github.com/playframework/twirl/pull/483
- Add syntax related fixes for Scala3 cross build #479 https://github.com/playframework/twirl/pull/479
- Update scalafmt to 3.3.1 #478 https://github.com/playframework/twirl/pull/478
- feat(scala3): WIP working on scala 3 cross compilation. #390. #461 https://github.com/playframework/twirl/pull/461
sbt/zinc
- Display error messages based on the transformed positions when source mapping is perfomed #1082 https://github.com/sbt/zinc/pull/1082
おまけ
今年もかぼちゃ育てたので見て
ScalaMatsuri 2022でCats Effect 3の話をしました
純粋関数型スタイルのプログラミングは私はあまりやって来なかったんですが、たまには違ったことをやってみようかなと思って発表してみました。年末年始にTypelevelの技術スタックをいろいろ見ててhttp4sとかさわってみてたりしたんですが、その過程でCats Effect 3が気になった感じです。
やはり純粋関数型スタイル自体にはそんなに興味ないんですが、Cats Effectのランタイムは面白くて、それに乗っかるためのフレームワークとして純粋関数型スタイルが要求されるというのはそんなに悪くないなと思いました。あと資料作成や準備の過程でCats Effectの作者の方(Danielさん)のトークを何本かYouTubeで見たんてすが面白すぎる。全部面白い。
Scalaは人気のピーク(ピークと言うほどでもない)を越えてしまって少しコミュニティの熱量は下がってるのかなって気がしますね。とは言え個人的には他に乗り換えたい言語もなく、今やるならRustなんだろうけどRustでWebアプリとか作る気にはならないしなあ、みたいな微妙な気持ちです。まあしばらくはplayframeworkのほうをやっていきます。あとPHPとかですかね?(冗談じゃなくて今本業PHPなんです)
Play Frameworkの開発チームに入れてもらいました
Play FrameworkがLightbendの手から離れる という発表が昨年の10月にあったんですが、それをきっかけにやりとりして Play FrameworkのGitHub Team に入れてもらいました。 しばらく待ちの状態だったんですが、先週くらいから動き初めたところです。とりあえず当面はTravis CIからGitHub Actionsへの移行やScala3対応になるのかなと思います。
PlayはLightbendから離れる決定をしたあとからOpenCollectiveで寄付を募っています。 寄付してくれてる方々を見ると日本の方も多いですね。ありがとうございます。 mkurzさんからも感謝のメッセージをいただいてます。引き続きご支援お願いします 🙏
// mkurzさんは私が何か働きかけたように思ってそうだが何もしていない Also, a high proportion of the current Play backers on Open Collective are Japanese, so it seems you reached out to the Japanese community already? If so, and if you have a mailing list or similar set up, would you mind to say thank you to them from me and the other contributors ;)
Emacs + Metals + lsp-mode でScalaを書く
最近重たい処理走らせてる時とかにIntelliJが遅くてつらかったのでEmacsでもある程度コード読んだり書けたり出来るようにしました。 最近はLSPが人気あるようでEmacsでもlsp-modeというのが結構使われているみたい。Scalaの場合はLSPとしてMetalsを使うことになります。
設定
- lsp-modeが入っていればとりあえずOK
- flycheckを入れてコンパイルエラーの表示が出来るようにしましょう
- lsp-uiを入れてなんかUIからポチポチ出来るようにしましょう
- companyをコード補完機能に使いましょう
(use-package flycheck :init (global-flycheck-mode)) (use-package lsp-mode :hook (lsp-mode . lsp-lens-mode) (scala-mode . lsp) :config (setq lsp-prefer-flymake nil)) (use-package lsp-ui) (use-package company :hook (scala-mode . company-mode) :config (setq lsp-completion-provider :capf) (setq company-minimum-prefix-length 1))
そんなにガリガリ設定書かなくても割といい感じになるのがすごい。
使い方
- インストールはMetalsのドキュメントでどうぞ。
M-.
,M-,
で定義元にジャンプしたり戻ったりできる。- キーバインドの一覧は https://emacs-lsp.github.io/lsp-mode/page/keybindings/
- 一覧に乗ってないけど
s-l g e
でエラー一覧表示ができた。ドキュメント更新のpull req送った方良いのかな?
トラブルシューティング
デバッグはとりあえず *lsp-log*
バッファを見てみると何か分かる。Metalsの場合は .metals/metals.log
でも良し。
必殺再起動は M-x lsp-restart-workspace
。
ハマったとこ
補完がうまく行かないなあ、Any型と判定されるなあとか思ってたんだけど、monorepo的なリポジトリでworkspaceのルートが誤判定されてちゃんとプロジェクトが読み込まれていないのが原因でした。M-x lsp-workspace-folder-add
で設定したら補完ができるようになりました。
あと大きなプロジェクトで遅くなるけどファイルをウォッチするか?遅くなるけど?と聞かれた時に読み込まないようにしたら補完が効きませんでした。ウォッチしないとダメなのかな。とりあえずウォッチすることにして、遅くなってから考えることにしました。
制限
今のところ機能的にここが限界だろうなあというのが2つ。
感想
コード読んだり、調査だったり、ちょっとした修正には十分な機能なので嬉しい。まあガッツリコード書くときはIntelliJだけどそれ以外を軽い環境でカバー出来るのは良いですね。
個人用のscala-stewardを動かす
scala-stewardを使い始めました。
ちょっと使うだけならscala-stewardのrepos.mdにプルリクエスト送るだけで良くて便利なんですが、個人の雑多なプロジェクト全てにそれをやるのはちょっと気が引けるので自前でも動かせるようにしました。
今だとGitHub Actionsを使うのが手取り早いんだけど、使うトークンの権限の問題でちょっとややこしかったです。そのあたりは吉田さんのブログが詳しいです。
xuwei-k.hatenablog.com xuwei-k.hatenablog.com
自分は次の形にしました。
- scala-ojisanという個人用のGitHub Appを作ってそのトークンを使う
- 権限は自分のリポジトリに対してcontentとpull-requestのread/writeがあれば良さそう。
- その辺も吉田さんのブログに書いてあります。
- scala-stewardは各リポジトリに設定するのではなく、1つのリポジトリで管理する
で、試行錯誤の上なんとかscalaおじさんがプルリクくれるようになりました。
Update sbt to 1.4.7 by scala-ojisan · Pull Request #3 · tototoshi/hello.g8 · GitHub
sbtn 便利かも
sbt 1.4.0 から sbtn という機能が追加されました。
https://eed3si9n.com/sbt-1.4.0-beta
Native thin client sbt 1.4.0 adds an official native thin client called sbtn that supports all tasks. > https://github.com/sbt/sbtn-dist/releases/tag/v1.4.0-RC2
This lets you run sbt tasks from the system shell as:
$ sbtn compile
$ sbtn shutdown
The native thin client will run sbt (server) as a daemon, which avoids the JVM > spinup and loading time for the second call onwards. This could an option if you > would like to use sbt from the system shell such as Zsh and Fish.
Remember to call sbtn shutdown when you're done!
Later on, sbt script will also support --client option to run the native thin client:
$ sbt --client compile
$ sbt --client shutdown
#5620 by @eatkins
sbtn 一度起動すると sbt をデーモンとして常駐させ、以降は sbt を起動するのではなくすでに起動している sbt に接続しに行くため起動時間が短縮されます。
emacs 使ってる人には emacsclient ですって言えば通じそう。あと sbt と同じ Scala のビルドツールである mill も似たようなことやってますよね。mill 最近使い始めたばかりでよく知らないけど。
sbt は起動が遅いので対話シェルを起動させておいてそれを利用するのが普通でしたが、sbtn によってシェルを起動しっぱなしにする必要がなくなります。私は気づくと tmux のウィンドウが無限に開いていて、sbt 起動してるのはどこのウィンドウだっけ??と探して回るのを毎日やっているのですが、sbtn を使うと今いる window でコマンド打てばいいのでそれがなくなりそうです。
あと他のシェルスクリプトとの連携がしやすくなるのも良いですね。今までは
#!/bin/sh sbt compile set_up_database sbt test tear_down_database
みたいなスクリプトを書くのは sbt の起動が遅いので辛く、シェルスクリプト側の処理を sbt の方に組み込むことになり、sbt 職人が build.sbt に
lazy val setUpDatabase = taskKey[Unit]("setup_database...") lazy val tearDownDatabase = taskKey[Unit]("teardown_database...") ...
とか書いてドヤッって感じだったんですが、sbtn を使えば
#!/bin/sh sbtn compile set_up_database sbtn test tear_down_database
でもう十分ですね。build.sbtのダイエットができそうです。
追記
- project/build.properties のバージョンを上げるのに加えてsbtコマンドのバージョンも 1.4 に上げましょう。
- sbtnでPlayのプロジェクトをrunするとプロジェクトは起動するけれどsbtn側で標準入力を受け取れないっぽい。(Playプロジェクトのログを受け取る前にデタッチされてそう?)
Modules should be fast and side-effect free と Guice は言っているがPlay Frameworkはそうなってはいない話
Modules should be fast and side-effect free
Modules should be fast and side-effect free(モジュールは速くて副作用なしであるべき)と Guice の Wiki にはあります。
これは Guice モジュールの中で DB に接続したりスレッド開始したりするなという話で、これに従うのならば Guice の Module (extends AbstractModule したクラスの中とか)の他にも Module を構成する各クラスのコンストラクタの中でも副作用を起こしてはいけないことになります。
副作用を起こしてはいけない理由としては
- Modules start up, but they don't shut down.
- Modules should be tested.
- Modules can be overridden.
を Guice は挙げています。
Guice の意図に沿ったコードを書くと初期化処理、終了処理のためのメソッドをコンストラクタ以外に用意して、アプリケーションの起動、終了時になんらかの形で呼んであげることになります。
public interface Service { void start() throws Exception; void stop(); }
public static void main(String[] args) throws Exception { Injector injector = Guice.createInjector( new WebserverModule(), ... ); Service webserver = injector.getInstance( Key.get(Service.class, WebserverService.class)); webserver.start(); addShutdownHook(webserver); }
サンプルでは単純ですが徹底するとなると例えば設定ファイルを読み込むとかも禁止となるのでなかなか厳しい制限にも感じます。(実際には設定ファイル読み込みとかは OK にして、スレッド開始とかは NG にする、くらいが落とし所かも?)
Play Framework の場合
Play Framework は Guice を使っていますが、Guice のこの主張を完全に無視して副作用をバリバリにつかっています。Play を構成するクラスは大体コンストラクタでスレッド開始したり設定読み込んだりとやって、終了処理はコンストラクタ内で ApplicationLifecycle
に登録します。
@Singleton class NanrakanoService @Inject()(lifecycle: ApplicationLifecycle) { val resource = new NanrakanoBackgroundResource().start() lifecycle.addStopHook(() => Future.successful(resource.shutdown())) }
Guice の wiki を見たあとでこれを見るとギョッとするんですが実際のところそんなに困ることはないです。
とはいえやっぱり時々は困るし、Guice の推奨しない方法で Guice をヘビーに使っているというのは精神衛生上あまり良くはありません。そして実はコンストラクタで副作用を起こすようになったのは Play が Guice を使い始めた Play 2.4 からの話で、Guice を使っていなかった Play 2.3 以前の Plugin
の仕組みでは副作用のためのメソッドが分かれていたのです。
package plugins import play.api.{Plugin, Application} class MyPlugin(app: Application) extends Plugin { val myComponent = new MyComponent() override def onStart() = { myComponent.start() } override def onStop() = { myComponent.stop() } override def enabled = true }
Guice を使う前は副作用が分かれていたのに、互換性を壊してまで Guice を使い始めたら副作用起こすようになったというのがなんかチグハグだなあって思います。