Scalazのバイナリ互換性とmigration-manager

べつにバイナリ互換の話はScalazに限らないですが、Scalazが7.0.xにおいてmigration-managerを使ってバイナリ互換を保つことを実践している数少ないライブラリで、
「どうするとバイナリ互換が崩れるのか」「逆に、バイナリ互換が崩れない変更とは?」
というノウハウがちょっと溜まったので簡単にまとめ。


まず簡単にmigration-managerの概要 https://github.com/typesafehub/migration-manager

migration-managerとは、Scala自体を作ってるtypesafe社が公開してるライブラリです。略してmimaと呼ばれることが多いです。バイナリ互換をチェックするものです。実際「Scala2.10.1とScala2.10.2がバイナリ互換か?」などをこれ使ってチェックしてます。sbt pluginもあります。*1



Scalazでは、7.0.xからmimaでバイナリ互換のチェックをしてます。(それ以前はやったことない)
mima使ってバイナリ互換維持しようとしてるライブラリは、現状では数えるくらいしかない気がします。自分が知ってるのは、あとplayframeworkでしょうか。ほかになにかあったっけ?あとで判明したら追記するかも github上を検索すると、数十件でてきますね


で、7.0.0をリリースしたあと、実際バイナリ互換維持しようとして、ちょっとだけ問題がでてきました。

  • traitにメソッド追加するとバイナリ互換崩れる*2 *3
  • sealed traitでもmimaはバイナリ非互換を検出する
    • ただし、そのsealed traitを継承したclassなどがさらに外部から継承できるようになっていない*4場合は、実質バイナリ互換。ただ、Javaからsealed traitが継承できてしまうので、そういうのを考慮して、mimaは安全側に倒してsealed traitへのメソッド追加はバイナリ非互換にしてるらしい?(この辺ちょっと自分の理解も曖昧かも)

というわけで、現在Scalazは7.1.0 finalに向けて開発中ですが、
「7.1.0 final をだした後に、7.1.0 と 7.1.1の互換性を保ちつつ、メソッド追加をできるだけやりやすくするため」
に、
「いままでtraitだったものでも、classに変更できるものはclassにする」
という方針がとられています。*5


あと、「sealed traitへのメソッド追加」の問題に関しては、mimaのsbt pluginは例外としてfilterを設定できるので、現状こんな感じになってます。
https://github.com/scalaz/scalaz/blob/v7.0.2/project/build.scala#L161-L176

さらに、"series/7.0.x" というbranchでは、travisでmimaのチェックが走るようになっています




現状大まかにまとめると、この程度でしょうか。
ただ、「"バイナリ互換を維持するライブラリ"を作る人」以外は、この知識ほとんど必要ないです。が、「"バイナリ互換維持するライブラリ"を作る人」にとってはややこしい問題で、まだmima利用者が少なく知見が溜まっていないので、面倒です。

さらに分かったことがあればまた別の機会に書くかもしれません。

*1:なぜかswingのGUIとかもあるらしいです。使ってる人いるのか・・・?

*2:scalazでは(classにできるとしても)traitが大量に使われている

*3: ところで、Java8からinterfaceのデフォルト実装ができるようになるけど、同じ問題発生するのだろうか?

*4:つまり、そのsealed traitを継承してるのが、privateだったりfinalだったり、もしくはobjectだったり

*5:詳しい事情とか経緯はwiki https://github.com/scalaz/scalaz/wiki/Roadmap#classify-traits とかMLとかissue探せばいっぱいでてきます