Reputation: 8281
I want to model log files that can be hourly or daily
sealed trait Level
case object Hour extends Level
case object Day extends Level
sealed trait Log[L <: Level]
and I want a method that return all logs for given a level. So here is the signature
def byLevel[L <: Level](l: L) : Seq[Log[L]]
Given some concrete log instances (there are a lot more in real code) :
case object HourlyLog extends Log[Hour.type]
case object DailyLog extends Log[Day.type]
I've figured out the following implementation :
object Log {
case class Pair[L <: Level](level : L, logs: Seq[Log[L]])
val logs = Seq(
Pair(Hour, Seq(HourlyLog)),
Pair(Day, Seq(DailyLog))
)
def byLevel[L <: Level](l: L) : Seq[Log[L]] = logs.find(_.level == l).get.logs.asInstanceOf[Seq[Log[L]]]
}
My questions are :
.asInstanceOf
?Pair
wrapper ?HMap
can do the trick) ?Upvotes: 0
Views: 122
Reputation: 44918
It seems that you are using simple object comparison on the companion objects anyway, so why complicating it all with types instead of treating Hour
and Day
as a good old enumeration? If you want to store Seq[DailyLog]
and Seq[HourlyLog]
in the same list, you will need an asInstanceOf
like it or not.
Anyway, here are some things you could do to get rid of Pair
and asInstanceOf
:
Hour
and Day
as values of an enum (only values, no types)All three approaches use neither Pair
, nor asInstanceOf
(one uses Map
, though... That's what your Pair
is doing anyway). Every one of the three walls of code is compilable on its own.
Only values, no types, essentially enum
sealed trait Level
case object Hour extends Level
case object Day extends Level
sealed trait Log
case object HourlyLog extends Log
case object DailyLog extends Log
object Log {
val logs = Map[Level, Seq[Log]](
Hour -> Seq(HourlyLog, HourlyLog, HourlyLog, HourlyLog),
Day -> Seq(DailyLog)
)
def byLevel(l: Level): Seq[Log] = logs(l)
}
import Log._
println(byLevel(Hour))
println(byLevel(Day))
Output:
List(HourlyLog, HourlyLog, HourlyLog, HourlyLog)
List(DailyLog)
Only types, no object values, implicits
sealed trait Level
sealed trait Hour extends Level
sealed trait Day extends Level
abstract class Log[L <: Level]
case object HourlyLog extends Log[Hour]
case object DailyLog extends Log[Day]
object Log {
case class Logs[L <: Level](val logs: Seq[Log[L]])
implicit val hourlyLogs = Logs[Hour](Seq(
HourlyLog, HourlyLog, HourlyLog, HourlyLog
))
implicit val dailyLogs = Logs[Day](Seq(
DailyLog
))
def byLevel[L <: Level](implicit logs: Logs[L]): Seq[Log[L]] =
logs.logs
}
import Log._
println(byLevel[Hour])
println(byLevel[Day])
Output:
List(HourlyLog, HourlyLog, HourlyLog, HourlyLog)
List(DailyLog)
Hybrid approach, everything mixed together
sealed trait Level
case object Hour extends Level
case object Day extends Level
sealed trait Log[L <: Level]
case object HourlyLog extends Log[Hour.type]
case object DailyLog extends Log[Day.type]
object Log {
case class Logs[L <: Level](val logs: Seq[Log[L]])
implicit val hourlyLogs = Logs[Hour.type](Seq(
HourlyLog, HourlyLog, HourlyLog, HourlyLog
))
implicit val dailyLogs = Logs[Day.type](Seq(
DailyLog
))
def byLevel[L <: Level](l: L)(implicit logs: Logs[L]): Seq[Log[L]] =
logs.logs
}
import Log._
println(byLevel(Hour))
println(byLevel(Day))
Output:
List(HourlyLog, HourlyLog, HourlyLog, HourlyLog)
List(DailyLog)
Upvotes: 1
Reputation: 27535
Depending on case there are different ways of getting rid of casting.
if you want to treat Log[Hour]
as special case of Log[Level]
(so each time you need Log[Level] you can substitute it with Log[Hour]
) use covariance: [+L <: Level]
You will need it in order to HourlyLog
to be treated as a subtype of Log
(sealed trait Log[+L <: Level]
)
when you have a case of something that is collection of potentially mixed subtypes, you can use collect:
def byLevel[L <: Level](l: L): Seq[Log[L]] = logs.collect { case Pair(l2, logs: Seq[Log[L]]) if l2 == l => logs }.flatten
Well, you still need to annotate the type of logs, but it as a lot more clean and doesn't require you to use shapeless not any other magic.
As for getting rid of Pair wrapper - thing is on JVM you will have a type erasure. You will need to compare against something that is runtime checked. I can think of something like ClassTag
, or TypeTag
... but you would still need to store it somewhere (e.g. as an implicit constructor argument sealed trait Log[+L <: Level: ClassTag]
) and then compare against it. So I am certain it is, but it would produce some boilerplate.
Alternatively you could filter/collect using HourlyLog
, but then you would have to store it expicitly somewhere to map Hour
to filtering by HourlyLog
... and I am not sure it would be worth the effort. Probably if you do it on a regular basic it would be worth to roll out dedicated type class and providing implicits... but that is your call. Depending on you use case that might be overengineering and purity for the sake of purity... or something that keeps codebase maintainable. In my humble experience sometimes "dirty" solution does the job well enough.
Upvotes: 0