我很确定我在这里遗漏了一些东西,因为我对无形体很陌生,而且我正在学习,但是辅助技术实际上是什么时候需要的呢?我看到它被用来公开一个type语句,方法是将它提升到另一个“同伴”type定义的签名中。
trait F[A] { type R; def value: R }
object F { type Aux[A,RR] = F[A] { type R = RR } }但是,这不等于把R放在F的类型签名中吗?
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我认为路径依赖类型如果可以在参数列表中使用,就会带来好处,但我们知道我们不能这样做。
def g[T](t:T)(implicit f: F[T], r: Blah[f.R]) ...因此,我们仍然被迫在g的签名中添加一个额外的类型参数。通过使用Aux技术,我们还需要花费额外的时间来编写配套的object。从使用的角度来看,对于像我这样天真的用户来说,使用路径依赖类型根本没有好处。
我只能想到一种情况,即对于给定的类型级别计算,返回多个类型级别的结果,您可能只想使用其中的一个。
我想这一切归结为我忽略了我简单的例子中的一些东西。
发布于 2015-12-31 15:37:20
这里有两个单独的问题:
Aux类型别名?我将从第二个问题开始,因为答案更直接:Aux类型别名完全是一种语法上的方便。你从来不用用它们。例如,假设我们想要编写一个方法,该方法只有在使用两个长度相同的hlist调用时才会编译:
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类型类有一个类型参数(对于HList类型)和一个类型成员(对于Nat)。Length.Aux语法使在隐式参数列表中引用Nat类型成员相对容易,但这只是一种方便--以下内容完全等价:
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版本与以这种方式写出类型改进相比有几个优点:它不太嘈杂,并且不需要我们记住类型成员的名称。不过,这些都是纯粹的人类工程学问题-- Aux别名使我们的代码更容易读和写,但它们不会以任何有意义的方式改变我们可以或不能用代码做的事情。
第一个问题的答案要复杂一些。在许多情况下,包括我的sameLength,Out作为类型成员而不是类型参数没有好处。由于Scala doesn't allow multiple implicit parameter sections,如果要验证两个Length实例是否具有相同的Out类型,则需要N作为方法的类型参数。在这一点上,Out on Length可能是一个类型参数(至少从我们的角度来看是sameLength的作者)。
但是,在其他情况下,我们可以利用这样一个事实,即有时使用类型成员而不是类型参数(稍后我将具体讨论这个问题)。例如,假设我们要编写一个方法,该方法将返回一个函数,该函数将指定的case类类型转换为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是类型参数而不是类型成员,那么我们必须编写类似的内容:
// Doesn't compile
def converter[A, R](implicit gen: Generic[A, R]): A => R = a => gen.to(a)Scala不支持类型参数的部分应用,所以每次我们调用这个(假设的)方法时,我们都必须指定这两个类型参数,因为我们想要指定A
val fooToHList = converter[Foo, Int :: String :: HNil]这使得它基本上一文不值,因为关键在于让通用机器计算出表示形式。
通常,每当一个类型被一个类型类的其他参数唯一地确定时,它就会成为一个类型成员而不是一个类型参数。每个case类都有一个泛型表示,因此Generic有一个类型参数(对于case类类型)和一个类型成员(对于表示类型);每个HList只有一个长度,因此Length有一个类型参数和一个类型成员,等等。
创建唯一确定的类型成员而不是类型参数意味着,如果我们只想使用它们作为路径依赖类型(就像上面的第一个converter中的那样),我们可以使用它们,但是如果我们想把它们当作类型参数来使用,我们总是可以写出类型细化(或者语法上更好的Aux版本)。如果“无形”从一开始就设置了这些类型的参数,那么就不可能朝着相反的方向发展。
顺便提一句,类型类的类型“参数”(我使用引号,因为它们可能不是文字Scala意义上的参数)之间的这种关系在像Haskell这样的语言中被称为"functional dependency",但是您不应该觉得需要了解Haskell中关于函数依赖关系的任何东西才能得到没有形状的东西。
https://stackoverflow.com/questions/34544660
复制相似问题