什么时候需要 Shapeless 中的依赖类型? [英] When are dependent types needed in Shapeless?
问题描述
据我所知,依赖类型允许您不指定输出类型:
As I understand dependent types allow you to leave output types unspecified:
例如,如果您有一个类型类:
E.g., if you have a type class:
trait Last[In] {
type Out
}
然后你可以在不指定输出类型的情况下召唤一个实例:
then you could summon an instance while leaving the output type unspecified:
implicitly(Last[String :: Int :: HNil]) // output type calculated as Int
并且 Aux 模式允许您再次指定输出类型:
And the Aux pattern allows you to specify the output type again:
implicitly(Last.Aux[String :: Int :: HNil, Int])
您需要在隐式参数列表中使用输出类型(解决 Scala 对依赖类型的限制).
which you need in an implicit parameter list in order to do something useful with the output type (to work around a Scala limitation on dependent types).
但如果你总是需要指定(或分配一个类型参数)输出类型,为什么首先要使用依赖类型(然后是 Aux)?
But if you always need to specify (or assign a type param to) the output type, why bother using dependent types (and then Aux) in the first place?
我尝试从 Shapeless 的 src 复制 Last
类型类,将 type Out
替换为特征中的附加类型参数并删除 Aux.它仍然有效.
I tried copying the Last
type class from Shapeless' src, replacing type Out
by an additional type param in the trait and removing Aux. It still works.
当我真正需要它们时是什么情况?
What is the situation when I actually need them?
推荐答案
我知道
Sum[A, B]
与Sum[A, B] { type Out = C }
或Sum.Aux[A, B, C]
.我在问为什么我需要输入Out
而不是只是Sum[A, B, C]
.
I get that
Sum[A, B]
is not the same asSum[A, B] { type Out = C }
orSum.Aux[A, B, C]
. I'm asking why do I need typeOut
at all rather than justSum[A, B, C]
.
区别在于部分应用.对于 trait MyTrait { type A;B型;type C }
您可以指定某些类型而不指定其他类型(期望编译器推断它们).但是对于 trait MyTrait[A, B, C]
,你只能指定所有这些或不指定任何一个.对于 Sum[A, B] { type Out }
,您更愿意指定 A
, B
而不是指定 Out
>(期望编译器根据作用域中存在的隐式推断其值).同样,对于 trait Last[In] { type Out }
,您更愿意指定 In
而不是指定 Out
(期望编译器推断其值).所以类型参数更像是输入,类型成员更像是输出.
The difference is in partial application. For trait MyTrait { type A; type B; type C }
you can specify some of types and not specify others (expecting that compiler infers them). But for trait MyTrait[A, B, C]
you can only either specify all of them or not specify any of them.
For Sum[A, B] { type Out }
you would prefer to specify A
, B
and not specify Out
(expecting that compiler infers its value based on implicits existing in scope). Similarly for trait Last[In] { type Out }
you would prefer to specify In
and not specify Out
(expecting that compiler infers its value).
So type parameters are more like inputs and type members are more like outputs.
https://www.youtube.com/watch?v=R8GksuRw3VI
抽象类型与类型参数和相关问题
但是在什么时候,我更愿意指定 In
而不是指定 Out
?
But when exactly, would I prefer to specify
In
and not specifyOut
?
让我们考虑以下示例.这是一个用于自然数相加的类型类:
Let's consider the following example. It's a type class for addition of natural numbers:
sealed trait Nat
case object Zero extends Nat
type Zero = Zero.type
case class Succ[N <: Nat](n: N) extends Nat
type One = Succ[Zero]
type Two = Succ[One]
type Three = Succ[Two]
type Four = Succ[Three]
type Five = Succ[Four]
val one: One = Succ(Zero)
val two: Two = Succ(one)
val three: Three = Succ(two)
val four: Four = Succ(three)
val five: Five = Succ(four)
trait Add[N <: Nat, M <: Nat] {
type Out <: Nat
def apply(n: N, m: M): Out
}
object Add {
type Aux[N <: Nat, M <: Nat, Out0 <: Nat] = Add[N, M] { type Out = Out0 }
def instance[N <: Nat, M <: Nat, Out0 <: Nat](f: (N, M) => Out0): Aux[N, M, Out0] = new Add[N, M] {
override type Out = Out0
override def apply(n: N, m: M): Out = f(n, m)
}
implicit def zeroAdd[M <: Nat]: Aux[Zero, M, M] = instance((_, m) => m)
implicit def succAdd[N <: Nat, M <: Nat, N_addM <: Nat](implicit add: Aux[N, M, N_addM]): Aux[Succ[N], M, Succ[N_addM]] =
instance((succN, m) => Succ(add(succN.n, m)))
}
这个类型类在类型级别上都有效
This type class works both on type level
implicitly[Add.Aux[Two, Three, Five]]
和价值水平
println(implicitly[Add[Two, Three]].apply(two, three))//Succ(Succ(Succ(Succ(Succ(Zero)))))
assert(implicitly[Add[Two, Three]].apply(two, three) == five)//ok
现在让我们用类型参数而不是类型成员重写它:
Now let's rewrite it with type parameter instead of type member:
trait Add[N <: Nat, M <: Nat, Out <: Nat] {
def apply(n: N, m: M): Out
}
object Add {
implicit def zeroAdd[M <: Nat]: Add[Zero, M, M] = (_, m) => m
implicit def succAdd[N <: Nat, M <: Nat, N_addM <: Nat](implicit add: Add[N, M, N_addM]): Add[Succ[N], M, Succ[N_addM]] =
(succN, m) => Succ(add(succN.n, m))
}
在类型级别上它的工作方式类似
On type level it works similarly
implicitly[Add[Two, Three, Five]]
但在值级别,现在您必须指定类型 Five
而在前一种情况下,它是由编译器推断的.
But on value level now you have to specify type Five
while in the former case it was inferred by compiler.
println(implicitly[Add[Two, Three, Five]].apply(two, three))//Succ(Succ(Succ(Succ(Succ(Zero)))))
assert(implicitly[Add[Two, Three, Five]].apply(two, three) == five)//ok
所以区别在于部分应用.
So the difference is in partial application.
但是如果你像往常一样添加一个 +
语法糖实用(shapeless 也适用于所有情况),依赖类型好像没有关系
But if you add a
+
syntax sugar as you normally would to make it practical (shapeless also does it for everything), the dependent type doesn't seem to matter
语法并不总是有帮助.例如,让我们考虑一个类型类,它接受一个类型(但不是这个类型的值)并产生一个这个类型的类型和值:
Syntax helps not always. For example let's consider a type class that accepts a type (but not value of this type) and produces a type and value of this type:
trait MyTrait {
type T
}
object Object1 extends MyTrait
object Object2 extends MyTrait
trait TypeClass[In] {
type Out
def apply(): Out
}
object TypeClass {
type Aux[In, Out0] = TypeClass[In] { type Out = Out0 }
def instance[In, Out0](x: Out0): Aux[In, Out0] = new TypeClass[In] {
override type Out = Out0
override def apply(): Out = x
}
def apply[In](implicit tc: TypeClass[In]): Aux[In, tc.Out] = tc
implicit val makeInstance1: Aux[Object1.T, Int] = instance(1)
implicit val makeInstance2: Aux[Object2.T, String] = instance("a")
}
println(TypeClass[Object1.T].apply())//1
println(TypeClass[Object2.T].apply())//a
但是如果我们将 Out
设为类型参数,那么在调用时我们必须指定 Out
并且无法定义扩展方法并推断类型参数 In
来自元素类型,因为没有 Object1.T
、Object2.T
.
but if we make Out
a type parameter then upon call we'll have to specify Out
and there's no way to define extension method and infer type parameter In
from element type since there are no elements of the types Object1.T
, Object2.T
.
这篇关于什么时候需要 Shapeless 中的依赖类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!