【问题标题】:How to use dependent types inside type classes in Scala?如何在 Scala 的类型类中使用依赖类型?
【发布时间】:2023-03-29 12:07:01
【问题描述】:

我想为几个不同的C -s 定义一对类型(A 中的BB)。例如,A=StringB=View 代表 C=View1(在隐式对象 DepInstanceView1 中定义)。然后我想在类型类定义中使用这些类型级别的依赖关系。

如何定义一个类型类实例(此处为 ServerSideFun[View1]),它具有具有依赖输入和返回类型(此处定义为 DepInstanceView1)的函数?

换句话说,我想有一个来自C -> (A,B) 的类型级函数(这里在DepInstanceView1 中定义了一个这样的类型级映射)并在类型类声明中使用这个类型级函数(这里@ 987654336@) 及其实例(此处为View1Conv)。

有人可以建议一种方法吗?

或者如何修改下面的代码来编码这样的类型级别,类型类中f(正如trait ServerSideFun[C]所暗示的那样)的类型依赖约束?

我这样做的尝试以编译器错误告终:

source_file.scala:47: error: type mismatch;
 found   : Int(42)
 required: B
    def f[A, B] (a:A)(implicit aux:Dep.Aux[View1,A,B]):B = 42
                                                           ^
one error found

代码示例:

下面的代码可以在这里在线执行:http://rextester.com/GYWW78561

object Rextester extends App {
    println("Hello, World!3")
 }

trait Dep[C]{
  type A
  type B
}

trait View1
trait View2


object Dep{

  type Aux[C0, A0, B0]=  Dep[C0] {type A=A0; type B= B0}

  implicit object DepInstanceView1  extends  Dep[View1] {
    type A=String
    type B=Int
  } 
}

trait ServerSideFun[C]
{
   def f[A,B] (a:A)(implicit aux:Dep.Aux[C,A,B]):B
}

object ServerSideFun {
  implicit object View1Conv extends ServerSideFun[View1]{
    def f[A, B] (a:A)(implicit aux:Dep.Aux[View1,A,B]):B = 42
  }
}

编辑:

我更新了代码以澄清意图:http://rextester.com/YVBV30174

这是:

1) 定义几个View-s:

  • trait View1
  • trait View2

2) 为每个View-s 定义一个单独的类型级映射View -> (A,B),例如:

  • View1-> (String,Int) in DepInstanceView1
  • View2-> (String,String)DepInstanceView2

3) 可以有多个不同的类型类ServerSideFun 实现,例如:

  • object ServerSideFun中的一个
  • object ServerSideFunAlternativeImplementation 中的另一个。

本质上

  • 类型类ServerSideFun[C]所有 实例应遵循object Dep 中定义的类型级别映射View -> (A,B)
  • 因此,对于给定的View,函数f 的每个定义(实现)都必须采用A 类型的输入参数并返回B 类型的结果。

代码如下:

object Rextester extends App {
    println("Hello, World!3")
 }

trait Dep[C]{
  type A
  type B
}

trait View1
trait View2


object Dep{

  type Aux[C0, A0, B0]=  Dep[C0] {type A=A0; type B= B0}

  implicit object DepInstanceView1  extends  Dep[View1] {
    type A=String
    type B=Int
  } 

  implicit object DepInstanceView2  extends  Dep[View2] {
    type A=String
    type B=String
  } 
}

trait ServerSideFun[C]
{
   def f[A,B] (a:A)(implicit aux:Dep.Aux[C,A,B]):B
}


object ServerSideFun {
  implicit object View1Conv extends ServerSideFun[View1]{
    def f[A, B] (a:A)(implicit aux:Dep.Aux[View1,A,B]):B = 42
  }

  implicit object View2Conv extends ServerSideFun[View2]{
    def f[A, B] (a:A)(implicit aux:Dep.Aux[View2,A,B]):B = "42"
  }
}



object ServerSideFunAlternativeImplementation {
  implicit object View1Conv extends ServerSideFun[View1]{
    def f[A, B] (a:A)(implicit aux:Dep.Aux[View1,A,B]):B = 43
  }

  implicit object View2Conv extends ServerSideFun[View2]{
    def f[A, B] (a:A)(implicit aux:Dep.Aux[View2,A,B]):B = "43"
  }

}

