给定方法参数,返回最特定的类型

时间:2019-01-11 17:16:13

标签: scala types shapeless scala-macros

我正在尝试给定一些可变参数来创建特定且唯一的类型。

在下面列出的列表中,您可以找到代表前四个自然数的预定类型(具有类似HList的Digit0..Digit9,DimensionCons和EndDimension(例如HNil))。

  object Defined {
    type D1 = DimensionCons[Digit1, EndDimension.type]
    val d1 = DimensionCons(Digit1(), EndDimension)

    type D2 = DimensionCons[Digit2, EndDimension.type]
    val d2 = DimensionCons(Digit2(), EndDimension)

    type D3 = DimensionCons[Digit3, EndDimension.type]
    val d3 = DimensionCons(Digit3(), EndDimension)

    type D4 = DimensionCons[Digit4, EndDimension.type]
    val d4 = DimensionCons(Digit4(), EndDimension)
  }

我正在寻找一种带有指示签名的方法,例如

  def getDimensionTpe[D <: Dimension](dim: Int) : D

例如返回DimensionCons[Digit2, EndDimension.type]的{​​{1}}。

问题:

  • 没有白盒宏(会生成类型吗?),这可能吗?如果是,如何以及如何照顾呢?
  • 是否有其他可行或适用的技术?
  • 路径依赖类型可以帮助吗?

谢谢马丁

1 个答案:

答案 0 :(得分:7)

我不确定我是否了解您对DimensionConsDigitN业务的确切了解,但是可以使用Shapeless编写将采用整数的方法(尽管仅一个文字常量,必须在编译时就知道),并让该整数确定返回值的静态类型。

为了简化您的代码,以提供完整的工作示例,假设我们要编写类似以下的方法:

trait Dimension
case class Dim1() extends Dimension
case class Dim2() extends Dimension
case class Dim3() extends Dimension
// And so on...

def getDimensionTpe[D <: Dimension](dim: Int) : D

…其中getDimensionTpe(1)将返回Dim1()(静态键入为Dim1),getDimensionTpe(2)将返回Dim2(),依此类推。为此,我们可以引入Shapeless自然数和维之间的类型类映射:

import shapeless.{ DepFn0, Nat }

trait DimMap[N <: Nat] extends DepFn0 {
  type Out <: Dimension
}

object DimMap {
  type Aux[N <: Nat, D <: Dimension] = DimMap[N] { type Out = D }

  implicit val dimMap1: Aux[Nat._1, Dim1] = new DimMap[Nat._1] {
    type Out = Dim1
    def apply(): Dim1 = Dim1()
  }

  implicit val dimMap2: Aux[Nat._2, Dim2] = new DimMap[Nat._2] {
    type Out = Dim2
    def apply(): Dim2 = Dim2()
  }

  implicit val dimMap3: Aux[Nat._3, Dim3] = new DimMap[Nat._3] {
    type Out = Dim3
    def apply(): Dim3 = Dim3()
  }

  // And so on as needed.
}

如果您的尺寸类型具有更多结构,则有可能在此处避免样板,但我不清楚DigitN的工作方式。不过,这似乎与问题的要点正交,即如何定义getDimensionTpe

如果您不介意编写白盒宏,则可以将其定义为具有以下特征的签名:

def getDimensionTpe[D <: Dimension](dim: Int) : D

这是一个巨大的难题,但是Shapeless允许您编写几乎没有自定义宏的内容。给定上面的DimMap类型类,您可以编写以下内容:

def getDimensionTpe(dim: Nat)(implicit m: DimMap[dim.N]) : m.Out = m()

然后:

scala> val d1: Dim1 = getDimensionTpe(1)
d1: Dim1 = Dim1()

scala> val d2: Dim2 = getDimensionTpe(2)
d2: Dim2 = Dim2()

scala> val d3: Dim3 = getDimensionTpe(3)
d3: Dim3 = Dim3()

如果您输入的静态类型错误,编译器将告诉您:

scala> val d3: Dim1 = getDimensionTpe(3)
<console>:15: error: type mismatch;
 found   : DimMap.dimMap3.Out
    (which expands to)  Dim3
 required: Dim1
       val d3: Dim1 = getDimensionTpe(3)
                                     ^

如果您提供不带映射的整数文字,那么这也是编译时错误:

scala> getDimensionTpe(0)
<console>:14: error: could not find implicit value for parameter m: DimMap[shapeless._0]
       getDimensionTpe(0)
                      ^

最后,如果您提供的不是整数文字的Int参数,您还会收到编译器错误:

scala> val x = 1
x: Int = 1

scala> getDimensionTpe(x)
<console>:16: error: Expression x does not evaluate to a non-negative Int literal
       getDimensionTpe(x)
                       ^

在幕后Shapeless正在使用宏来使之成为可能,甚至还没有 令人恐惧-您可以阅读the NatMacros definition以获得详细信息。