scala - 改变类状态的惯用方式

标签 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)
}
  • 有没有更好的方法来避免这种情况?
  • 有没有一个选项可以避免在每个案例类中重新定义那个 age:Int 并保持它的状态(年龄已经在特征中定义了)?
  • 最佳答案

    一种可能涵盖某些用例的可能解决方案是使用 Lens es 来自 shapeless图书馆:

    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而不是 trait .否则,您必须在每个 child 中编写一些代码来提供隐式。

    另外,我认为没有办法避免定义 age: Int在每个 child 身上。在构建实例时,您必须以某种方式提供年龄,例如Dog(Pincher, 5) ,所以你必须有那个年龄的构造函数参数。

    还有一些解释:

    借用 Haskell Lens tutorial :

    A lens is a first-class reference to a subpart of some data type. [...] Given a lens there are essentially three things you might want to do

    1. View the subpart
    2. Modify the whole by changing the subpart
    3. Combine this lens with another lens to look even deeper

    The first and the second give rise to the idea that lenses are getters and setters like you might have on an object.



    修改部分可以用来实现我们想要做的age .

    Shapeless 库提供了一种漂亮的、无样板的语法来定义和使用案例类字段的镜头。 The code example in the documentation是不言自明的,我相信。

    以下代码为 age字段来自该示例:
    final private val ageLens = lens[???] >> 'age
    def age: Int
    def addToAge(i: Int): ??? = ageLens.modify(self)(_ + i)
    
    addToAge的返回类型应该是什么是?它应该是调用此方法的子类的确切类型。这通常通过 F-bounded polymorphism 实现.所以我们有以下内容:
    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在那里用作 child 的确切类型,并且每个类都扩展 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表示特定值的确切类型,即类型级别的值。两种不同的文字,例如Symbol s 'age'foo , 有不同的见证人,因此可以区分它们的类型。

    Shapeless 提供了一种花哨的反引号语法来获得 Witness有一定的值(value)。对于 'age象征:
    Witness.`'age`    // Witness object
    Witness.`'age`.T  // Specific type of the 'age symbol
    
  • 遵循第 1 项和 >>签名,我们需要有一个隐含的 MkFieldLens可用,适用于类 T (子 case class)和字段 'age :
    MkFieldLens[T, Witness.`'age`.T]
    
    age字段还应具有类型 Int .可以用 Aux pattern 来表达这个要求。常见于无形:
    MkFieldLens.Aux[T, Witness.`'age`.T, Int]
    

  • 为了更自然地提供这种隐式,作为隐式参数,我们必须使用 abstract class而不是 trait .

    关于scala - 改变类状态的惯用方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37765846/

    相关文章:

    scala - 嵌套列表的通用类型参数

    scala - 使用 Scalding 的 HBase 到 Hive 示例

    scala - 使用 scala Actor 时我应该如何处理阻塞操作?

    f# - 互斥子句的顺序在函数或匹配表达式中是否重要

    javascript - react : is there a better pattern for higher-order component rendering?

    scala - 如何仅向特定类型的列表添加额外的行为?

    scala - 为什么方法的类型位置被标记为负数?

    javascript - 我应该如何在小函数之间传递数据 - 通过闭包或通过对象的属性?

    functional-programming - 如何在OCaml中返回for循环的索引?

    python - 将类转换为模块 Python3