Scala:根据提供的类型重载方法
Scala: overloading methods based on provided type
考虑一个简单的对象,它用作一些按类型区分的内聚数据的存储。我希望它有一个 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。提供的运行时检查 class
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) 的优点,同时防止用户询问除 Dog 或 Cat 之外的任何内容。不过,我宁愿不更改对象层次结构。我可以使用 union types 来缩短代码。
所以,最后一个解决方案还可以,但它仍然感觉很乱,也许还有另一种我无法弄清楚的已知方法。
使用名称略有不同的函数做类似的工作,但对于不同的类型,对我来说似乎并不坏。
如果你真的想要一个外观 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")
考虑一个简单的对象,它用作一些按类型区分的内聚数据的存储。我希望它有一个 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。提供的运行时检查 class
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) 的优点,同时防止用户询问除 Dog 或 Cat 之外的任何内容。不过,我宁愿不更改对象层次结构。我可以使用 union types 来缩短代码。
所以,最后一个解决方案还可以,但它仍然感觉很乱,也许还有另一种我无法弄清楚的已知方法。
使用名称略有不同的函数做类似的工作,但对于不同的类型,对我来说似乎并不坏。
如果你真的想要一个外观 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")