简体   繁体   English

驯服Scala类型系统

[英]Taming the Scala type system

I don't seem to understand the Scala type system. 我似乎不理解Scala类型系统。 I'm trying to implement two base traits and a trait for a family of algorithms to work with them. 我正在尝试实现两个基本特征和一系列算法的特性来使用它们。 What am I doing wrong in the below? 我在下面做错了什么?

The base traits for moves & states; 移动和状态的基本特征; these are simplified to just include methods that expose the problem. 这些被简化为仅包括暴露问题的方法。

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

Here's the trait for the family of algorithms that makes use of above. 这是使用上述算法系列的特性。 I'm Not sure this is right! 我不确定这是对的! There might be some +M / -S stuff involved... 可能会涉及一些+ M / -S的东西......

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

Concrete move and state: 具体举动和状态:

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

I'm on very shaky ground regarding the below, but the compiler seems to accept it... Attempting to make a concrete implementation of the Algorithm trait. 我对下面的内容非常不满意,但编译器似乎接受了它...试图对算法特性进行具体实现。

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

So far there are no compile errors; 到目前为止,没有编译错误; they show up when I try to put all the parts together, however: 当我尝试将所有部件放在一起时,它们会出现:

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

The above throws this error: 以上抛出此错误:

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

Update: I changed the Algorithm trait to use abstract type members, as suggested. 更新:我按照建议更改了算法特征以使用抽象类型成员。 This solved the question as I had phrased it but I had simplified it a bit too much. 这解决了这个问题, 因为我已经措辞了,但我已经简化了一点。 The MyAlgorithm.bestMove() method must be allowed to call itself with the output from s.successor(m), like this: 必须允许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
    }
}

The above gives now 2 errors: 上面给出了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
                                  ^

Do I have to move to an approach using traits of traits, aka the Cake pattern, to make this work? 我是否必须使用特征特征(即蛋糕模式)来实现这一目标? (I'm just guessing here; I'm thoroughly confused still.) (我只是在这里猜测;我仍然很困惑。)

You declare MyAlgorithm#bestMove explicitly as taking a State[Move] parameter, but inside Main you are trying to pass it a MyState , which is a State[MyMove] not a State[Move] . 你将MyAlgorithm#bestMove明确地声明为一个State[Move]参数,但是在Main你试图传递一个MyState ,它是一个State[MyMove]而不是一个State[Move]

You have a couple of options to resolve this. 您有几个选项可以解决此问题。 One would be to not constrain the types in MyAlgorithm : 一种是不限制MyAlgorithm的类型:

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

Unfortunately, the scala type inference isn't smart enough to figure these types out for you, so at the call site, you have to declare them, making the call to MyAlgorithm#bestMove look like this: 不幸的是,scala类型推断不够智能,无法为您计算这些类型,因此在调用站点,您必须声明它们,使得对MyAlgorithm#bestMove的调用如下所示:

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

Another option use abstract type members of the Algorithm trait: 另一个选项使用Algorithm trait的抽象类型成员:

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

And resolve the abstract types in the concrete implementation: 并在具体实现中解析抽象类型:

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

Then the call site gets to return to your original version, without mentioning the types: 然后呼叫站点返回到您的原始版本,而不提及类型:

val m = MyAlgorithm.bestMove(s)

You may want to keep MyAlgorithm unaware of the actual types, and leave the determining of those types to the 'clients' of that object, in which case, change the object to a trait: 您可能希望让MyAlgorithm不知道实际类型,并将这些类型的确定留给该对象的“客户端”,在这种情况下,将对象更改为特征:

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

Then in your Main class, you instantiate a MyAlgorithm with the concrete types: 然后在Main类中,使用具体类型实例化MyAlgorithm

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

Your comment "There might be some +M / -S stuff involved" was a good guess, but It won't work for you here. 你的评论“可能有一些+ M / -S涉及的东西”是一个很好的猜测,但它在这里不适合你。 You might hope that the covariant type modifier "+" might help here. 您可能希望协变类型修饰符“+”可能对此有所帮助。 If you had declared the type parameter on State as 如果已将State上的type参数声明为

State[+M]

This would indicate that State[M] <:< State[N] if M <:< N . 这表示如果M <:< N ,则State[M] <:< State[N] (read <:< as "is a subtype of"). (读<:< as“是”的子类型“)。 Then you would have no problem passing in a State[MyMove] where a State[Move] was expected. 然后你可以在状态[MyMove]中传递状态[Move]进行预测时没有问题。 However, you cannot use the covariant modifier on M here because it appears in the contravariant position as an argument to the successor function. 但是,您不能在此处使用M上的协变修改器,因为它在逆变位置中显示为后继函数的参数。

Why is this a problem? 为什么这是个问题? Your declaration of successor says that it will take an M and return a State. 你的继任者声明说它需要一个M并返回一个国家。 The covariant annotation says that a State[M] is also a State[Any]. 协变注释说国家[M]也是国家[任何]。 So we should allow this assignment: 所以我们应该允许这个任务:

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

