Укрощение системы типа Scala

Кажется, я не понимаю систему типа Scala. Я пытаюсь реализовать две основные черты и черту для семейства алгоритмов для работы с ними. Что я делаю неправильно в ниже?

Базовые признаки для движений & состояний; они упрощены, чтобы просто включать методы, которые раскрывают проблему.

trait Move
trait State[M <: Move] {
    def moves: List[M]
    def successor(m: M): State[M]
}

Вот черта для семейства алгоритмов, использующих выше. Я не уверен, что это правильно! Возможно, некоторые из них связаны с CN00/CN01.

trait Algorithm {
    def bestMove[M <: Move, S <: State[M]](s: S): M
}

Бетонный ход и состояние:

case class MyMove(x: Int) extends Move
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))
}

Я нахожусь на очень шаткой почве относительно ниже, но компилятор, кажется, согласен с этим... Попытка сделать конкретную реализацию признака Алгоритма.

object MyAlgorithm extends Algorithm {
    def bestMove(s: State[Move]) = s.moves.head
}

Пока нет ошибок компиляции; они появляются, когда я пытаюсь собрать все части вместе:

object Main extends App {
    val s = new MyState(Map())
    val m = MyAlgorithm.bestMove(s)
    println(m)
}

Вышеупомянутая ошибка:

error: overloaded method value bestMove with alternatives:
  (s: State[Move])Move <and>
  [M <: Move, S <: State[M]](s: S)M
 cannot be applied to (MyState)
    val m = MyAlgorithm.bestMove(s)
                        ^

Обновление: я изменил черту алгоритма, чтобы использовать элементы абстрактного типа, как это было предложено. Это решило вопрос, как я его сформулировал, но я немного упростил его. Метод MyAlgorithm.bestMove() должен иметь возможность вызывать себя с выходом из s.successor(m), например:

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
    def bestMove(s: S): M = {
        val groups = s.moves.groupBy(m => score(s.successor(m)))
        val max = groups.keys.max
        groups(max).head
    }
}

Приведенное выше дает 2 ошибки:

Foo.scala:38: error: type mismatch;
 found   : State[MyAlgorithm.this.M]
 required: MyAlgorithm.this.S
            val groups = s.moves.groupBy(m => score(s.successor(m)))
                                                               ^
Foo.scala:39: error: diverging implicit expansion for type Ordering[B]
starting with method Tuple9 in object Ordering
            val max = groups.keys.max
                                  ^

Должен ли я перейти к подходу, используя черты признаков, например, шаблон Cake, чтобы сделать эту работу? (Я просто догадываюсь, я все еще смущен.)

Ответ 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 даст мне эту способность когда-нибудь.

Ответ 2

Вы объявляете MyAlgorithm#bestMove явно как принимающий параметр State[Move], но внутри Main вы пытаетесь передать ему MyState, который является State[MyMove] не State[Move].

У вас есть несколько вариантов решения этой проблемы. Можно было бы не ограничивать типы в MyAlgorithm:

object MyAlgorithm extends Algorithm {
    def bestMove[M <: Move, S <: State[M]](s: S) : M = s.moves.head
}

К сожалению, вывод типа scala не достаточно умен, чтобы определить эти типы для вас, поэтому на сайте вызова вы должны объявить их, сделав вызов MyAlgorithm#bestMove похожим на это:

val m = MyAlgorithm.bestMove[MyMove, MyState](s)

Другой вариант использует элементы абстрактного типа для Algorithm:

trait Algorithm {
  type M <: Move
  type S <: State[M]
    def bestMove(s: S): M
}

И разрешите абстрактные типы в конкретной реализации:

object MyAlgorithm extends Algorithm {
  type M = MyMove
  type S = MyState
  def bestMove(s: S) : M = s.moves.head
}

Затем сайт вызова возвращается к исходной версии без упоминания типов:

val m = MyAlgorithm.bestMove(s)

Возможно, вы захотите, чтобы MyAlgorithm не знал о реальных типах и оставил определение этих типов "клиентам" этого объекта, и в этом случае измените объект на признак:

trait MyAlgorithm extends Algorithm {
  def bestMove(s: S) : M = s.moves.head
}

Затем в вашем MyAlgorithm классе вы MyAlgorithm с конкретными типами:

val a = new MyAlgorithm {
  type M = MyMove
  type S = MyState
}
val m = a.bestMove(s)

Ваш комментарий: "Возможно, некоторые из них +M / -S были" хороши, но это не сработает для вас здесь ". Вы можете надеяться, что модификатор ковариантного типа "+" может помочь здесь. Если вы указали параметр type в State как

State[+M]

Это указывает на то, что State[M] <:< State[N] если M <:< N. (читать <:< as "является подтипом"). Тогда у вас не будет проблем с передачей в State [MyMove], где ожидалось состояние [Move]. Однако вы не можете использовать ковариантный модификатор на M здесь, потому что он появляется в контравариантной позиции как аргумент функции-преемника.

Почему это проблема? Ваше выражение о преемнике говорит, что оно займет М и вернуть государство. Ковариантная аннотация говорит, что государство [M] также является государством [Any]. Поэтому мы должны разрешить это задание:

val x : State[Any] = y : State[MyMove]

Теперь, если у нас есть State[Any], то x.successor - это какой тип? Any => MyMove. Что не может быть правильным, так как ваша реализация ожидает MyMove, а не Any