I have the following problem, I want to map items of an HList to another HList but Strings in the source HList should only be converted to URL if the "target" type is URL.
val name = "Stackoverflow"
val url = "https://stackoverflow.com/q"
val list = name :: url :: HNil
val mapped: String :: URL :: HNil = list.map(???)
As far as my research took me is that all the Poly stuff only cares about the input type but not about the output type. So are there ways to archive my goal ?
I don't think you're going to get exactly what you want, since Scala's implicit resolution happens before type inference (but who knows—people do things that surprise me in Scala all the time).
(Side note: the CanBuildFrom
/ breakOut
pattern supports something similar to what you're asking for, but I don't see a way to make it work in this situation, since the source type does constrain what instances are available.)
There is a pretty standard workaround for this kind of situation, though, involving the use of a helper class to approximate partial application of type parameters. Suppose you've got a fairly straightforward type class that captures your conversion logic:
import java.net.URL
import shapeless._
trait Convert[I <: HList, O <: HList] { def apply(i: I): O }
object Convert extends LowPriorityConvertInstances {
implicit val convertHNil: Convert[HNil, HNil] = new Convert[HNil, HNil] {
def apply(i: HNil): HNil = i
}
implicit def convertHConsURL[T <: HList, TO <: HList](implicit
c: Convert[T, TO]
): Convert[String :: T, URL :: TO] = new Convert[String :: T, URL :: TO] {
def apply(i: String :: T): URL :: TO = new URL(i.head) :: c(i.tail)
}
}
sealed class LowPriorityConvertInstances {
implicit def convertHCons[H, T <: HList, TO <: HList](implicit
c: Convert[T, TO]
): Convert[H :: T, H :: TO] = new Convert[H :: T, H :: TO] {
def apply(i: H :: T): H :: TO = i.head :: c(i.tail)
}
}
Now you might try something like this:
def convert[I <: HList, O <: HList](i: I)(implicit c: Convert[I, O]): O = c(i)
But there are two problems here. The first is that if you let the type parameters be inferred, you'll always get a conversion that turns every string into a URL. You can override this behavior by explicitly providing both type parameters, but ugh.
We can (kind of) improve this situation with a helper class:
class PartiallyAppliedConvert[O <: HList] {
def apply[I <: HList](i: I)(implicit c: Convert[I, O]): O = c(i)
}
def convert[O <: HList]: PartiallyAppliedConvert[O] =
new PartiallyAppliedConvert[O]
Now you can write the following:
scala> val mapped = convert[String :: URL :: HNil](list)
mapped: shapeless.::[String,shapeless.::[java.net.URL,shapeless.HNil]] = Stackoverflow :: https://stackoverflow.com/q :: HNil
It's not exactly what you asked for, but it's pretty close, in that the only type we have to specify explicitly is the desired target type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With