3 回答

TA貢獻1868條經驗 獲得超4個贊
拉鏈
Huet的Zipper為不可變數據結構提供了方便的遍歷和“變異”。Scalaz為Stream
(scalaz.Zipper)和Tree
(scalaz.TreeLoc)提供Zippers 。事實證明,拉鏈的結構可以自動地從原始數據結構中導出,其方式類似于代數表達式的符號區分。
但是,這對您的Scala案例類有何幫助?好吧,Lukas Rytz最近制作了一個scalac擴展原型,可以自動為帶注釋的案例類創建拉鏈。我將在這里重現他的例子:
scala> @zip case class Pacman(lives: Int = 3, superMode: Boolean = false)
scala> @zip case class Game(state: String = "pause", pacman: Pacman = Pacman())
scala> val g = Game()
g: Game = Game("pause",Pacman(3,false))
// Changing the game state to "run" is simple using the copy method:
scala> val g1 = g.copy(state = "run")
g1: Game = Game("run",Pacman(3,false))
// However, changing pacman's super mode is much more cumbersome (and it gets worse for deeper structures):
scala> val g2 = g1.copy(pacman = g1.pacman.copy(superMode = true))
g2: Game = Game("run",Pacman(3,true))
// Using the compiler-generated location classes this gets much easier:
scala> val g3 = g1.loc.pacman.superMode set true
g3: Game = Game("run",Pacman(3,true)
因此,社區需要說服Scala團隊,這項工作應該繼續并集成到編譯器中。
順便提一下,Lukas最近發布了Pacman版本,用戶可以通過DSL進行編程。但是,看起來他沒有使用修改過的編譯器,因為我看不到任何@zip注釋。
樹重寫
在其他情況下,您可能希望在整個數據結構中應用一些轉換,根據某種策略(自上而下,自下而上),并基于與結構中某個點的值匹配的規則。經典的例子是為一種語言轉換AST,也許是為了評估,簡化或收集信息。Kiama支持重寫,請參閱RewriterTests中的示例,并觀看此視頻。這是一個激發你胃口的片段:
// Test expression
val e = Mul (Num (1), Add (Sub (Var ("hello"), Num (2)), Var ("harold")))
// Increment every double
val incint = everywheretd (rule { case d : Double => d + 1 })
val r1 = Mul (Num (2), Add (Sub (Var ("hello"), Num (3)), Var ("harold")))
expect (r1) (rewrite (incint) (e))
請注意,Kiama會在類型系統之外執行此操作。

TA貢獻1802條經驗 獲得超10個贊
有趣的是,沒有人添加鏡頭,因為他們是為這種東西制作的。所以,這里有一篇CS背景文件,這里有一篇博客,簡要介紹了Scala中使用的鏡頭,這里是Scalaz的鏡頭實現,這里有一些使用它的代碼,看起來非常像你的問題。而且,為了減少鍋爐板,這是一個為案例類生成Scalaz鏡頭的插件。
對于獎勵積分,這是另一個涉及鏡頭的SO問題,以及Tony Morris 的論文。
鏡頭的重要性在于它們是可組合的。所以一開始它們有點麻煩,但是你使用它們的次數越來越多。此外,它們非常適合測試,因為您只需要測試單個鏡頭,并且可以理所當然地將它們的成分視為理所當然。
因此,根據本答案末尾提供的實現,以下是您使用鏡頭的方法。首先,聲明鏡頭以更改地址中的郵政編碼和人員中的地址:
val addressZipCodeLens = Lens(
get = (_: Address).zipCode,
set = (addr: Address, zipCode: Int) => addr.copy(zipCode = zipCode))
val personAddressLens = Lens(
get = (_: Person).address,
set = (p: Person, addr: Address) => p.copy(address = addr))
現在,組合它們以獲得一個改變一個人的zipcode的鏡頭:
val personZipCodeLens = personAddressLens andThen addressZipCodeLens
最后,使用那個鏡頭來改變raj:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)
或者,使用一些語法糖:
val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)
甚至:
val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)
以下是從Scalaz中獲取的簡單實現,用于此示例:
case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
def apply(whole: A): B = get(whole)
def updated(whole: A, part: B): A = set(whole, part) // like on immutable maps
def mod(a: A, f: B => B) = set(a, f(this(a)))
def compose[C](that: Lens[C,A]) = Lens[C,B](
c => this(that(c)),
(c, b) => that.mod(c, set(_, b))
)
def andThen[C](that: Lens[B,C]) = that compose this
}
添加回答
舉報