使用参数的Shapeless HList多态映射

3
给定一个由Label[A](String)组成的HList,我想将其映射为由LabelWithValue[A](Label[A], A)组成的HList,实际值来自于Map[String, Any]。在下面的示例中,我只是在方法中定义了一个值的映射,想象一下这些值来自于数据库。
下面的代码可以工作,但它非常非常hacky,因为它使用了全局变量。相反,我想将Map[String, Any]传递到GetLabelWithValue中。然而我没有找到一种方法,因为getValues的调用者隐式地创建了一个Mapper,在那个时刻值的映射还不存在。我试图自己创建一个Mapper,但我的类型级别编程技能还不够好。
import shapeless._
import shapeless.poly._
import shapeless.ops.hlist._

object Main extends App {
  case class Label[A](name: String)
  case class LabelWithValue[A](label: Label[A], value: A)

  // TODO: avoid the horrible global state - pass in the Map as a parameter
  var horribleGlobalState: Map[String, Any] = _
  object GetLabelWithValue extends (Label ~> LabelWithValue) {
    def apply[A](label: Label[A]) =
        LabelWithValue(label, horribleGlobalState.get(label.name).asInstanceOf[A])
  }

  val label1 = Label[Int]("a")
  val label2 = Label[String]("b")
  val labels = label1 :: label2 :: HNil
  val labelsWithValues: LabelWithValue[Int] :: LabelWithValue[String] :: HNil = getValues(labels)
  println(labelsWithValues)

  def getValues[L <: HList, M <: HList](labels: L)(
    implicit mapper: Mapper.Aux[GetLabelWithValue.type, L, M]) = {

    horribleGlobalState = Map("a" -> 5, "b" -> "five")
    labels map GetLabelWithValue
  }
}

这里是GetLabelWithValue的另一种实现方式,其行为与原版相同:
object GetLabelWithValue extends Poly1 {
  implicit def caseLabel[A] = at[Label[A]] { label ⇒
    LabelWithValue(label, horribleGlobalState.get(label.name).asInstanceOf[A])
  }
}
2个回答

5

我并不是一个毫无形状的大师,但以下是我首先想到的:

object Main extends App {
  case class Label[A](name: String)
  case class LabelWithValue[A](label: Label[A], value: A)

  object combine extends Poly2 {
    implicit def workS[A <: HList, B] = at[Label[B], (Map[String, Any], A)] {
      case (i, (map, res)) ⇒
        (map, LabelWithValue(i, map.get(i.name).asInstanceOf[B]) :: res)
    }
  }

  var state: Map[String, Any] = Map("a" -> 5, "b" -> "five")

  val label1 = Label[Int]("a")
  val label2 = Label[String]("b")

  val labels = label1 :: label2 :: HNil
  val mapped = labels.foldRight((state, HNil))(combine)._2
  println(mapped)
}

我不是说这没有更好的方法,但这似乎相当合理 - 你可以使用fold来捕获而不是全局状态,并根据它进行决策。可能会给你比你需要的更多的能力(因为你可以在折叠之间改变映射),但...


2
这是完整的解决方案(基于KadekM的方案),适用于在方法中使用。难点在于从元组中提取类型(这是fold的结果)。
import shapeless._
import shapeless.ops.hlist._
import shapeless.ops.tuple.IsComposite

object Main extends App {
  case class Label[A](name: String)
  case class LabelWithValue[A](label: Label[A], value: A)

  object combineLabelWithValue extends Poly2 {
    implicit def atLabel[A, B <: HList] = at[Label[A], (B, Map[String, Any])] {
      case (label, (acc, values)) ⇒
        (LabelWithValue(label, values.get(label.name).asInstanceOf[A]) :: acc, values)
    }
  }

  val label1 = Label[Int]("a")
  val label2 = Label[String]("b")
  val labels = label1 :: label2 :: HNil

  val labelsWithValues: LabelWithValue[Int] :: LabelWithValue[String] :: HNil = getValues(labels)
  println(labelsWithValues)

  def getValues[L <: HList, Out, P](labels: L)(
    implicit folder: RightFolder.Aux[L, (HNil.type, Map[String, Any]), combineLabelWithValue.type, P],
    ic: IsComposite.Aux[P, Out, _]
    ): Out = {
    val state = Map("a" -> 5, "b" -> "five")
    val resultTuple = labels.foldRight((HNil, state))(combineLabelWithValue)
    ic.head(resultTuple)
  }
}

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