依赖于目标类型的Shapeless map HList

7

我有一个问题,我想将HList的项目映射到另一个HList,但是如果“目标”类型为URL,则源HList中的字符串应该仅转换为URL。

val name = "Stackoverflow"
val url = "https://stackoverflow.com/q"
val list = name :: url :: HNil

val mapped: String :: URL :: HNil = list.map(???)

据我研究,所有的Poly相关内容只关心输入类型而不关心输出类型。那么有没有实现我的目标的方法呢?
1个回答

8
我认为你可能无法完全得到你想要的,因为Scala的隐式解析发生在类型推断之前(但是谁知道呢——在Scala中,人们总是做出让我惊讶的事情)。
(顺便说一下:CanBuildFrom / breakOut 模式支持类似于您所要求的内容,但我没有找到在这种情况下使其工作的方法,因为源类型限制了哪些实例可用。)
不过,对于这种情况有一个相当标准的解决方法,涉及使用辅助类来近似应用类型参数的部分。假设您有一个相当简单的类型类来捕获转换逻辑:
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)
  }
}

现在您可以尝试类似以下的方式:
def convert[I <: HList, O <: HList](i: I)(implicit c: Convert[I, O]): O = c(i)

但是这里有两个问题。第一个问题是,如果您让类型参数被推断,您将始终获得将每个字符串转换为URL的转换。您可以通过显式提供两个类型参数来覆盖此行为,但这很烦琐。
我们可以通过使用帮助类(kind of)改善这种情况:
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]

现在您可以编写以下内容:
scala> val mapped = convert[String :: URL :: HNil](list)
mapped: shapeless.::[String,shapeless.::[java.net.URL,shapeless.HNil]] = Stackoverflow :: https://stackoverflow.com/q :: HNil

虽然不完全符合您的要求,但我们可以通过显式指定所需目标类型来实现相似的效果,这是唯一需要明确指定的类型。


这基本上就是我所要求的,只是我不知道最终会是什么样子。谢谢! - shim_

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接