简体   繁体   中英

How to avoid calling asInstanceOf in Scala with family polymorphism

By design, we know for sure that we have an instance of HourlyDateFormat

How to avoid calling asInstanceOf in this case (ie how to help the compiler to infer the type)?

  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)
    }
  }

Try to add one more implicit parameter

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

Assertion looks strange: assert(ev == ev) .

Or just

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

Fixed version (I added one more type parameter, it's now similar to the first @user 's version)

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()))

Generally things like that are kind-of type-classy, so I would do it this way instead:

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)
}

It has the advantage that you don't have to be aware of existence of types HourlyDateFormat and DailyDateFormat to make it work.

A quick fix would be something like this:

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

However, I don't think you've designed this the right way. It'd probably be better to have a separate trait for each type of format. This makes it more scalable, since you don't need to add a case in your match expression for each different class, the right method gets selected automatically at runtime. You still have to use those evidence parameters, which I don't like, but it still feels cleaner to me.

Edit: I've updated the code so that everything extends FormatLocalDate and you only need an evidence parameter for 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM