Scala,将异构地图组合-Hmap与类运算

时间:2018-07-09 15:22:47

标签: scala shapeless monoids

我希望创建一个可以包含任意数量的已定义元组的异构映射:

A -> B
C -> D
E -> F
etc

现在对于B,D,F的每个类型都有一个Monoid类型类,因此从理论上讲,我可以为我的异构映射创建一个Monoid。
有没有一种完美的方法来实现这一目标?

1 个答案:

答案 0 :(得分:1)

这取决于。如果您只想合并地图,那很容易:只需HMap.empty[YourType](hm1.underlying ++ hm2.underlying)

但是您对monoid的提及表明您希望合并相应位置上的值。

我会说:没有附加信息是不可能的。这样的信息将是例如在编译时就知道了您放置在HMap中的类型的副产品。

HMap不会为您跟踪它,因此您必须自己实现它,方法是使用一些包装器/构建器,该包装器/构建器会在每次添加内容时(例如,

)更新返回的类型。

class Wrapper[R[_,_], Vs <: Coproduct](hmap: HMap[R] = HMap.empty[R]) {

  def +[K,V](k: K, v: V): Wrapper[R, V :+: Vs] = new Wrapper(hmap + (k,v))
}

object Wrapper {

  def apply[R[_,_]]: Wrapper[R, CNil] = new Wrapper()
}

这个Coproduct可以让您归纳地构建一些通用的类半体(尽管为简单起见,我实现了半群):

// treat it as pseudocode, sth might not work here
trait CoproductSemigroup[A] { def combine(a1: Any, a2: Any): Any }
implicit val cnilCombine = new CoproductSemigroup[CNil] { def combine(a1: Any, a2: Any): Any = ??? }
implicit val coproductCombine[H : ClassTag : Semigroup,
                              T <: Coproduct : CoproductSemigroup] =
  new CoproductSemigroup[H :+: T] {

    def combine(a1: Any, a2: Any): Any = {
      if (implicitly[ClassTag[H].runtimeClass.isInstance(a1) && 
            implicitly[ClassTag[H].runtimeClass.isInstance(a2))
        implicitly[Semogroup[H].combine(a1.asInstanceOf[H], a2.asInstanceOf[H])]
      else
        implicitly[CoproductSemigroup[T]].combine(a1, a2)
    }
  }

已经变得很丑陋,然后您必须manually group values by the same key并将此功能应用于每个组。

最后,您必须根据上面的代码创建一个monoid。包装器最有可能,因为它已经包含了您在这些隐式中迫切需要的类型信息。

可能有更好的方法来实现它,但是我唯一看到的是……非常丑陋和不安全。