scala - 改变阶级状态的惯用方法

时间:2016-06-11 16:10:48

标签: scala functional-programming

我有几个类都扩展了相同的特性,并且共享了应该改变其状态的相互功能。但是我想知道是否有更好的方法来实现相同的功能。

例如:

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)
}
  1. 有更好的方法可以避免吗?

  2. 是否有一个选项可以避免重新定义该年龄:在每个案例类中使用Int并保持其状态(年龄已在特征中定义)?

1 个答案:

答案 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,所以你必须在那里有年龄的构造函数参数。

更多解释:

借鉴Haskell Lens tutorial

  

镜头是对某些数据类型的子部分的第一类引用。 [...]   鉴于镜头,你基本上有三件事   可能想做

     
      
  1. 查看子部分
  2.   
  3. 通过更改子部分
  4. 来修改整体   
  5. 将此镜头与另一个镜头组合以显得更深入
  6.         

    第一个和第二个产生了镜头是吸气剂的想法   像你可能拥有的对象一样。

修改部分可用于实现我们想要对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方法的签名,看看它需要起什么作用:

>>
  1. 我们看到def >>(k: Witness)(implicit mkLens: MkFieldLens[A, k.T]): Lens[S, mkLens.Elem] 参数被隐式转换为'ageshapeless.Witness表示特定值的确切类型,或者换言之,表示类型级别值。两种不同的文字,例如Witness s Symbol'age有不同的证人,因此可以区分他们的类型。

    Shapeless提供了一种花哨的反引号语法来获得某个值的'foo。对于Witness符号:

    'age
  2. 从第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
  3. 为了更自然地提供这种隐含,作为隐含参数,我们必须使用MkFieldLens.Aux[T, Witness.`'age`.T, Int] 而不是abstract class