什么时候需要 Shapeless 中的依赖类型? [英] When are dependent types needed in Shapeless?

查看:41
本文介绍了什么时候需要 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 as Sum[A, B] { type Out = C } or Sum.Aux[A, B, C]. I'm asking why do I need type Out at all rather than just Sum[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 specify Out?

让我们考虑以下示例.这是一个用于自然数相加的类型类:

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.TObject2.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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