在Shapeless中实现与路径相关的Map类型

时间:2018-01-17 06:49:43

标签: scala shapeless hlist

我想把一个包含从外到内的路径依赖类型映射的地图放在一起:

import shapeless._
import shapeless.ops.hlist._

abstract class Outer {
  type Inner
  def inner: Inner
}

private case class Derp(hl: HList) {
  def get(outer: Outer): outer.Inner = hl.select[outer.Inner]
  def put(outer: Outer)(inner: outer.Inner): Derp = Derp(hl :: inner :: HNil)
  def delete(outer: Outer)(c: outer.Inner): Derp = ???
}

理论上,通过使用HList,我可以避免使用类型选择器,并确保程序只能获取由Inner创建的Outer实例。

这可能,甚至是个好主意吗?大多数HList问题似乎都是关于arity和case类的,我觉得我在盒子外工作。

请注意,我知道https://stackoverflow.com/a/30754210/5266,但这个问题特别是关于Shapeless HList实现 - 我不知道如何从HList中删除元素并可能返回Option [outer.Inner]。

1 个答案:

答案 0 :(得分:1)

首先,您几乎肯定需要参数化Derp

case class Derp[L <: HList](hl: L)

这是因为只要你有Derp,编译器就需要知道它的静态HList类型是什么,以便做任何有用的事情。

HList类型对列表中每种类型的信息进行编码 - 比如

type Foo = Int :: String :: Boolean :: HNil

只要您说hl: HList,该信息就会丢失。

接下来,您需要正确指定操作的返回类型:

def get[O <: Outer](o: O)(implicit selector: Selector.Aux[L, o.type, o.Inner]): o.Inner = selector(hl)

def put[O <: Outer](o: O)(i: o.Inner): Derp[FieldType[o.type, o.Inner] :: L] = copy(hl = field[o.type](i))

这是&#34;标记&#34;每个Inner值都带有Outer类型,因此您可以稍后检索它(这是Selector.Aux所做的)。 Shapeless中所有有趣的东西都是通过它附带的类型类(或者你自己定义的)来实现的,并且它们依赖于类型信息来工作。因此,您可以在操作中保留的类型信息越多,就越容易。

在这种情况下,您永远不会返回Option,因为如果您尝试访问地图中不存在的值,则无法进行编译。这通常是您使用HList的原因,我不确定它是否符合您的使用案例。

Shapeless也有HMap,它使用像普通Map这样的键值映射。不同之处在于每种键类型都可以映射到不同的值类型。这似乎更符合您的使用案例,但它的组织方式略有不同。要使用HMap,请将 relation 定义为类型函数。类型函数是具有依赖类型的类型类:

trait MyRelation[Key] {
  type Value
}

object MyRelation {
  type Aux[K, V] = MyRelation[K] { type Value = V }

  implicit val stringToInt: Aux[String, Int] = new MyRelation[String] { type Value = Int }
  implicit val intToBool: Aux[Int, Boolean] = new MyRelation[Int] { type Value = Boolean }

}

现在您可以定义HMap超过MyRelation,因此当您使用String个键时,您将添加/检索Int值,并在您使用{时{1}}您要添加/检索Int值的密钥:

Boolean

这与您的示例略有不同,因为您具有依赖类型的,并且您希望使用外部类型作为键,并将内部类型作为值。通过使用val myMap = HMap[MyRelation.Aux]("Ten" -> 10, 50 -> true) val myMap2 = myMap + ("Fifty" -> 50) myMap2.get("Ten") // Some(10), statically known as Option[Int] myMap2.get(44) // None, statically known as Option[Boolean] 作为关系,也可以将此表达为HMap的关系。但是通常不会工作会让你感到惊讶,因为依赖于路径的类型很棘手并且真正依赖于外部的具体子类型(这将需要大量样板)或单例类型(这将很难在不丢失必要的情况下传递类型信息)。