为什么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级别的结果,并且您可能只想使用其中的一个。
我想这一切都归结为我忽略了一些简单的例子。
这里有两个单独的问题:
- 为什么Shapeless在某些types的类中使用types成员而不是types参数?
- 为什么Shapeless在这些types的伴随对象中包含
Aux
types别名?
我将从第二个问题开始,因为答案更直接: Aux
types别名完全是句法上的便利。 你永远不必使用它们。 例如,假设我们想编写一个只有在两个具有相同长度的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参数(对于HList
types)和一个types成员(对于Nat
)。 Length.Aux
语法使得在隐式参数列表中引用Nat
types成员变得相对容易,但这只是一个方便,以下内容完全相同:
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
别名使我们的代码更容易阅读和写入,但是它们不会以任何有意义的方式改变我们可以或不可以对代码做什么。
第一个问题的答案稍微复杂一点。 在很多情况下,包括我的sameLength
, Out
是一个types成员而不是types参数。 因为Scala 不允许多个隐式参数部分 ,所以如果我们想validation两个Length
实例具有相同的Out
types,我们需要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
。 如果Generic
的Repr
是一个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中正在发生的事情。
- 在PostgreSQL中使用EXCLUDE防止相邻/重叠条目
- 如何将C#匿名types序列化为JSONstring?
- 如何解决“传递给my_function()的参数1必须是string的一个实例,在PHP 7之前给出的string?
- 如何将一个gi-正常的整数(以string格式)转换为hex格式? (C#)
- JAXB:如何在XSD中指定attrtypes时更改XJC生成的类名称?
- super()引发新类风格的“TypeError:必须是types,而不是classobj”
- 在C中是无效的数据types?
- SQL Server在计算平均值时给出算术溢出
- HTMLinput=“文件”接受属性文件types(CSV)