Scala:基于提供的类型重载方法

时间:2016-01-26 08:17:44

标签: scala api-design

考虑一个简单的对象,它可以作为存储类型的一些内聚数据。我希望它有一个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缩短代码。

所以,最后一个解决方案没问题,但它仍然感觉很糟糕,也许还有另一种我无法弄清楚的已知方法。

1 个答案:

答案 0 :(得分:2)

具有不同名称的函数执行类似的工作,但对于不同的类型对我来说似乎并不坏。

如果你真的想根据类型进行外观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")