java-object-diff を F# に移植

久しぶりの移植芸です。

移植理由

Persimmonの開発に必要みたいな話になり、かつ移植する時間のありそうな人間が他にいなかったとかそういう。

先達

https://github.com/SQiShER/java-object-diff

さすがJavaさん、探せばだいたいでてくる。

成果物

https://github.com/pocketberserker/FSharp.Object.Diff

3月中旬に始めたので、まともに動かせるようになるまで二か月かかった計算に。 まぁ、FSDNやら業務のあれそれやら遊びといったことを考えると妥当な数値っぽい感じがしますね?

あれそれ

移植元はJavaなので、Javaを前提に実装されているわけですね。 なので、当然仕様の差が出てきます。

  • Javaにプロパティはないが、.NETにはある
    • Javaメソッドから値を取得する形になっていたので、そこをだいたいプロパティに置き換え
  • フィールドのdiff取得機能はomit
    • F#では必要ないと思うんだ

このあたりは仕様の話。

ここから実装の時に遭遇した(または現在進行形)の問題。

  • Javaの一部のMapだとkeyにnullが突っ込める
    • .NETのDictionaryは突っ込めないんだよねーとなってちょっと悩んだ
  • Map.getでnullが返ってくる
    • そういえばそんなんだったなーとか
  • null nullしている
    • Javaだもんね、仕方ないね
    • 移植を優先した関係で F# 版もnull nullしてます…
    • Some nullさん…
  • F#のレコードがほぼ使えない
    • 移植元がmutable前提なのでしょうがない
    • abstract classが出てくるとレコード使えないしね
  • .NETのコレクションの基底となるインターフェースは?
    • read onlyで良いならIEnumerable(ジェネリックじゃないやつ)
    • じゃあ、書き込みしたい場合は?
    • 今回は仕方なくIList(ジェネリックじゃないやつ)をwrite部分での基底に採用している
  • .NETの連想配列の基底となるインターフェースは?
    • IDictionary(ジェネリックじゃないやつ)はあまり役に立たない。けど楽だったのでいったんこれを採用している。
    • IDictinary<'T, 'U>を基底にしたいところだが、type eraseされないのとkeyやvalueの型情報がその場にない関係でパターンマッチで型チェックみたいなことはできない。
    • もしかして:ひたすらリフレクションで頑張るしかない…?
    • JavaさんはCollection<?>とかMap<?>とかやってて「ワイルドカードええ子や」みたいな心境になった
  • 比較演算子とか
    • Java==Object.ReferenceEqualに、equals=に読み替えるの、わりと難しい
    • これをミスして何度か嵌った
  • equality制約を回避したい
    • F#だと=を使うと型パラメータがequality制約を要求する
    • が、diffライブラリなのでそんな制約は外したい
    • ので、比較する部分でbox xを挟んでobjで比較するバッドノウハウを駆使して制約を回避している

まとめ

nullよりもコレクションの仕様の差やtype eraseかそうでないかなど、もっと別な場所で精神が削れました。