返回当前类型的问题经常在StackOverflow上被问到。这里就是一个这样的例子。通常的答案似乎是F-界多态性或典型模式解决方案。奥德斯基在“F-结合多态性有用吗?”中建议
F-界确实增加了相当大的复杂性。我希望能摆脱它们,用更好的亚型代替它们。
而tpolecat (链接帖子的作者)则建议
一个更好的策略是使用一个类型,它能很好地解决问题,并且几乎没有担心的余地。事实上,在这种情况下,完全放弃子类型多态性是值得考虑的。
其中标识了以下劣势
F限制多态性将类型参数化为其自己的子类型,这是一个比用户通常想要的更弱的约束,这是一种表示“my type”的方式,您无法通过子类型精确地表示它。不过,打字员可以直接表达这个想法,所以我要教初学者
我的问题是,根据上面的建议,有人能证明F-有界多态性是有利的情况吗?还是我们应该把典型的解作为解决返回电流型问题的典型答案?
按类型参数F-绑定多态性
trait Semigroup[A <: Semigroup[A]] { this: A =>
def combine(that: A): A
}
final case class Foo(v: Int) extends Semigroup[Foo] {
override def combine(that: Foo): Foo = Foo(this.v + that.v)
}
final case class Bar(v: String) extends Semigroup[Bar] {
override def combine(that: Bar): Bar = Bar(this.v concat that.v)
}
def reduce[A <: Semigroup[A]](as: List[A]): A = as.reduce(_ combine _)
reduce(List(Foo(1), Foo(41))) // res0: Foo = Foo(42)
reduce(List(Bar("Sca"), Bar("la"))) // res1: Bar = Bar(Scala)F-按成员类型划分的多态性
trait Semigroup {
type A <: Semigroup
def combine(that: A): A
}
final case class Foo(v: Int) extends Semigroup {
override type A = Foo
override def combine(that: Foo): Foo = Foo(this.v + that.v)
}
final case class Bar(v: String) extends Semigroup {
override type A = Bar
override def combine(that: Bar): Bar = Bar(this.v concat that.v)
}
def reduce[B <: Semigroup { type A = B }](as: List[B]) =
as.reduce(_ combine _)
reduce(List(Foo(1), Foo(41))) // res0: Foo = Foo(42)
reduce(List(Bar("Sca"), Bar("la"))) // res1: Bar = Bar(Scala)台风
trait Semigroup[A] {
def combine(x: A, y: A): A
}
final case class Foo(v: Int)
object Foo {
implicit final val FooSemigroup: Semigroup[Foo] =
new Semigroup[Foo] {
override def combine(x: Foo, y: Foo): Foo = Foo(x.v + y.v)
}
}
final case class Bar(v: String)
object Bar {
implicit final val BarSemigroup: Semigroup[Bar] =
new Semigroup[Bar] {
override def combine(x: Bar, y: Bar): Bar = Bar(x.v concat y.v)
}
}
def reduce[A](as: List[A])(implicit ev: Semigroup[A]): A = as.reduce(ev.combine)
reduce(List(Foo(1), Foo(41))) // res0: Foo = Foo(42)
reduce(List(Bar("Sca"), Bar("la"))) // res1: Bar = Bar(Scala)发布于 2020-01-20 16:24:06
F-界是一个很好的例子,说明了类型系统能够表达什么,甚至更简单的系统,比如Java。但是,类型的总是更安全和更好的选择。
安全是什么意思?简单地说,我们不能违反合同,返回完全相同的类型。这可以用于两种形式的F限制的多态性(相当容易)。
按成员类型划分的f-有界多态性
这个很容易被打破,因为我们只需要对类型成员撒谎。
trait Pet {
type P <: Pet
def name: String
def renamed(newName: String): P
}
final case class Dog(name: String) extends Pet {
override type P = Dog
override def renamed(newName: String): Dog = Dog(newName)
}
final case class Cat(name: String) extends Pet {
override type P = Dog // Here we break it.
override def renamed(newName: String): Dog = Dog(newName)
}
Cat("Luis").renamed(newName = "Mario")
// res: Dog = Dog("Mario")按类型参数划分的f-有界多态性
由于this: A强制要求扩展类是相同的,所以这个类更难打破。但是,我们只需要添加一个额外的继承层。
trait Pet[P <: Pet[P]] { this: P =>
def name: String
def renamed(newName: String): P
}
class Dog(override val name: String) extends Pet[Dog] {
override def renamed(newName: String): Dog = new Dog(newName)
override def toString: String = s"Dog(${name})"
}
class Cat(name: String) extends Dog(name) // Here we break it.
new Cat("Luis").renamed(newName = "Mario")
// res: Dog = Dog(Mario)尽管如此,很明显,类型的方法更复杂,有更多的样板;此外,人们可以说,要打破F界的,您必须有意识地这样做。因此,如果您对F有界的问题很满意,并且不喜欢处理类型的复杂性,那么它仍然是一个有效的解决方案。
此外,我们应该注意,即使是类型方法也可以通过使用asInstanceOf或反射之类的方法来打破。
顺便提一下,如果您想修改当前对象并返回自身以允许调用链接(就像传统的Java ),那么您可以(应该)使用this.type,而不是返回修改后的副本。
trait Pet {
def name: String
def renamed(newName: String): this.type
}
final class Dog(private var _name: String) extends Pet {
override def name: String = _name
override def renamed(newName: String): this.type = {
this._name = newName
this
}
override def toString: String = s"Dog(${name})"
}
val d1 = Dog("Luis")
// d1: Dog = Dog(Luis)
val d2 = d1.renamed(newName = "Mario")
// d2: Dog = Dog(Mario)
d1 eq d2
// true
d1
// d1: Dog = Dog(Mario)发布于 2020-01-19 20:18:26
发布于 2021-01-21 12:40:54
从我的经验
1不要使用F绑定类型参数.
2您确实需要类型类.
3直接继承仍然是类型类的一个非常有用的补充。
考虑三个我碰巧熟悉的例子。
ShowT从对象/数据值生成字符串表示形式。
UnshowT从字符串表示形式生成对象/数据值。
ShearT对对象进行剪切几何变换。
所以UnshowT很简单,我们必须使用类型类,并且只使用类型类。在我们生产出T型物体之前,我们没有可以使用的对象。直接继承被取消了。我说直接继承,因为我们仍然希望在类型类实现中使用继承。我们可能希望在类型类层次结构中使用继承,但是让我们忽略这种复杂的情况,从现在开始,这不是我所说的继承。但是,请注意,UnshowT的代数和类型的实现非常简单。在UnshowT中,T是代数和类型,本质上是查找操作,您遍历T的子类型列表,直到找到成功返回子类型值的子类型。例如,对于UnShow[OptionA],您可以尝试Unshow[SomeA],如果失败,您可以尝试UnshowNone。虽然实现起来很简单,但最大的限制是在创建UnshowT实例时必须知道T的所有子类型。
因此,对于ShowT,作为一个光荣的或重新设想的toString方法,我们实际上不需要缩小我们的方法的返回类型。但是我想谈谈它,因为它在概念上很简单,并且说明了继承的优点。现在,如果不使用类型类,那么使用ShowT就不会有多大进展,如果我们想要一个Show[ListA]、Show[ArrayA]或Show[OptionA],那么我们将使用一个类型类。然而,继承对于实现代数和类型的实例仍然非常有用。如果用于ShowT的TypeT类型类实例委托给继承的方法,则即使在实现时不知道T的所有子类型,也可以实现T类型的实例。即使代数和类型的所有子类型在实例创建时都是已知的,即使类型T是有效密封的,在我看来,委托给继承的方法比使用match语句更干净。
因此,剪切操作,我们想要能够执行剪切操作的形状。我们想要剪切操作给我们一个形状。我们不想封闭我们的形状特征,所以我们需要使用继承来实现这一点。当然,如果要剪切列表形状、ArrayShape或OptionShape,我们仍然需要一个类型类。因此,为了简单起见,我们有:
trait Shape {
def xShear(operand: Double): Shape
}然后,我们可以简单地缩小返回类型,即使同时保持它的抽象。
trait Polygon extends Shape {
override def xShear(operand: Double): Polygon
}
trait Triangle extends Polygon {
override def xShear(operand: Double): Triangle = { implementation }
}
object Triangle {
/** This allows us to create Triangles as if it was a class while keeping it a trait. */
def apply(stuff): Trangle = { blah blah}
def unapply(inp: Any): [(Pt, Pt, Pt)] = { blah, blah }
}
class EquilateralTriangle(stuff) extends Triangle {
//Doesn't override xShear as can not fulfil the interface
}最终建议.
如果有多个要保持锁定的方法返回类型,则使用类型成员而不是类型参数。它说,3人完全学会了使用F绑定类型的参数安全。一个死了,一个疯了,另一个忘了。
将继承对象和类型类实例中的方法数量保持在最低限度。将所有可以从其他实例实现的方法中删除的助手方法放到类型类的扩展类中。
倾向于避免使用非最终类,如果在其伴生对象中使用带有应用和不应用方法的特性,则可以在避免非最终类问题的同时获得类的许多优点。
https://stackoverflow.com/questions/59813323
复制相似问题