通俗的方式改变阶级的状态 [英] scala - idiomatic way to change state of class

查看:85
本文介绍了通俗的方式改变阶级的状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有几个类都扩展了相同的特性,并且所有的共享功能都应该改变它们的状态。然而,我想知道是否有更好的方式来实现相同的功能。



例如:

  trait品种
案例对象Pincher延伸品种
案例对象Haski延伸品种

特质Foox {
def age:Int
def addToAge(i:Int):Foox
}

case class Dog(品种:品种,年龄:Int)扩展Foox
case class Person(name:String,age: Int)扩展Foox

我希望 addToAge 将返回与额外的int相同的对象,
当然我可以为每个类实现相同的操作,这与DRY规则相矛盾:

  case class Dog(品种:品种,年龄:Int)扩展Foox {
def addToAge(i:Int)= copy(age = age + i)
}
case class Person(name:String,age:Int)扩展Foox {
def addToAge(i:Int)= copy(age = age + i)
}




  1. 有没有更好的方法可以避免这种情况?

  2. >

    是否有避免重新定义这个年龄的选择:每种情况下的类都是Int并保持它的状态(年龄已经在特征中定义)?

    解决方案

    一个可能的解决方案,可能涵盖一些用例,从<$ $使用 Lens es c $ c> shapeless library:

      import shapeless._ 

    抽象类Foox [T](
    implicit l:MkFieldLens.Aux [T,Witness.`'age`.T,Int]
    ){
    self:T =>
    final private val ageLens = lens [T]>> 'age
    $ b def age:Int
    def addToAge(i:Int):T = ageLens.modify(self)(_ + i)
    }

    case class Dog(品种:品种,年龄:Int)延伸Foox [Dog]
    case class Person(name:String,age:Int)扩展Foox [Person]

    请注意,要创建 Lens ,您需要一个隐式的 MkFieldLens ,因此将 Foox 定义为抽象类而不是<$ c $更容易C>性状。否则,你必须在每个孩子身上编写一些代码来提供这种隐式的。



    另外,我不认为有一种方法可以避免定义 age:Int 在每个孩子中。您必须在构建实例时以某种方式提供年龄,例如 Dog(Pincher,5),所以你必须在那里有那个构造函数参数。




    更多解释:

    借用哈斯克尔镜头教程


    镜头是对某种数据类型的子部分的第一级引用。
    给定一个镜头,您可能需要做三件事情:b
    $ b


    1. 查看子部分

    2. 通过更改子部分修改整体

    3. 将此镜片与另一个镜片结合使用, >

    第一个和第二个提出了一个观点,即镜头是获得者
    ,并且像你可能在一个物体上拥有的设置者。

    修改部分可以用来实现我们想要处理的事情 age

    无形文库为案例类字段定义和使用镜头提供了一个漂亮的无样板文法。 文档中的代码示例是我自己的解释,我相信。



    age 字段遵循该示例:

      final private val ageLens = lens [???]>> 'age 
    def age:Int
    def addToAge(i:Int):??? = ageLens.modify(self)(_ + i)

    返回类型 addToAge 是?它应该是从中调用此方法的子类的确切类型。这通常通过 F-界限多态性来实现。所以我们有以下几点:

      trait Foox [T] {self:T => //变体F-bound多态性

    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] 
    code>

    现在我们需要制作 lens [T]>> '年龄行工作。



    让我们来分析>> 方法的签名,看看它需要什么功能:




    1. 我们看到 'age 参数会隐式转换为 shapeless.Witness 证人表示特定值的确切类型,或者换句话说,是一个类型级别的值。两个不同的文字,例如符号 s '年龄'foo ,有不同的证人因此可以区分它们的类型。

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

        Witness.`'age` //见证对象
      Witness.`'age`.T //'年龄符号

      的具体类型

    2. 从项目1和>> 签名之后,我们需要一个隐含的 MkFieldLens 对于 T (小孩案例类)和字段'age

        MkFieldLens [T,Witness.`'age`.T] 

      $ b

      age 字段也应该有 Int 。可以用 Aux 模式通用于无形:

        MkFieldLens。 Aux [T,Witness.`'age`.T,Int] 


    为了更加自然地提供这个隐式,作为一个隐含的参数,我们必须使用抽象类而不是特征


    I have several classes all extends the same trait and all share mutual functionality that should change their state. However I was wondering if is there a better way to implement the same functionality.

    e.g :

    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
    

    I want that addToAge will return the same object with the additional int, of course I can implement the same for each class, which contradicts DRY rule:

    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. is there a better way to avoid that ?

    2. is there an option to avoid redefine that age:Int in each case class and maintain it's state (the age is already defined in the trait) ?

    解决方案

    One possible solution, that may cover some use cases, is to use Lenses from the shapeless library:

    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]
    

    Note that to create a Lens you need an implicit MkFieldLens, so it's easier to define Foox as an abstract class instead of a trait. Otherwise you'd have to write some code in every child to provide that implicit.

    Also, I don't think there is a way to avoid defining an age: Int in every child. You have to provide the age in some way when you construct an instance, e.g. Dog(Pincher, 5), so you have to have that constructor argument for age there.


    Some more explanation:

    Borrowing from a 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.

    The modification part can be used to implement what we want to do with age.

    Shapeless library provides a pretty, boilerplate-free syntax to define and use lenses for case class fields. The code example in the documentation is self explanatory, I believe.

    The following code for the age field follows from that example:

    final private val ageLens = lens[???] >> 'age
    def age: Int
    def addToAge(i: Int): ??? = ageLens.modify(self)(_ + i)
    

    What should the return type of addToAge be? It should be the exact type of the subclass from which this method is being called. This is usually achieved with F-bounded polymorphism. So we have the following:

    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 is used there as the exact type of the child, and every class extending Foox[T] should provide itself as T (because of the self-type declaration self: T =>). For example:

    case class Dog(/* ... */) extends Foox[Dog]
    

    Now we need to make that lens[T] >> 'age line work.

    Let's analyze the signature of the >> method to see what it needs to function:

    def >>(k: Witness)(implicit mkLens: MkFieldLens[A, k.T]): Lens[S, mkLens.Elem]
    

    1. We see that the 'age argument gets implicitly converted to a shapeless.Witness. Witness represents the exact type of a specific value, or in other words a type-level value. Two different literals, e.g. Symbols 'age and 'foo, have different witnesses and thus their types can be distinguished.

      Shapeless provides a fancy backtick syntax to get a Witness of some value. For 'age symbol:

      Witness.`'age`    // Witness object
      Witness.`'age`.T  // Specific type of the 'age symbol
      

    2. Following from item 1 and the >> signature, we need to have an implicit MkFieldLens available, for class T (the child case class) and field 'age:

      MkFieldLens[T, Witness.`'age`.T]
      

      The age field should also have the type Int. It is possible to express this requirement with the Aux pattern common in shapeless:

      MkFieldLens.Aux[T, Witness.`'age`.T, Int]
      

    And to provide this implicit more naturally, as an implicit argument, we have to use an abstract class instead of a trait.

    这篇关于通俗的方式改变阶级的状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