Ответ 1
Для обновленного кода.
Компилятор очень честен с жалобами. Алгоритм использует один подкласс государства как обозначенный, а преемник состояния может возвращать любой другой подкласс государства [M]
Вы можете объявить класс IntegerOne
trait Abstract[T]
class IntegerOne extends Abstract[Int]
но компилятор не знает, что все экземпляры AbstractOne [Int] будут IntegerOne. Предполагается, что может быть другой класс, который также реализует Abstract [Int]
class IntegerTwo extends Abstract[Int]
Вы можете попытаться использовать неявное преобразование в листинг из Abstract [Int] в IntegerOne, но черты не имеют неявных границ представления, поскольку они вообще не имеют параметров значения.
Решение 0
Таким образом, вы можете переписать свой признак Алгоритма как абстрактный класс и использовать неявное преобразование:
abstract class MyAlgorithm[MT <: Move, ST <: State[MT]] (implicit val toSM : State[MT] => ST) extends Algorithm {
override type M = MT // finalize types, no further subtyping allowed
override type S = ST // finalize types, no further subtyping allowed
def score(s : S) : Int = s.moves.size
override def bestMove(s : S) : M = {
val groups = s.moves.groupBy( m => score(toSM ( s.successor(m)) ) )
val max = groups.keys.max
groups(max).head
}
}
implicit def toMyState(state : State[MyMove]) : MyState = state.asInstanceOf[MyState]
object ConcreteAlgorithm extends MyAlgorithm[MyMove,MyState]
object Main extends App {
val s = new MyState(Map())
val m = ConcreteAlgorithm.bestMove(s)
println(m)
}
В этом решении есть два недостатка
- используя неявное преобразование с asInstanceOf
- типы привязки
Вы можете сначала потушить, как стоимость дальнейшего связывания типов.
Решение 1
Позвольте использовать алгоритм как источник параметризации единственного типа и соответственно переписать структуру типа
trait State[A <: Algorithm] { _:A#S =>
def moves : List[A#M]
def successor(m : A#M): A#S
}
trait Algorithm{
type M <: Move
type S <: State[this.type]
def bestMove(s : S) : M
}
В этом случае ваш MyAlgorithm может использоваться без перезаписи
trait MyAlgorithm extends Algorithm {
def score(s : S) : Int = s.moves.size
override def bestMove(s : S) : M = {
val groups = s.moves.groupBy(m => score(s.successor(m)))
val max = groups.keys.max
groups(max).head
}
}
Используй это:
class MyState(val s : Map[MyMove,Int]) extends State[ConcreteAlgorithm.type] {
def moves = MyMove(1) :: MyMove(2) :: Nil
def successor(p : MyMove) = new MyState(s.updated(p,1))
}
object ConcreteAlgorithm extends MyAlgorithm {
override type M = MyMove
override type S = MyState
}
object Main extends App {
val s = new MyState(Map())
val m = ConcreteAlgorithm.bestMove(s)
println(m)
}
См. Более абстрактный и сложный пример использования для этого теханика: Scala: абстрактные типы и дженерики
Решение 2
Существует также простое решение вашего вопроса, но я сомневаюсь, что оно может решить вашу проблему. В конечном итоге вы столкнетесь с несогласованностью типа в более сложных случаях использования.
Просто заставьте MyState.successor вернуть this.type
вместо State[M]
trait State[M <: Move] {
def moves : List[M]
def successor(m : M): this.type
}
final class MyState(val s : Map[MyMove,Int]) extends State[MyMove] {
def moves = MyMove(1) :: MyMove(2) :: Nil
def successor(p : MyMove) = (new MyState(s.updated(p,1))).asInstanceOf[this.type]
}
другие вещи неизменны
trait Algorithm{
type M <: Move
type S <: State[M]
def bestMove(s : S) : M
}
trait MyAlgorithm extends Algorithm {
def score(s : S) : Int = s.moves.size
override def bestMove(s : S) : M = {
val groups = s.moves.groupBy(m => score(s.successor(m)))
val max = groups.keys.max
groups(max).head
}
}
object ConcreteAlgorithm extends MyAlgorithm {
override type M = MyMove
override type S = MyState
}
object Main extends App {
val s = new MyState(Map())
val m = ConcreteAlgorithm.bestMove(s)
println(m)
}
Обратите внимание на final
модификатор класса MyState. Это гарантирует, что преобразование asInstanceOf [this.type] является правильным. Компилятор Scala может вычислить, что последний класс всегда сохраняет this.type
но у него все еще есть некоторые недостатки.
Решение 3
Нет необходимости связывать алгоритм с пользовательским состоянием. До тех пор, пока Алгоритм не использует определенную функцию состояния, он может быть написан проще без ограничений на определение типа.
trait Algorithm{
type M <: Move
def bestMove(s : State[M]) : M
}
trait MyAlgorithm extends Algorithm {
def score(s : State[M]) : Int = s.moves.size
override def bestMove(s : State[M]) : M = {
val groups = s.moves.groupBy(m => score(s.successor(m)))
val max = groups.keys.max
groups(max).head
}
}
Этот простой пример не приходит мне на ум быстро, потому что я предположил, что обязательная привязка к различным состояниям обязательна. Но иногда только часть системы действительно должна быть параметризована явно, и вы можете избежать дополнительной сложности с ней
Вывод
Рассматриваемая проблема отражает множество проблем, которые возникают в моей практике очень часто.
Есть две конкурирующие цели, которые не должны исключать друг друга, но делать это в scala.
- растяжимость
- всеобщность
Во-первых, вы можете создавать сложную систему, реализовывать некоторую базовую реализацию и иметь возможность поочередно заменять ее части для реализации более сложной реализации.
Второй позволяет определить очень абстрактную систему, которая может использоваться для разных случаев.
Разработчики Scala имели очень сложную задачу для создания системы типов для языка, который может быть как функциональным, так и объектно-ориентированным, будучи ограниченным ядром реализации jvm с огромными дефектами, такими как стирание стилей. Аннотации типа Co/Contra-variance, предоставляемые пользователям, недостаточны для выражения отношений типов в сложной системе
У меня есть свои трудные времена каждый раз, когда я сталкиваюсь с дилеммой универсальности, которая решает, какой компромисс принять.
Я бы хотел не использовать шаблон дизайна, но объявить его на целевом языке. Я надеюсь, что scala даст мне эту способность когда-нибудь.