【问题讨论】:

  • ServerSideFun 和类型级函数C -> (A, B) 在您的代码中已经可以使用了。问题是这些类型级函数没有提供任何有用的定义实际值级函数fDep.Aux[View1, A, B] 可以是任何东西。为什么42 应该匹配anything?似乎Dep[C] 应该是定义f(a: A): B 的特征。
  • 编辑的两个问题:1) ServerSideFunAlternativeImplementation 到底实现了什么?它是否应该以某种模糊的方式与ServerSideFun 兼容的源代码? 2) Dep.Aux[View1, A, B] 类型不限制隐式参数为" 类型级别映射View -> (A,B) 定义在对象Dep",因为额外的@ 987654372@ 可以由其他人在任何地方定义。
  • 1) 根据范围内的隐式,将执行 f 的不同实现。 2)嗯,有趣的一点。确实。感谢您指出了这一点。但是,在这种情况下,这将在调用代码中明确显示,类型类的用户需要采取额外的努力(偏离默认范围内的默认实现),所以在某种意义上仍然存在某种“软限制”到位。
  • 1) 我不太明白你所说的“源代码兼容”是什么意思?
  • To 1): "取决于哪个隐含在范围内" -- 你首先会寻找什么隐含的type?我期待像(impliicit ssf: ServerSideFunIntf) 这样的东西,但目前ServerSideFunServerSideFunAlternativeImplementation 除了AnyRef 之外没有共同的祖先。

标签: scala types typeclass dependent-type


【解决方案1】:

如果ServerSideFun[C] 应该包含针对某些类型AB 的函数f: A => B 的实现,那么ServerSideFun[C] 显然必须知道AB 是什么类型,否则它将不得不从来自特征ServerSideFun[C] 之外的一些片段组成函数f。但是我们可以将type Atype B 包含在ServerSideFun[C] 中,然后完全删除Dep[C]

trait View1
trait View2

trait ServerSideFun[C]{
  type A
  type B
  def f(a: A): B
}

object ServerSideFun {
  implicit object View1Conv extends ServerSideFun[View1] {
    type A = String
    type B = Int
    def f(a: String): Int = 42
  }
}

如果需要,可以重新引入Dep[C],然后将Dep[C]扩展为ServerSideFun[C]

trait Dep[C]{
  type A
  type B
}

trait View1
trait View2

object Dep {
  trait DepInstanceView1 extends Dep[View1] {
    type A = String
    type B = Int
  } 
}

trait ServerSideFun[C] extends Dep[C] {
  def f(a: A): B
}

object ServerSideFun {
  implicit object View1Conv 
    extends ServerSideFun[View1] 
    with Dep.DepInstanceView1 {

    def f(a: String): Int = 42
  }
}

如果f 不是完全通用函数的“自然组合”,那么在某些时候你将不得不在AB 已知的上下文中写下f 的主体。在正文中写下具体的42,但同时从外部将完全未指定的AB 带入Dep.Aux[View1,A,B] 是行不通的。

【讨论】:

  • 非常感谢安德烈!我会试着理解你的答案,可能需要几个小时(希望我能在一两天内以有意义的方式回复它)。我也试着澄清一下这个问题的意图,也是为了我自己。这样我就可以清楚地描述问题/问题是什么。
  • 我明白你的回答安德烈!非常感谢!我把你的代码放在这里:rextester.com/MOUQX83317
  • @jhegedus 好的,这激发了乐观情绪 :) 它实际上或多或少地做了你想要的,还是它仍然为一个微妙的不同问题提供了解决方案?
  • 我发现trait DepInstanceView1是你的编码类型级别依赖C->(A,B)的解决方案非常有趣,你的这个想法是从哪里来的?我发现很难学习 Scala 中类型级编程的基础知识。你是如何得出这个想法的?我应该学习/阅读什么,以便我能够自己回答类似的“类型级编程相关”问题?
  • “它实际上或多或少地做了你想要的,还是它仍然为一个微妙的不同问题提供了解决方案?”我认为这是我问题的完美答案。我使用implicits 来编码类型级别依赖的想法来自Scala + Aux 模式中函数依赖/多参数类型类的标准编码。但是,对于这个问题,这是一个非常糟糕的主意。我发现您对问题的解决方案非常出色,尤其是我尽量避免使用继承作为一般经验法则,但显然它非常有用。所以这是一个很好的教训。
猜你喜欢
  • 2011-03-16
  • 1970-01-01
  • 2012-12-29
  • 2022-08-14
  • 2013-04-17
  • 2019-03-12
  • 1970-01-01
  • 2013-06-20
  • 1970-01-01
相关资源
最近更新 更多