简体   繁体   中英

Shapeless HList polymorphic map with an argument

Given an HList of Label[A](String) I want to map it into an HList of LabelWithValue[A](Label[A], A) , where the actual values come from a Map[String, Any] . In the example below I just defined the map of values in the method, just imagine the values come from a database.

The below works, but it is very veeery hacky because it uses a global var. Instead I'd like to pass the Map[String, Any] into GetLabelWithValue . I didn't find a way though, because the caller of getValues implicitly creates a Mapper, and at that point the map of values doesn't exist yet. I tried to create a Mapper myself, but my type level programming skills aren't yet good enough.

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
  }
}

Here is an alternative implementation of GetLabelWithValue, which behaves the same way:

object GetLabelWithValue extends Poly1 {
  implicit def caseLabel[A] = at[Label[A]] { label ⇒
    LabelWithValue(label, horribleGlobalState.get(label.name).asInstanceOf[A])
  }
}

I am by no means shapeless guru but here's first thing that comes to my mind:

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)
}

I'm not saying there's not better way, but this seems pretty reasonable - instead of global state you capture it using fold and decide based on it. Probably gives you a bit more power than you need though (as you could mutate the map inbetween folds, but...)

Here's the full solution (based on KadekM's solution) when you want to use it in a method. The hard bit was to extract the type out of the tuple (which is the result of the 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)
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM