【问题标题】:Type safe dominoes in scala在 scala 中键入安全的多米诺骨牌
【发布时间】:2021-03-04 21:30:14
【问题描述】:

所以我正在尝试编写一个多米诺骨牌游戏服务器,我正在编写我的核心类型、瓷砖和多米诺骨牌集,我突然想到,包含瓷砖点的类型信息将使我能够编写一个更简单的创建多米诺骨牌链的函数,由于我无法弄清楚这一点,我已经开始并多次留下这个项目不完整,希望有人有一个简单的类型安全瓷砖表示,导致一个简单的多米诺骨牌链功能。原因是我目前的心理模型是多米诺骨牌游戏中的一块棋盘,游戏只是一个初始瓷砖和 1-3 个多米诺骨牌链,每个开始并匹配初始瓷砖上的点数。

在此先感谢您,对于我的问题中的任何不完善之处,我们深表歉意。

sealed case class DoubleSix[L >: Nat, R <: Nat](lPips: Int, rPips: Int) extends Tile[L, R]

object DoubleSixSet {
  val zeroZero: DoubleSix[_0, _0] = DoubleSix(0, 0)

}

类型安全链接功能的较早尝试。

trait DominoChain[End] {
    // val hasSpinner: Boolean
}
case object End extends DominoChain[Nothing] {
    // val hasSpinner = false
}
case class  Chain[D0, D1, X](head: Domino[D0, D1], tail: DominoChain[Domino[D1, X]]) extends DominoChain[Domino[D0, D1]] {
     def hasSpinner =
         if (head.isDouble || rest.hasSpinner) true
         else false
}

【问题讨论】:

    标签: scala types shapeless


    【解决方案1】:

    您可能已经注意到,用类型表示多米诺骨牌很容易:

    sealed trait One
    sealed trait Two
    sealed trait Three
    sealed trait Four
    sealed trait Five
    sealed trait Six
    
    sealed trait Domino[A, B] extends Product with Serializable
    object Domino {
        case object OneOne[One, One]
        case object OneTwo[One, Two]
        ... // the other types of dominoes
    }
    

    如果你想有一个线性链也很容易:

    sealed trait Chain[A, B] extends Product with Serializable
    object Chain {
      case class One[A, B](domino: Domino[A, B]) extends Chain[A, B]
      case class Prepend[A, B, C](head: Domino[A, B], tail: Chain[B, C]) extends Chain[A, C]
    }
    

    如果这不是线性的,事情就会变得棘手。你可能想转弯。这样做的方法不止一种:

    xxyy
    
     yy
    xx
     
    xx
     yy
    
    xx
     y
     y
    
     y
     y
    xx
    

    并且它们中的每一个都必须表示为一个单独的案例。如果你想避免这样的事情:

      f <- f tile would have to be over or under bb tile
    aabbc  f
      e c
      edd
    

    您必须以某种方式检测到这种情况并阻止它。你有两个选择:

    • 不要以类型表示,将其表示为值并使用一些智能构造函数来计算您的移动是否有效,并返回带有添加图块或错误的链
    • 将每个回合表示为不同的类型,以类型级别表示,并需要一些证据才能创建图块。这应该是可能的,但要困难得多,并且需要您在编译时知道确切的类型(因此按需动态添加图块可能更难,因为您必须为每次移动预先准备好证据)

    但是在多米诺骨牌中,除了转弯之外,我们还可以有分支:

    aab
      bdd
     cc
    

    如果你想用类型来表达它,现在你有一个可以附加到的两个头部(和一个可以附加到的尾部)。在游戏过程中,你可以拥有更多它们,所以你必须以某种方式表达两者:你有多少分支,以及你想向哪个分支添加新瓷砖。仍然可能,但会使您的代码更加复杂。

    你可以例如用某种 HList 表达头(如果您使用的是无形的)并使用该表示来隐式告诉您要修改 HList 的哪个元素。

    然而,在这一点上,类型级编程几乎没有什么好处:你必须提前知道你的类型,你很难动态添加新的瓦片,你必须以这样的方式保持状态,你会能够检索确切的类型,以便类型级别的证据可以工作......

    因此,我建议使用一种仍然是类型安全但更容易接近的方法:只需使用smart constructors

    type Position = UUID
    
    sealed trait Chain extends Product with Serializable
    object Chain {
      // prevent user from accessing constructors and copy directly
      sealed abstract case class One private (
        domino: Domino,
        position: Position
      ) extends Chain
      sealed abstract case class PrependLine private (
        domino: Domino,
        position: Position,
        chain:  Chain
      )
      sealed abstract case class Branch private (
        chain1: Chain,
        chain2: Chain
      )
    
      def start(domino: Domino): Chain
    
      // check if you can add domino at this position, recursively rewrite tree
      // if needed to add it at the right branch or maybe even create a new branch
      def prepend(domino: Domino, to: Chain, at: Position): Either[Error, Chain]
    }
    

    这仍然无法创建“无效”的多米诺骨牌链。同时,添加新规则、扩展功能和在请求之间保持状态会更容易(您提到要构建服务器)。

    【讨论】:

    • 是的,我的错误,正在修复
    猜你喜欢
    • 2022-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-03
    • 1970-01-01
    • 2012-12-13
    • 1970-01-01
    相关资源
    最近更新 更多