在 scala 中键入安全的多米诺骨牌

Posted

技术标签:

【中文标题】在 scala 中键入安全的多米诺骨牌【英文标题】:Type safe dominoes in 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

【问题讨论】:

【参考方案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]

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

【讨论】:

是的,我的错误,正在修复

以上是关于在 scala 中键入安全的多米诺骨牌的主要内容,如果未能解决你的问题,请参考以下文章

使用 Java 在 Spark 中键入安全连接

如何在火花聚合函数中实现scala类型安全

Scala Web 应用程序安全性

键入安全的胡子模板

为 std::vector 键入安全索引值

未知工件 sbtplugin 带有 scala 2.12 的超级安全编译器