Scala 中的链接类型

Linking Types in Scala

我有 class 个具有一种或另一种 ID 的对象:

sealed trait ItemId
case class NumericId(id: Int) extends ItemId
case class StringId(id: String) extends ItemId

sealed trait Item {
  def id: ItemId
}

case class ItemWithNumericId(id: NumericId) extends Item
case class ItemWithStringId(id: StringId) extends Item

我想为某种检索项目的服务创建一个接口:

trait ItemService[IdType <: ItemId, ItemType <: Item] {
  def get(id: IdType): ItemType
}

我如何 link ItemId 类型和 Item 类型来限制 ItemService 不允许这样的事情:

class SillyItemService extends ItemService[NumericId, ItemWithStringId] {
  def get(id: NumericId): ItemWithStringId = ???
}

我意识到我可以将泛型类型添加到 Item class:

sealed trait ItemId
case class NumericId(id: Int) extends ItemId
case class StringId(id: String) extends ItemId

sealed trait Item[Id <: ItemId] {
  def id: Id
}

case class ItemWithNumericId(id: NumericId) extends Item[NumericId]
case class ItemWithStringId(id: StringId) extends Item[StringId]

trait ItemService[IdType <: ItemId, ItemType <: Item[IdType]] {
  def get(id: IdType): ItemType
}

还可以,但是太冗长了。理想情况下,该服务将只有一个通用类型。

非常感谢任何answers/input。

也许是这样的?

 trait Item {
    type IdType
    def id: IdType
 }

 trait ItemService[I <: Item] {
   def get(id: I#IdType): Item
 }

路径相关类型是另一个答案涵盖的一个选项,但出于灵活性的目的,在这种情况下我个人会选择 implicits/context 边界。

trait Proof[IdType <: ItemId, ItemType <: Item[IdType]]

trait ItemService[IdType <: ItemId, ItemType <: Item[IdType]] {
  def get(id: IdType)(implicit ev: Proof[IdType, ItemType])
}

您还可以使 trait 成为 abstract class 来提升隐式证明的声明,或者其他许多可用的技巧让您避免在每个方法中包含证据的需要在服务上。

然后我会为 Proof 创建一个伴生对象并列出在您的域内可行的相关性。

object Proof {
  implicit numericProof: Proof[NumericId, ItemWithNumericId] = new Proof[NumericId, ItemWithNumericId] {}
  ...
}

此时你并不真正关心你的服务是什么样子,尽管 f 有界多态性稍后可能允许你进行超细粒度的控制,因为你可以为特定的实现专门隐含,并在你创建模棱两可的证明想要为不应该混合在一起的事物提供编译时错误。

这将是我的方法:

sealed trait ItemId
case class NumericId(id: Int) extends ItemId
case class StringId(id: String) extends ItemId

trait Item[A <: ItemId] {
  def id: A
}

trait ItemService[A <: ItemId, B <: Item[A]] {
  def get(id: A): B
}

老实说,这与您所做的并没有太大区别,我只是认为没有太多必要将 Item 特征密封并在那里引入两个实现。

如果您不需要缩小特定 ItemServiceget 方法的 return 类型,您甚至可以将 B 类型参数保留为让事情更简单一步:

trait ItemService[A <: ItemId] {
  def get(id: A): Item[A]
}