F-Bounded является отличным примером того, на что способна система типов express, даже более простые, такие как Java. Но класс типов всегда будет более безопасной и лучшей альтернативой.
Что мы имеем в виду под безопаснее ? Просто мы не можем разорвать договор о возврате точно такого же типа. Что можно сделать для двух форм F-ограниченного полиморфизма (довольно легко) .
F-ограниченного полиморфизма по типу member
Этот довольно легко сломать, так как нам нужно только l ie относительно члена типа .
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)
. Тем не менее, ясно, что подход typeclass является более сложным и имеет более шаблонный характер; Также можно утверждать, что чтобы сломать F-Bounded , нужно сделать это намеренно. Таким образом, если вы в порядке с проблемами F-Bounded и не любите иметь дело со сложностью класса типов , то это все еще верное решение.
Кроме того, мы должны отметить, что даже подход typeclass может быть нарушен с помощью таких вещей, как 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)