2015-10-21 34 views
3

給定HList爲Label[A](String)我想將它映射到LabelWithValue[A](Label[A], A)的HList,其中實際值來自Map[String, Any]。在下面的例子中,我剛剛定義了方法中值的映射,只要想象這些值來自數據庫即可。帶有自變量的無形HList多態映射

下面的工作,但它是非常veeery hacky,因爲它使用全局變量。相反,我想通過Map[String, Any]GetLabelWithValue。我沒有找到方法,因爲getValues的調用者隱式地創建了一個映射器,並且此時值的映射尚不存在。我試圖自己創建一個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]) 
    } 
} 

回答

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來捕獲它並根據它來決定。可能會給你多一點比你需要的力量(因爲你可以改變地圖之間的摺疊,但...)

1

當你想在方法中使用它時,這裏有完整的解決方案(基於KadekM的解決方案)。最難的是從元組中提取出類型(這是摺疊的結果)。

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