简体   繁体   English

链接路径依赖类型并在Scala中具有不同参数列表时实例化它们

[英]Chaining path-dependent types and instantiating them when they having different parameter lists in Scala

I'm experimenting with writing more statically type-safe code by implementing a simple card game. 我正在尝试通过实现一个简单的纸牌游戏来编写更多静态类型安全的代码。 In this game, there are several unique cards and each card has a card-specific effect which may require additional parameters (eg, a target for the effect). 在该游戏中,存在若干唯一卡并且每张卡具有卡特定效果,其可能需要附加参数(例如,效果的目标)。 A player holds two cards and on their turn chooses to play one of them, causing that card's effect to take place. 一名玩家持有两张牌,然后轮流选择其中一张牌,从而导致该牌的效果发生。

Note: most of the details in this post are from trying it out in the REPL. 注意:本文中的大部分细节都来自REPL中的尝试。 I have a less statically type-safe implementation written but I want to make sure that what I want is feasible before diving completely into it. 我写了一个不太静态的类型安全的实现,但我想确保我想要的是可行的,然后再深入研究它。

Here are some relevant definitions: 以下是一些相关的定义:

trait CardEffectParams
case class OneTarget(player: Player) extends CardEffectParams
case class TwoTargets(player1: Player, player2: Player) extends CardEffectParams
// ...

trait Card {
  // the parameters to use are specific to the card
  type Params <: CardEffectParams
}

trait Hand {
  case class CardInHand(card: Card) { /* with ctor not accessible from outside */ }
  // a player can hold two cards
  val card1: CardInHand
  val card2: CardInHand
}

I want to delegate the choosing of which card to play to some strategy so I can see how different strategies compare. 我想委托选择使用哪种卡进行某种策略,这样我就可以看出不同策略的比较方式。 This is where I'm stuck: I want to limit the cards you can return to the ones in the Hand object passed in the parameters, which I can do by typing it as hand.CardInHand : 这就是我被困住的地方:我想限制你可以返回参数传递的Hand对象中的Hand ,我可以通过输入hand.CardInHand来做到这hand.CardInHand

trait Strategy {
  def choose(hand: Hand, gameState: GameState): hand.CardsInHand
}

But I also want to pass extra parameters: for example, one card might allow me to target just one player (eg, skip their turn), but another might let me target two (eg, swap their cards). 但我也希望传递额外的参数:例如,一张牌可能允许我只针对一个玩家(例如,跳过轮到他们),但另一张牌可能让我瞄准两个(例如,交换他们的牌)。 These are modelled by CardEffectParams . 这些是由CardEffectParams建模的。 So I want to return both hand.CardsInHand and a cardInHand.card.Params where cardInHand is the instance I'm returning, something like this: 所以我想返回hand.CardsInHandcardInHand.card.Params ,其中cardInHand是我要返回的实例,如下所示:

/* NOT valid scala */
trait Strategy {
  def choose(hand: Hand, gameState: GameState): (c: hand.CardsInHand, c.card.Params)
}

So the first question is, can this be done? 所以第一个问题是,这可以做到吗? How would you represent this relationship? 你会如何表达这种关系?

I'm also stuck on how to instantiate the CardEffectParams subclasses, since each one may have different parameter lists. 我也坚持如何实例化CardEffectParams子类,因为每个子类可能有不同的参数列表。 My first thought is to do a pattern match, but this fails because the type of the match block is the common ancestor of all possible results: 我的第一个想法是进行模式匹配,但这会失败,因为匹配块的类型是所有可能结果的共同祖先:

case object CardA extends Card {
  type Params = OneTarget
}
case object CardB extends Card {
  type Params = TwoTargets
}

object RandomStrategy extends Strategy {
  def choose(hand: Hand, gameState: GameState) = {
    val card: Card = /* randomly pick card1 or card2 */
    /* the type of the match block is CardEffectParams, not card.Params */
    val param: card.Params = card match {
      case CardA => OneTarget(...)
      case CardB => TwoTargets(...)
    }
  }
}

My current idea is to define a factory method within each card object that takes an hlist of arguments from which it produces the correct type: 我目前的想法是在每个卡对象中定义一个工厂方法,该方法接受一个参数列表,从中生成正确的类型:

trait Card {
  type Params <: CardEffectParams
  type HListTypeOfParams = /* insert shapeless magic */
  def create[L <: HListTypeOfParams](l: L): Params
}

from which I can then do the following? 然后,我可以从中做到以下几点?

// no idea if this works or not
val card: Card = ...
val params: card.Params = card match {
  case c: CardA => c.create(1 :: HNil)
  case c: CardB => c.create(1 :: 2 :: HNil)
}

But I feel like I've gone too far down the rabbit hole. 但我觉得我已经走得太远了兔子洞。 Is what I want to achieve possible? 我想要实现的目标是什么? Is it necessary? 有必要吗? Do I need to dive so deep into typing to ensure static type safety or am I missing something really elementary? 我是否需要深入打字才能确保静态类型安全,或者我错过了一些非常基本的东西?

For the first question, I would replace your tuple with a type that represents the relationship 对于第一个问题,我会用代表关系的类型替换你的元组

trait CardAndParams {
    type C <: Card
    val card: C
    val params: C#Params
}

def choose[R <: CardAndParams](hand: Hand, gameState: GameState)(
    implicit helper: Helper {type Out = R}): R

You will need to use implicits like my Helper example to drive the actual strategy implementations and ensure the correct R is inferred. 您需要使用像我的Helper示例这样的隐含来驱动实际的策略实现并确保推断出正确的R. This is also the more usual way to do type-level computation: 这也是进行类型级计算的更常用方法:

sealed trait RandomStrategyHelper[C <: Card] {
    def params(): C#Params
}
object RandomStrategyHelper {
    implicit def forCardA = new RandomStrategyHelper[CardA] {
        def params() = 1 :: HNil
    }
    implicit def forCardB = new RandomStrategyHelper[CardB] {
        def params() = 1 :: 2 :: HNil
    }
}

def randomParams[C <: Card](card: C)(implicit rsh: RandomStrategyHelper[C]) =
    rsh.params()

But I guess you need a way to move from your randomly-generated card to a strongly typed one, and for that the pattern match seems appropriate, since it would be difficult to represent a random card at type level. 但我想你需要一种从随机生成的卡转移到强类型卡的方法,并且因为模式匹配似乎是合适的,因为很难在类型级别表示随机卡。

In general this kind of type-level programming is possible but hard in Scala - the language wasn't really designed for it. 一般来说,这种类型级编程是可能的,但在Scala中很难 - 语言并不是真的为它设计的。 If you want to push this as far as it will go you may be better off using something like Idris. 如果你想尽可能地推动它,你最好使用像伊德里斯这样的东西。

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

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