当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么类型级计算需要Aux技术?》经验,为你挑选了1个好方法。

我很确定我在这里遗漏了一些东西,因为我对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的类型签名中吗?

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.从使用的角度来看,像我这样的天真用户会觉得使用路径依赖类型没有任何好处.

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

我想这一切归结为我在我的简单例子中忽略了一些东西.



1> Travis Brown..:

这里有两个不同的问题:

    为什么在某些类型的类中,Shapeless使用类型成员而不是类型参数?

    为什么Shapeless Aux在这些类型类的伴随对象中包含类型别名?

我将从第二个问题开始,因为答案更直接:Aux类型别名完全是语法上的便利.你永远不必使用它们.例如,假设我们想要编写一个方法,只有在使用两个具有相同长度的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类型的类具有一种类型的参数(对于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别名使我们的代码更易于阅读和编写,但它们并没有以任何有意义的方式改变我们能够或不能完成的代码.

第一个问题的答案有点复杂.在许多情况下,包括my sameLength,Out作为类型成员而不是类型参数没有任何优势.因为Scala 不允许多个隐式参数部分,所以N如果我们要验证两个Length实例具有相同的Out类型,我们需要成为我们方法的类型参数.那时,Outon 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.如果GenericRepr是一个类型参数,而不是一个类型的成员,我们不得不写这样的事情,而不是:

// 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]

这使得它基本上毫无价值,因为重点是让通用机器找出代表性.

通常,只要类型由类型类的其他参数唯一确定,Shapeless就会使其成为类型成员而不是类型参数.每个case类都有一个通用表示,因此Generic有一个类型参数(对于case类型)和一个类型成员(对于表示类型); 每个HList都有一个长度,所以Length有一个类型参数和一个类型成员等.

使用唯一确定的类型类型成员而不是类型参数意味着如果我们只想将它们用作路径依赖类型(如converter上面的第一个),我们可以,但如果我们想要使用它们就像它们是类型参数一样,我们总是可以写出类型细化(或语法更好的Aux版本).如果Shapeless从一开始就使这些类型的参数类型化,那就不可能朝相反的方向发展.

作为旁注,类型类的类型"参数"之间的这种关系(我使用引号,因为它们可能不是文字Scala意义上的参数)在Haskell这样的语言中称为"函数依赖",但你不应该'感觉你需要了解Haskell中关于功能依赖的任何信息才能得到Shapeless中正在发生的事情.

推荐阅读
贴进你的心聆听你的世界
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有