我有几个类都扩展了相同的特性,并且共享了应该改变其状态的相互功能。但是我想知道是否有更好的方法来实现相同的功能。
例如:
trait Breed
case object Pincher extends Breed
case object Haski extends Breed
trait Foox{
def age: Int
def addToAge(i: Int): Foox
}
case class Dog(breed: Breed, age: Int) extends Foox
case class Person(name: String, age: Int) extends Foox
我希望addToAge
将使用附加的int返回相同的对象,
当然,我可以为每个类实现相同的内容,这与DRY规则相矛盾:
case class Dog(breed: Breed, age: Int) extends Foox{
def addToAge(i: Int) = copy(age = age + i)
}
case class Person(name: String, age: Int) extends Foox{
def addToAge(i:Int) = copy(age = age + i)
}
有更好的方法可以避免吗?
是否有一个选项可以避免重新定义该年龄:在每个案例类中使用Int并保持其状态(年龄已在特征中定义)?
答案 0 :(得分:5)
可能涵盖某些用例的一种可能解决方案是使用Lens
库中的shapeless
es:
import shapeless._
abstract class Foox[T](
implicit l: MkFieldLens.Aux[T, Witness.`'age`.T, Int]
) {
self: T =>
final private val ageLens = lens[T] >> 'age
def age: Int
def addToAge(i: Int): T = ageLens.modify(self)(_ + i)
}
case class Dog(breed: Breed, age: Int) extends Foox[Dog]
case class Person(name: String, age: Int) extends Foox[Person]
请注意,要创建Lens
,您需要隐式MkFieldLens
,因此将Foox
定义为abstract class
而不是{{1}更容易}}。否则,您必须在每个孩子中编写一些代码来提供隐含的代码。
另外,我认为没有办法避免在每个孩子中定义trait
。在构造实例时,您必须以某种方式提供年龄,例如age: Int
,所以你必须在那里有年龄的构造函数参数。
更多解释:
镜头是对某些数据类型的子部分的第一类引用。 [...] 鉴于镜头,你基本上有三件事 可能想做
- 查看子部分
- 通过更改子部分
来修改整体- 将此镜头与另一个镜头组合以显得更深入
醇>第一个和第二个产生了镜头是吸气剂的想法 像你可能拥有的对象一样。
修改部分可用于实现我们想要对Dog(Pincher, 5)
执行的操作。
无形库提供了一种漂亮的无样板语法,用于为案例类字段定义和使用镜头。我相信The code example in the documentation是不言自明的。
以下age
字段的代码来自该示例:
age
final private val ageLens = lens[???] >> 'age
def age: Int
def addToAge(i: Int): ??? = ageLens.modify(self)(_ + i)
的返回类型应该是什么?它应该是从中调用此方法的子类的确切类型。这通常通过F-bounded polymorphism来实现。所以我们有以下几点:
addToAge
trait Foox[T] { self: T => // variation of F-bounded polymorphism
final private val ageLens = lens[T] >> 'age
def age: Int
def addToAge(i: Int): T = ageLens.modify(self)(_ + i)
}
在那里被用作孩子的确切类型,每个扩展T
的班级都应该将自己设为Foox[T]
(因为自我类型声明T
)。例如:
self: T =>
现在我们需要让case class Dog(/* ... */) extends Foox[Dog]
行正常工作。
让我们分析一下lens[T] >> 'age
方法的签名,看看它需要起什么作用:
>>
我们看到def >>(k: Witness)(implicit mkLens: MkFieldLens[A, k.T]): Lens[S, mkLens.Elem]
参数被隐式转换为'age
。 shapeless.Witness
表示特定值的确切类型,或者换言之,表示类型级别值。两种不同的文字,例如Witness
s Symbol
和'age
有不同的证人,因此可以区分他们的类型。
Shapeless提供了一种花哨的反引号语法来获得某个值的'foo
。对于Witness
符号:
'age
从第1项和Witness.`'age` // Witness object
Witness.`'age`.T // Specific type of the 'age symbol
签名开始,我们需要为类>>
(子MkFieldLens
)和字段提供隐式T
case class
:
'age
MkFieldLens[T, Witness.`'age`.T]
字段也应该包含age
类型。可以用Aux
pattern无形的公式来表达这个要求:
Int
为了更自然地提供这种隐含,作为隐含参数,我们必须使用MkFieldLens.Aux[T, Witness.`'age`.T, Int]
而不是abstract class
。