I have a sealed case class family that specify some rules, which are deserialized from an external source. I also have a typeclass with a few instances to perform the actual logic, like those:
import scala.util.Try
sealed trait ReaderConfig
case class Substring(begin: Int, end: Int) extends ReaderConfig
case class Regex(expr: String) extends ReaderConfig
trait Read[M[_], RC <: ReaderConfig] {
def apply(config: RC, raw: String): M[String]
}
object Read {
implicit val TryReadSubstring: Read[Try, Substring] = (config: Substring, raw: String) => Try {
raw.substring(config.begin, config.end)
}
implicit val TryReadRegex: Read[Try, Regex] = (config: Regex, raw: String) => Try {
config.expr.r.findFirstIn(raw).get
}
trait Helper[RC <: ReaderConfig] {
def as[M[_]](implicit read: Read[M, RC]): M[String]
}
def apply[RC <: ReaderConfig](config: RC)(raw: String) = new Helper[RC] {
override def as[M[_]](implicit read: Read[M, RC]): M[String] = read.apply(config,raw)
}
}
Now, while using it with concrete types there's no problem to find the correct implicit.
@ val ok: Try[String] = Read(Substring(0,1))("abc").as[Try]
ok: Try[String] = Success("a")
@ val Fail: Try[String] = Read(Substring(1000,9001))("abc").as[Try]
Fail: Try[String] = Failure(
java.lang.StringIndexOutOfBoundsException: String index out of range: 9001
)
When I have a val that has the upper trait as type (such as when I deserialize it as mentioned above), it fails to compile, as expected:
@ val config: ReaderConfig = Substring(0,1)
config: ReaderConfig = Substring(0, 1)
@ val fail2: Try[String] = Read(config)("abc").as[Try]
cmd8.sc:1: could not find implicit value for parameter read: $sess.cmd2.Read[scala.util.Try,$sess.cmd1.ReaderConfig]
val fail2: Try[String] = Read(config)("abc").as[Try]
^
Compilation Failed
The only solution I came up with is to write a function that will match up the actual types with the correct instances, such as:
val tryRead: ReaderConfig => String => Try[String] = {rc => raw => rc match {
case s: Substring => Read[Substring](s)(raw).as[Try]
case r: Regex => Read[Regex](r)(raw).as[Try]
}}
Then it happily compiles and I can use it in those circumstances.
@ tryRead(config)("abc")
res9: Try[String] = Success("a")
The trait is sealed, so the compiler should warn me about missing case
s, but obviously it will turn out to be very cumbersome as soon as I have more of those instances.
Is there some way that I could have this function automatically generated?
It is after all something that can be created by copying and pasting case
s and just filling in the variable pattern.