为什么types级计算需要Aux技术?

我很确定我在这里错过了一些东西,因为我对Shapeless很陌生,而且我正在学习,但是何时需要 Aux技术? 我发现通过将它提升到另一个“伴侣” type定义的签名中,它被用来暴露一个type语句。

 trait F[A] { type R; def value: R } object F { type Aux[A,RR] = F[A] { type R = RR } } 

但是这不就是把R放在F的types签名中吗?

 trait F[A,R] { def value: R } implicit def fint = new F[Int,Long] { val value = 1L } implicit def ffloat = new F[Float,Double] { val value = 2.0D } def f[T,R](t:T)(implicit f: F[T,R]): R = f.value f(100) // res4: Long = 1L f(100.0f) // res5: Double = 2.0 

我发现如果可以在参数列表中使用它们,path依赖types会带来好处,但是我们知道我们不能这样做

 def g[T](t:T)(implicit f: F[T], r: Blah[fR]) ... 

因此,我们仍然不得不在g的签名中增加一个types参数。 通过使用Aux技术,我们需要额外的时间来编写伴随object 。 从使用的angular度来看,像我这样一个天真的用户,根本不使用path依赖types。

我只能想到一种情况,就是对于给定的types级计算,返回多个types级别的结果,并且您可能只想使用其中的一个。

我想这一切都归结为我忽略了一些简单的例子。

这里有两个单独的问题:

  1. 为什么Shapeless在某些types的类中使用types成员而不是types参数?
  2. 为什么Shapeless在这些types的伴随对象中包含Auxtypes别名?

我将从第二个问题开始,因为答案更直接: Auxtypes别名完全是句法上的便利。 你永远不必使用它们。 例如,假设我们想编写一个只有在两个具有相同长度的hlists调用时才能编译的方法:

 import shapeless._, ops.hlist.Length def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit al: Length.Aux[A, N], bl: Length.Aux[B, N] ) = () 

Length类有一个types参数(对于HListtypes)和一个types成员(对于Nat )。 Length.Aux语法使得在隐式参数列表中引用Nattypes成员变得相对容易,但这只是一个方便,以下内容完全相同:

 def sameLength[A <: HList, B <: HList, N <: Nat](a: A, b: B)(implicit al: Length[A] { type Out = N }, bl: Length[B] { type Out = N } ) = () 

Aux版本比用这种方式写出types优化有两个优点:它不那么嘈杂,并且不需要我们记住types成员的名字。 这些纯粹是人体工程学的问题,虽然Aux别名使我们的代码更容易阅读和写入,但是它们不会以任何有意义的方式改变我们可以或不可以对代码做什么。

第一个问题的答案稍微复杂一点。 在很多情况下,包括我的sameLengthOut是一个types成员而不是types参数。 因为Scala 不允许多个隐式参数部分 ,所以如果我们想validation两个Length实例具有相同的Outtypes,我们需要N作为我们的方法的types参数。 在这一点上, Out on Length也可能是一个types参数(至less从我们作为sameLength的作者的angular度来看)。

然而,在其他情况下,我们可以利用Shapeless有时(我会特别谈论在哪里 )使用types成员而不是types参数的事实。 例如,假设我们要编写一个方法,该方法将返回一个将指定的案例类types转换为HList

 def converter[A](implicit gen: Generic[A]): A => gen.Repr = a => gen.to(a) 

现在我们可以像这样使用它:

 case class Foo(i: Int, s: String) val fooToHList = converter[Foo] 

我们将得到一个不错的Foo => Int :: String :: HNil 。 如果GenericRepr是一个types参数而不是types成员,我们不得不这样写:

 // Doesn't compile def converter[A, R](implicit gen: Generic[A, R]): A => R = a => gen.to(a) 

Scala不支持部分应用types参数,所以每次我们调用这个(假设的)方法时,我们都必须指定两个types参数,因为我们要指定A

 val fooToHList = converter[Foo, Int :: String :: HNil] 

这使得它基本上是毫无价值的,因为整个问题的目的是让通用机器找出代表性。

通常,只要types由types的其他参数唯一确定,Shapeless将使其成为types成员而不是types参数。 每个case类都有一个单一的通用表示,所以Generic有一个types参数(对于casetypes)和一个types成员(对于表示types)。 每个HList有一个单一的长度,所以Length有一个types参数和一个types成员等。

使用唯一确定的typestypes成员而不是types参数意味着如果我们只想将它们用作path依赖types(如上面的第一个converter ),我们可以,但是如果我们想要使用它们就好像它们是types参数,我们总是可以写出types细化(或者语法上更好的Aux版本)。 如果Shapeless从一开始就制作了这些types的参数,就不可能走向相反的方向。

作为一个侧面说明,typestypes的“参数”(我使用引号,因为它们可能不是Scala意义上的参数 )之间的这种关系在像Haskell这样的语言中被称为“函数依赖” ,但是你不应该“你觉得你需要了解Haskell中的函数依赖的任何事情,以获得Shapeless中正在发生的事情。