【问题标题】:Scala: overloading methods based on provided typeScala:基于提供的类型重载方法
【发布时间】:2016-05-02 18:08:00
【问题描述】:

考虑一个简单的对象,它可以存储一些按类型区分的内聚数据。我希望它有一个 API:

  • 一致且简洁;
  • 编译时安全。

我可以通过使用重载轻松提供这样的API来保存对象:

object CatsAndDogsStorage {
  def save(key: String, cat: Cat): Future[Unit] = { /* write cat to db */ }
  def save(key: String, dog: Dog): Future[Unit] = { /* save dog to Map */ }
  /* other methods */
}

但是我找不到一个好的方法来声明这些加载对象的方法。理想情况下,我想要这样的东西:

// Futures of two unrelated objects
val catFuture: Future[Cat] = CatsAndDogsStorage.load[Cat]("Lucky")
val dogFuture = CatsAndDogsStorage.load[Dog]("Lucky")

我对 Scala 还很陌生,但我知道我有这些选项(从最不喜欢的开始排序):

1。不同的方法名

def loadCat(key: String): Future[Cat] = { /* ... */ }
def loadDog(key: String): Future[Dog] = { /* ... */ }

不是最简洁的方法。我不喜欢如果我决定将 Cat 重命名为其他名称,我也必须重命名该方法。

2。提供的类的运行时检查

def load[T: ClassTag](key: String): Future[T] = classTag[T] match {
  case t if t == classOf[Dog] => /* ... */
  case c if c == classOf[Cat] => /* ... */
}

这个给出了所需的语法,但它在运行时失败,而不是编译时。

3。虚拟隐含

def load[T <: Cat](key: String): Future[Cat] = /* ... */
def load[T <: Dog](key: String)(implicit i1: DummyImplicit): Future[Dog]

当您需要支持多种类型时,此代码将成为噩梦。删除这些类型也很不方便

4。密封特征 + 运行时检查

sealed trait Loadable
case class Cat() extends Loadable
case class Dog() extends Loadable

def load[T <: Loadable: ClassTag](key: String): Future[T] = classTag[T] match {
  case t if t == classOf[Dog] => /* ... */
  case c if c == classOf[Cat] => /* ... */
}

这具有 2) 的优点,同时防止用户询问除了狗或猫之外的任何东西。不过,我宁愿不改变对象层次结构。我可以使用union types 来缩短代码。


所以,最后一个解决方案是可以的,但它仍然感觉 hack-ish,也许还有另一种我无法弄清楚的已知方法。

【问题讨论】:

  • 我认为你应该检查Type Classes,这是类型驱动泛型专业化的常用模式。

标签: scala api-design


【解决方案1】:

使用名称稍有不同的函数做类似的工作,但类型不同对我来说似乎并不坏。

如果你真的想根据类型进行外观 API 调度,你可以使用类型类。

trait SaveFn[T] extends (T => Future[Unit]) {}

object SaveFn {
  implicit object SaveDog extends SaveFn[Dog] { def apply(dog: Dog): Future[Unit] = ??? }

  implicit object SaveCat extends SaveFn[Dog] { def apply(cat: Cat): Future[Unit] = ??? }
}

object Storage {
  def save[T : SaveFn](in: T): Future[Unit] = implicitly[SaveFn[T]](in)
}

对于.load 案例:

trait LoadFn[T] extends (String => Future[T]) {}

object LoadFn {
  implicit object LoadDog extends LoadFn[Dog] { def apply(key: String): Future[Dog] = ??? }

  implicit object LoadCat extends LoadFn[Cat] { def apply(key: String): Future[Cat] = ??? }
}

object Storage {
  def load[T : LoadFn](key: String): Future[T] = implicitly[LoadFn[T]](key)
}

至于.load,无法根据.save的参数找到推断,使用起来不太好:Storage.load[Dog]("dogKey")

【讨论】:

  • 感谢您的示例。我知道用不同的名字并没有错,我只是在探索我的宠物项目以获得一些乐趣。
猜你喜欢
  • 2011-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多