私は現在、より機能的なプログラミングスタイルを低レベル(LWJGLベース)のGUI開発を含むプロジェクトに適用しようとしています。明らかに、そのような場合、現在のバージョンでは変更可能な多くの状態を持ち歩く必要があります。私の目標は、副作用としての状態変化を回避するために、最終的に完全に不変の状態になることです。私はしばらくの間scalazのレンズと状態モナドを研究しましたが、私の主な関心事は残っています:これらの技術はすべてコピーオンライトに依存しています。私の州にはたくさんのフィールドとかなりのサイズのフィールドの両方があるので、パフォーマンスが心配です。
私の知る限り、不変オブジェクトを変更するための最も一般的なアプローチは、aの生成されたcopy
メソッドを使用することですcase class
(これは、レンズが内部で行うことでもあります)。私の最初の質問は、このcopy
メソッドが実際にどのように実装されているかということです。次のようなクラスでいくつかの実験を行いました。
case class State(
innocentField: Int,
largeMap: Map[Int, Int],
largeArray: Array[Int]
)
ベンチマークとその出力を見ると、-Xprof
更新は実際にディープコピーを実行しているように見え、とsomeState.copy(innocentField = 42)
のサイズを大きくするとパフォーマンスが大幅に低下することがわかります。内部的には参照がコンストラクターに渡されるだけなので、新しく構築されたインスタンスが元の状態のオブジェクト参照を共有することをどういうわけか期待していました。デフォルトのこのディープコピー動作を強制または無効にすることはできますか?largeMap
largeArray
copy
コピーオンライトの問題について考えている間、不変データの変更を一種の増分的な方法で(「更新の収集」または「収集」の意味で)保存するFPで、この問題に対するより一般的な解決策があるかどうか疑問に思いました。変更」)。驚いたことに、何も見つからなかったので、次のことを試しました。
// example state with just two fields
trait State {
def getName: String
def getX: Int
def setName(updated: String): State = new CachedState(this) {
override def getName: String = updated
}
def setX(updated: Int): State = new CachedState(this) {
override def getX: Int = updated
}
// convenient modifiers
def modName(f: String => String) = setName(f(getName))
def modX(f: Int => Int) = setX(f(getX))
def build(): State = new BasicState(getName, getX)
}
// actual (full) implementation of State
class BasicState(
val getName: String,
val getX: Int
) extends State
// CachedState delegates all getters to another state
class CachedState(oldState: State) extends State {
def getName = oldState.getName
def getX = oldState.getX
}
これで、次のようなことができます。
var s: State = new BasicState("hello", 42)
// updating single fields does not copy
s = s.setName("world")
s = s.setX(0)
// after a certain number of "wrappings"
// we can extract (i.e. copy) a normal instance
val ns = s.setName("ok").setX(40).modX(_ + 2).build()
今の私の質問は、このデザインについてどう思いますか?これは私が気付いていないある種のFPデザインパターンですか(ビルダーパターンとの類似性は別として)?似たようなものは見つからなかったので、このアプローチに大きな問題があるのではないかと思います。または、不変性をあきらめることなく、コピーオンライトのボトルネックを解決するための標準的な方法はありますか?
get / set / mod関数を何らかの方法で統合する可能性さえありますか?
編集:
copy
ディープコピーを実行するという私の仮定は確かに間違っていました。