Now if we have a State[Any] , then x.successor is what type? 现在如果我们有一个State[Any] ,那么x.successor是什么类型的? Any => MyMove . Any => MyMove Which cannot be correct, since your implementation is expecting a MyMove , not an Any 这可能不正确,因为您的实现期望MyMove ,而不是Any

For updated code. 对于更新的代码。

The compiler is very fair with complaints. 编译器对投诉非常公平。 Algorithm use one subclass of State as denoted and state successor may return any other subclass of State[M] 算法使用State的一个子类表示,状态后继可以返回State [M]的任何其他子类

You may declare IntegerOne class 您可以声明IntegerOne类

trait Abstract[T]
class IntegerOne extends Abstract[Int]

but the compiler have no clue that all instances of AbstractOne[Int] would be IntegerOne. 但是编译器不知道AbstractOne [Int]的所有实例都是IntegerOne。 It assume that there may be another class that also implements Abstract[Int] 它假设可能还有另一个类也实现了Abstract [Int]

class IntegerTwo extends Abstract[Int]

You may try to use implicit conversion to cast from Abstract[Int] to IntegerOne, but traits have no implicit view bounds as they have no value parameters at all. 您可以尝试使用隐式转换从Abstract [Int]转换为IntegerOne,但是traits没有隐式视图边界,因为它们根本没有值参数。

Solution 0 解决方案0

So you may rewrite your Algorithm trait as an abstract class and use implicit conversion: 因此,您可以将算法特征重写为抽象类并使用隐式转换:

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

There are two drawbacks in this solution 这个解决方案有两个缺点

  • using implicit conversion with asInstanceOf 使用asInstanceOf进行隐式转换
  • tying types 搭售类型

You may extinguish first as the cost of further type tying. 您可以先将其作为进一步打字的费用熄灭。

Solution 1 解决方案1

Let use Algorithm as sole type parameterization source and rewrite type structure accordingly 让我们使用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
}

In that case your MyAlgorithm may be used without rewriting 在这种情况下,您的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
  }
}

Using it: 使用它:

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

See more abstract and complicated usage example for this tecnique: Scala: Abstract types vs generics 查看此tecnique的更多抽象和复杂的用法示例: Scala:抽象类型与泛型

Solution 2 解决方案2

There is also a simple solution to your question but I doubt it can solve your problem. 您的问题也有一个简单的解决方案,但我怀疑它可以解决您的问题。 You will eventually stuck upon type inconsistency once again in more complex use cases. 在更复杂的用例中,您最终会再次遇到类型不一致的问题。

Just make MyState.successor return this.type instead of State[M] 只需让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]
}

other things are unchanged 其他事情没有变化

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

Pay attention to final modifier to MyState class. 注意MyState类的final修饰符。 It ensures that conversion asInstanceOf[this.type] is correct one. 它确保转换asInstanceOf [this.type]是正确的。 Scala compiler may compute itself that final class keeps always this.type but it still have some flaws. Scala编译器可以自己计算最终类总是保持this.type但它仍然有一些缺陷。

Solution 3 解决方案3

There is no need to tie Algorithm with custom State. 无需将算法与自定义状态绑定。 As long as Algorithm does not use specific State function it may be written simpler without type bounding exercises. 只要算法不使用特定的状态函数,它就可以在没有类型边界练习的情况下编写得更简单。

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

This simple example doesn't come to my mind quickly because I've assumed that binding to different states are obligatory. 这个简单的例子很快就没有出现在我脑海中,因为我认为绑定到不同的状态是强制性的。 But sometimes only part of system really should be parameterized explicitly and your may avoid additional complexity with it 但有时只有部分系统真正应该明确参数化,你可以避免额外的复杂性

Conclusion 结论

Problem discussed reflects bunch of problems that arises in my practice very often. 讨论的问题反映了我经常在我的实践中出现的一系列问题。

There are two competing purposes that should not exclude each other but do so in scala. 有两个相互竞争的目的不应该相互排斥,而是在scala中这样做。

  • extensibility 可扩展性
  • generality 概论

First means that you can build complex system, implement some basic realization and be able to replace its parts one by one to implement more complex realization. 首先意味着您可以构建复杂的系统,实现一些基本的实现,并能够逐个替换它的部分,以实现更复杂的实现。

Second allows your to define very abstract system, that may be used for different cases. 第二个允许您定义非常抽象的系统,可用于不同的情况。

Scala developers had very challenging task for creating type system for a language that can be both functional and object oriented while being limited to jvm implementation core with huge defects like type erasure. Scala开发人员在为一种既可以实现功能又面向对象的语言创建类型系统方面具有非常具有挑战性的任务,同时仅限于具有类型擦除等巨大缺陷的jvm实现核心。 Co/Contra-variance type annotation given to users are insufficient for expressing types relations in complex system 给予用户的Co / Contra-variance类型注释不足以在复杂系统中表达类型关系

I have my hard times every time I encounter extensiblity-generality dilemma deciding which trade-off to accept. 每当我遇到可扩展性 - 一般性困境时,我就会遇到困难,决定接受哪种权衡。

I'd like not to use design pattern but to declare it in the target language. 我不想使用设计模式,而是用目标语言声明它。 I hopes that scala will give me this ability someday. 我希望有一天scala会给我这个能力。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM