通俗的方式改变阶级的状态 [英] scala - idiomatic way to change state of class
问题描述
我有几个类都扩展了相同的特性,并且所有的共享功能都应该改变它们的状态。然而,我想知道是否有更好的方式来实现相同的功能。
例如:
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)
}
-
有没有更好的方法可以避免这种情况?
> - 查看子部分
- 通过更改子部分修改整体
- 将此镜片与另一个镜片结合使用, >
-
我们看到
'age
参数会隐式转换为shapeless.Witness
。证人
表示特定值的确切类型,或者换句话说,是一个类型级别的值。两个不同的文字,例如符号
s'年龄
和'foo
,有不同的证人因此可以区分它们的类型。
Shapeless提供了一种花哨的反向语法来获取某个值的
Witness
。对于'age
符号:
Witness.`'age` //见证对象
Witness.`'age`.T //'年龄符号
的具体类型
-
从项目1和
>>
签名之后,我们需要一个隐含的MkFieldLens
对于T
(小孩案例类
)和字段'age
:MkFieldLens [T,Witness.`'age`.T]
$ bage
字段也应该有Int
。可以用Aux
模式通用于无形:
MkFieldLens。 Aux [T,Witness.`'age`.T,Int]
is there a better way to avoid that ?
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) ?
- View the subpart
- Modify the whole by changing the subpart
- Combine this lens with another lens to look even deeper
We see that the
'age
argument gets implicitly converted to ashapeless.Witness
.Witness
represents the exact type of a specific value, or in other words a type-level value. Two different literals, e.g.Symbol
s'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
Following from item 1 and the
>>
signature, we need to have an implicitMkFieldLens
available, for classT
(the childcase class
) and field'age
:MkFieldLens[T, Witness.`'age`.T]
The
age
field should also have the typeInt
. It is possible to express this requirement with theAux
pattern common in shapeless:MkFieldLens.Aux[T, Witness.`'age`.T, Int]
是否有避免重新定义这个年龄的选择:每种情况下的类都是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
第一个和第二个提出了一个观点,即镜头是获得者
,并且像你可能在一个物体上拥有的设置者。
修改部分可以用来实现我们想要处理的事情 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]>> '年龄
行工作。
让我们来分析>>
方法的签名,看看它需要什么功能:
为了更加自然地提供这个隐式,作为一个隐含的参数,我们必须使用抽象类
而不是特征
。
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)
}
One possible solution, that may cover some use cases, is to use Lens
es 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
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]
And to provide this implicit more naturally, as an implicit argument, we have to use an abstract class
instead of a trait
.
这篇关于通俗的方式改变阶级的状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!