簡體   English   中英

馴服Scala類型系統

[英]Taming the Scala type system

我似乎不理解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
                                  ^

我是否必須使用特征特征(即蛋糕模式)來實現這一目標? (我只是在這里猜測;我仍然很困惑。)

你將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的抽象類型成員:

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

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

你的評論“可能有一些+ M / -S涉及的東西”是一個很好的猜測,但它在這里不適合你。 您可能希望協變類型修飾符“+”可能對此有所幫助。 如果已將State上的type參數聲明為

State[+M]

這表示如果M <:< N ,則State[M] <:< State[N] (讀<:< as“是”的子類型“)。 然后你可以在狀態[MyMove]中傳遞狀態[Move]進行預測時沒有問題。 但是,您不能在此處使用M上的協變修改器,因為它在逆變位置中顯示為后繼函數的參數。

為什么這是個問題? 你的繼任者聲明說它需要一個M並返回一個國家。 協變注釋說國家[M]也是國家[任何]。 所以我們應該允許這個任務:

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

現在如果我們有一個State[Any] ,那么x.successor是什么類型的? Any => MyMove 這可能不正確,因為您的實現期望MyMove ,而不是Any

對於更新的代碼。

編譯器對投訴非常公平。 算法使用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,但是traits沒有隱式視圖邊界,因為它們根本沒有值參數。

解決方案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)
}

查看此tecnique的更多抽象和復雜的用法示例: 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)
}

注意MyState類的final修飾符。 它確保轉換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會給我這個能力。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM