在 Shapeless 中实现路径相关的地图类型
Implementing Path dependent Map types in Shapeless
我想整理一张地图,其中包含从外部到内部的路径相关类型映射:
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,我可以避免使用类型选择器并确保程序只能获取由 Outer
.[=14= 创建的 Inner
的实例]
这可能吗,甚至是个好主意?大多数 HList 问题似乎都是关于 arity 和 case 类,我觉得我在跳出框框。
请注意,我知道 但这个问题特别是关于 Shapeless HList 实现的——我不知道如何从 HList 中删除元素,并且可能 return 选项[outer.Inner].
首先,您几乎肯定需要参数化 Derp
:
case class Derp[L <: HList](hl: L)
这是因为只要你有一个 Derp
,编译器就需要知道它的静态 HList
类型是什么,以便做任何有用的事情。
HList
types 对列表中每个类型的信息进行编码——比如
type Foo = Int :: String :: Boolean :: HNil
一旦您说 hl: HList
,该信息就会丢失。
接下来,您需要正确指定操作的 return 类型:
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))
这是 "tagging" 每个具有 Outer
类型的 Inner
值,因此您可以稍后检索它(这就是 Selector.Aux
的作用)。 Shapeless 中所有有趣的东西都是通过它附带的类型类(或您自己定义的)发生的,并且它们依赖于类型信息来工作。因此,您可以在操作中保留的类型信息越多,操作起来就越容易。
在这种情况下,您永远不会 return 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 }
}
现在您可以在 MyRelation
上定义 HMap
,因此当您使用 String
键时,您将 add/retrieve Int
值,并且当您使用 Int
键你会 add/retrieve 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]
这与您的示例有点不同,因为您有一个具有依赖类型的 value,并且您希望使用外部类型作为键,而内部类型类型作为值。通过使用 K#Inner =:= V
作为关系,也可以将其表示为 HMap
的关系。但它通常会因不起作用而让您感到惊讶,因为依赖于路径的类型很棘手并且实际上取决于外部的具体子类型(这将需要大量样板文件)或单例类型(这将很难在不丢失必要的情况下传递)类型信息)。
我想整理一张地图,其中包含从外部到内部的路径相关类型映射:
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,我可以避免使用类型选择器并确保程序只能获取由 Outer
.[=14= 创建的 Inner
的实例]
这可能吗,甚至是个好主意?大多数 HList 问题似乎都是关于 arity 和 case 类,我觉得我在跳出框框。
请注意,我知道 但这个问题特别是关于 Shapeless HList 实现的——我不知道如何从 HList 中删除元素,并且可能 return 选项[outer.Inner].
首先,您几乎肯定需要参数化 Derp
:
case class Derp[L <: HList](hl: L)
这是因为只要你有一个 Derp
,编译器就需要知道它的静态 HList
类型是什么,以便做任何有用的事情。
HList
types 对列表中每个类型的信息进行编码——比如
type Foo = Int :: String :: Boolean :: HNil
一旦您说 hl: HList
,该信息就会丢失。
接下来,您需要正确指定操作的 return 类型:
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))
这是 "tagging" 每个具有 Outer
类型的 Inner
值,因此您可以稍后检索它(这就是 Selector.Aux
的作用)。 Shapeless 中所有有趣的东西都是通过它附带的类型类(或您自己定义的)发生的,并且它们依赖于类型信息来工作。因此,您可以在操作中保留的类型信息越多,操作起来就越容易。
在这种情况下,您永远不会 return 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 }
}
现在您可以在 MyRelation
上定义 HMap
,因此当您使用 String
键时,您将 add/retrieve Int
值,并且当您使用 Int
键你会 add/retrieve 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]
这与您的示例有点不同,因为您有一个具有依赖类型的 value,并且您希望使用外部类型作为键,而内部类型类型作为值。通过使用 K#Inner =:= V
作为关系,也可以将其表示为 HMap
的关系。但它通常会因不起作用而让您感到惊讶,因为依赖于路径的类型很棘手并且实际上取决于外部的具体子类型(这将需要大量样板文件)或单例类型(这将很难在不丢失必要的情况下传递)类型信息)。