【问题标题】:How to avoid calling asInstanceOf in Scala with family polymorphism如何避免在具有家族多态性的 Scala 中调用 asInstanceOf
【发布时间】:2020-07-04 06:16:40
【问题描述】:

根据设计,我们确定我们有一个 HourlyDateFormat 的实例

在这种情况下如何避免调用 asInstanceOf (即如何帮助编译器推断类型)?

  sealed trait StorageLayout extends Product with Serializable
  case object Hourly         extends StorageLayout
  case object Daily          extends StorageLayout

  sealed trait DateFormat[S <: StorageLayout]

  sealed abstract class HourlyDateFormat extends DateFormat[Hourly.type] {
    def format(localDate: LocalDate): String         = ???
    def format(localDateTime: LocalDateTime): String = ???
  }

  sealed abstract class DailyDateFormat extends DateFormat[Daily.type] {
    def format(localDate: LocalDate): String = ???
  }

  class Log[S <: StorageLayout](storageLayout: S, dateFormat: DateFormat[S]) {
    def getPath(date: LocalDate): String =
      dateFormat match {
        case hdf: HourlyDateFormat => hdf.format(date)
        case ddf: DailyDateFormat  => ddf.format(date)
      }
    @SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
    def getPath(date: LocalDateTime)(implicit ev: S =:= Hourly.type): String = {
      assert(ev == ev)
      dateFormat.asInstanceOf[HourlyDateFormat].format(date)
    }
  }

【问题讨论】:

  • 如果你能描述一下你可以改变什么,你的最终目标是什么,这可能会有所帮助?你想怎么用Log

标签: scala casting


【解决方案1】:

一般来说,这样的事情有点典型,所以我会这样做:

trait DailyFormatter[S] {
  def formatDate(localDate: LocalDate): String
}
trait HourlyFormatter[S] {
  def formatDateTime(localDateTime: LocalDateTime): String
}

implicit val dailyFormats: DailyFormatter[Daily]
implicit val hourFormats: DailyFormatter[Hourly] with HourlyFormatter[Hourly]

class Log[S <: StorageLayout](storageLayout: S, dateFormat: DateFormat[S]) {

  def getPath(date: LocalDate)(implicit formater: DailyFormatter[S]): String =
    formater.formatDate(date)

  def getPath(date: LocalDateTime)(implicit formater: HourlyFormatter[S]): String =
    formater.formatDateTime(date)
}

它的优点是您不必知道类型 HourlyDateFormatDailyDateFormat 的存在即可使其工作。

【讨论】:

  • 不需要dateFormat和storageLayout。
  • 我同意DailyFormater 可以被删除,但这样设计就会不一致。
  • 不一致是什么意思?
  • 鉴于DailyDateFormat 可能有多个子类(这就是为什么它是抽象的),如何使用类型类?
  • @YannMoisan 它仍然适用于格式化程序的不同实现:scastie.scala-lang.org/fLSDbzP5RMipXyYIxD6w8g
【解决方案2】:

尝试再添加一个隐式参数

def getPath(date: LocalDateTime)(implicit ev: S =:= Hourly.type, ev1: DateFormat[S] =:= HourlyDateFormat): String = {
  //assert(ev == ev)
  dateFormat.format(date)
}

断言看起来很奇怪:assert(ev == ev).

或者只是

def getPath(date: LocalDateTime)(implicit ev1: DateFormat[S] =:= HourlyDateFormat): String

固定版本(我增加了一个类型参数,现在类似于第一个 @user 的版本)

class Log[S <: StorageLayout, D <: DateFormat[S]](storageLayout: S, dateFormat: D) {
  def getPath(date: LocalDate): String =
    dateFormat match {
      case hdf: HourlyDateFormat => hdf.format(date)
      case ddf: DailyDateFormat  => ddf.format(date)
    }
  def getPath(date: LocalDateTime)(implicit
                                   ev: S =:= Hourly.type,
                                   ev1: D <:< HourlyDateFormat,
  ): String = {
    dateFormat.format(date)
  }
}

val log = new Log(Hourly, new HourlyDateFormat(){})
print(log.getPath(LocalDateTime.now()))

【讨论】:

  • 断言可能是为了抑制未使用的警告。在您的第二个示例中,不需要。
  • 鉴于DailyDateFormat 可能有多个子类(这就是为什么它是抽象的),我已将=:= 更改为&lt;:&lt;,但它不起作用。 new Log(Hourly, DateFormat.HOURLY_LEADING_ZEROS).getPath(LocalDate.now)).无法证明 DateFormat[Hourly.type] <: hourlydateformat.>
  • @YannMoisan 是的,您的问题中没有测试代码。查看更新。
【解决方案3】:

快速修复将是这样的:

class Log[S <: StorageLayout, D <: DateFormat[S]](storageLayout: S, dateFormat: D) {
  def getPath(date: LocalDateTime)(implicit ev: D <:< HourlyDateFormat): String =
    dateFormat.format(date)
}

但是,我认为您的设计方式并不正确。为每种类型的格式设置一个单独的特征可能会更好。这使得它更具可扩展性,因为您不需要在匹配表达式中为每个不同的类添加一个案例,正确的方法会在运行时自动选择。您仍然必须使用那些我不喜欢的证据参数,但对我来说仍然感觉更干净。

编辑:我已经更新了代码,以便所有内容都扩展 FormatLocalDate 并且您只需要 getPath(LocalDateTime) 的证据参数

sealed trait FormatLocalDate[S <: StorageLayout] {
  def format(localDate: LocalDate): String
}
sealed trait FormatLocalDateTime[S <: StorageLayout] extends FormatLocalDate[S] {
  def format(localDate: LocalDateTime): String
}

sealed abstract class HourlyDateFormat extends FormatLocalDateTime[Hourly.type] {
  def format(localDate: LocalDate): String = ???
  def format(localDateTime: LocalDateTime): String = ???
}
sealed abstract class DailyDateFormat extends FormatLocalDate[Daily.type] {
  def format(localDate: LocalDate): String = ???
}

class Log[S <: StorageLayout, D <: FormatLocalDate[S]](storageLayout: S, dateFormat: D) {
  def getPath(date: LocalDate): String =
    dateFormat.format(date)

  def getPath(date: LocalDateTime)(implicit ev: D <:< FormatLocalDateTime[S]): String =
    dateFormat.format(date)
}

Scastie

【讨论】:

  • 我想知道我们是否可以避免提及这个隐含的(implicit ev: D &lt;:&lt; FormatLocalDate[S]),因为所有具体的DateFormat 在设计上都是这个的子类。
  • @YannMoisan 在这种情况下,您可以将FormatLocalDate 设置为超级特征而不是FormatDate。让我更新我的答案
猜你喜欢
  • 2020-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-11
  • 1970-01-01
  • 1970-01-01
  • 2019-02-09
  • 2020-01-30
相关资源
最近更新 更多