【问题标题】:How to avoid calling asInstanceOf in Scala如何避免在 Scala 中调用 asInstanceOf
【发布时间】:2020-10-24 05:34:15
【问题描述】:

这是我的代码的简化版本。

我怎样才能避免打电话给asInstanceOf(因为这是一个糟糕的设计解决方案的味道)?

sealed trait Location
final case class Single(bucket: String)     extends Location
final case class Multi(buckets: Seq[String]) extends Location

@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
class Log[L <: Location](location: L, path: String) { // I prefer composition over inheritance
  // I don't want to pass location to this method because it's a property of the object
  // It's a separated function because there is another caller
  private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"

  def getPaths(): Seq[String] =
    location match {
      case _: Single => Seq(this.asInstanceOf[Log[_ <: Single]].getSinglePath())
      case m: Multi  => m.buckets.map(bucket => s"fs://${bucket}/$path")
    }
}

【问题讨论】:

    标签: scala casting pattern-matching typeclass implicit


    【解决方案1】:

    尝试类型类

    class Log[L <: Location](location: L, val path: String) {
      def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
      def getPaths()(implicit gp: GetPaths[L]): Seq[String] = gp.getPaths(location, this)
    }
    
    trait GetPaths[L <: Location] {
      def getPaths(location: L, log: Log[L]): Seq[String]
    }
    object GetPaths {
      implicit val single: GetPaths[Single] = (_, log) => Seq(log.getSinglePath())
      implicit val multi:  GetPaths[Multi]  = (m, log) => m.buckets.map(bucket => s"fs://${bucket}/${log.path}")
    }
    

    通常,类型类是模式匹配的编译时替换。

    我必须将 getSinglePath 设为 public 并将 path 设为 val,以便在 GetPaths 中提供对它们的访问权限。如果您不想这样做,可以将类型类嵌套到Log

    class Log[L <: Location](location: L, path: String) {
      private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
      def getPaths()(implicit gp: GetPaths[L]): Seq[String] = gp.getPaths(location)
    
      private trait GetPaths[L1 <: Location] {
        def getPaths(location: L1): Seq[String]
      }
      private object GetPaths {
        implicit def single(implicit ev: L <:< Single): GetPaths[L] = _ => Seq(getSinglePath())
        implicit val multi: GetPaths[Multi] = _.buckets.map(bucket => s"fs://${bucket}/$path")
      }
    }
    

    实际上我们不必显式传递location,也不需要L1

    class Log[L <: Location](location: L, path: String) {
      private def getSinglePath()(implicit ev: L <:< Single): String = s"fs://${location.bucket}/$path"
      def getPaths()(implicit gp: GetPaths): Seq[String] = gp.getPaths()
    
      private trait GetPaths {
        def getPaths(): Seq[String]
      }
      private object GetPaths {
        implicit def single(implicit ev: L <:< Single): GetPaths = () => Seq(getSinglePath())
        implicit def multi(implicit ev: L <:< Multi):   GetPaths = () => location.buckets.map(bucket => s"fs://${bucket}/$path")
      }
    }
    

    现在GetPaths 是零参数type class,与magnet pattern 略有相似。

    【讨论】:

    • 使用TC,必须显式传递位置,所以有编码错误的余地:gp.getPaths(SingleBucket("nottheonefrommyobject")private def getSinglePath(location: Single): String = s"fs://${location.bucket}/$path" 相比似乎没有太大的胜利
    • @YannMoisan 你是对的。我们不必明确传递location。查看更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-11
    • 1970-01-01
    • 2019-02-09
    • 2020-01-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多