16

我似乎不了解 Scala 类型系统。我正在尝试实现两个基本特征和一个特征,以供一系列算法使用。我在下面做错了什么?

动作和状态的基本特征;这些被简化为仅包括暴露问题的方法。

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

这是使用上述算法的家族的特征。我不确定这是否正确!可能涉及一些+M / -S的东西......

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
                                  ^

我是否必须转向使用特征特征的方法,也就是蛋糕模式,才能使这项工作有效?(我只是在这里猜测;我仍然非常困惑。)

4

2 回答 2

14

MyAlgorithm#bestMove明确声明采用State[Move]参数,但在内部Main您试图传递它 a MyState,它State[MyMove]不是 a 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
}

然后在您的 Main 类中,MyAlgorithm使用具体类型实例化 a :

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

您的评论“可能涉及一些 +M / -S 的东西”是一个很好的猜测,但它在这里对您不起作用。您可能希望协变类型修饰符“+”在这里有所帮助。如果您已将类型参数声明State

State[+M]

这将表明State[M] <:< State[N]如果M <:< N. (读<:<作“是”的子类型)。然后,您可以毫无问题地传入 State[MyMove] ,而 State[Move] 是预期的。但是,您不能在此处对 M 使用协变修饰符,因为它作为后继函数的参数出现在逆变位置。

为什么这是个问题?您的继任者声明说它将需要一个 M 并返回一个状态。协变注释说 State[M] 也是 State[Any]。所以我们应该允许这个赋值:

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

现在如果我们有一个State[Any],那么 x.successor 是什么类型? Any => MyMove. 这不可能是正确的,因为您的实现需要 a MyMove,而不是Any

于 2012-11-14T03:16:13.323 回答
7

对于更新的代码。

编译器对投诉非常公平。算法使用 State 的一个子类,并且状态后继者可以返回 State[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

让我们使用 Algorithm 作为唯一的类型参数化源并相应地重写类型结构

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)
}

注意finalMyState 类的修饰符。它确保转换 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 有一天会给我这种能力。

于 2012-11-14T12:19:36.763 回答