Scala 非指示类型与普通旧子类型

Scala 非指示类型与普通旧子类型,scala,haskell,functional-programming,subtype,impredicativetypes,Scala,Haskell,Functional Programming,Subtype,Impredicativetypes,上周,我的一个朋友提出了一个看似无害的Scala语言问题,我没有一个好的答案:是否有一种简单的方法来声明属于某个常见类型类的事物集合。当然,Scala中没有一流的“类型类”概念,因此我们必须从特征和上下文边界(即隐式)的角度来考虑这一点 具体地说,给定一些表示类型类的特征T[\u],以及类型a,B和C,在范围T[a],T[B]和T[C]中有相应的含义,我们想要为所有{type a}声明类似于列表[T[a]的东西,我们可以在其中抛出A、B和C的实例而不受惩罚。这在Scala中当然不存在;a对此进行

上周,我的一个朋友提出了一个看似无害的Scala语言问题,我没有一个好的答案:是否有一种简单的方法来声明属于某个常见类型类的事物集合。当然,Scala中没有一流的“类型类”概念,因此我们必须从特征和上下文边界(即隐式)的角度来考虑这一点

具体地说,给定一些表示类型类的特征
T[\u]
,以及类型
a
B
C
,在范围
T[a]
T[B]
T[C]
中有相应的含义,我们想要为所有{type a}声明类似于
列表[T[a]的东西,我们可以在其中抛出
A
B
C
的实例而不受惩罚。这在Scala中当然不存在;a对此进行了更深入的讨论

自然的后续问题是“Haskell是如何做到的?”好吧,GHC特别有一个类型系统扩展,称为,如本文所述。简言之,给定一个typeclass
T
就可以合法地构造一个列表
[forall a.ta=>a]
。给定这种形式的声明,编译器执行一些字典传递魔术,让我们在运行时保留与列表中每个值的类型对应的typeclass实例

事实是,“字典传递魔法”听起来很像“vtables”。在Scala这样的面向对象语言中,子类型是一种比“Boxy类型”方法更简单、更自然的机制。如果我们的
A
B
C
都扩展了trait
T
,那么我们可以简单地声明
List[T]
,并感到高兴。同样,正如迈尔斯在下面的评论中指出的那样,如果它们都扩展了traits
T1
T2
T3
,那么我可以使用
List[T1带T2带T3]
作为非指示性Haskell
[对于所有的a.(T1 a,t2a,t3a)=>a]

然而,与类型类相比,子类型的主要、众所周知的缺点是紧密耦合:my
A
B
C
类型必须有其
T
行为。让我们假设这是一个主要的交易破坏者,我不能使用子类型。因此,Scala的中间地带是皮条客^H^H^H^H^Himplicit转换:在隐式范围内,给定一些
A=>T
B=>T
C=>T
,我可以再次非常愉快地用我的
A
B
C
值填充
列表[T]/code>

。。。直到我们想要
列表[T1与T2与T3]
。此时,即使我们有隐式转换
A=>T1
A=>T2
A=>T3
,我们也不能将
A
放入列表中。我们可以重新构造隐式转换,从字面上提供
A=>T1和T2以及T3
,但我以前从未见过有人这样做,这似乎是紧耦合的另一种形式


好的,所以我的问题最后是,我想,以前在这里问过的几个问题的组合:和。。。有没有统一的理论认为非指示多态性和亚型多态性是一回事?隐晦的转变不知何故是这两个人的暗恋之子?有人能在Scala中清晰地表达多个边界(如上面的最后一个例子)吗?

你把非指示类型和存在类型混淆了。非指示性类型允许您在数据结构中放置多态值,而不是任意的具体值。换句话说,
[forall a.Num a=>a]
意味着您有一个列表,其中每个元素都作为任何数字类型工作,因此您不能将例如
Int
Double
放在类型的列表中
[forall a.Num a=>a]
,但您可以在其中放置类似
0::Num a=>a
的内容。非指示性类型不是您想要的类型

您需要的是存在类型,即
[exists a.Num a=>a]
(不是真正的Haskell语法),它表示每个元素都是未知的数字类型。但是,要在Haskell中编写此代码,我们需要引入包装器数据类型:

data SomeNumber=forall a。Num a=>SomeNumber a
请注意,对于所有
,从
存在到
的更改。那是因为我们在描述构造函数。我们可以输入任何数字类型,但是类型系统会“忘记”它是哪种类型。一旦我们把它取出来(通过模式匹配),我们只知道它是某种数字类型。在幕后发生的事情是,
SomeNumber
类型包含一个隐藏字段,该字段存储类型类字典(aka.vtable/implicit),这就是我们需要包装器类型的原因

现在我们可以将类型
[SomeNumber]
用于任意数字的列表,但我们需要在输入的过程中包装每个数字,例如
[SomeNumber(3.14::Double),SomeNumber(42::Int)]
。每种类型的正确字典都会被查找,并自动存储在隐藏字段中的每个数字的换行位置

存在类型和类型类的组合在某些方面类似于子类型,因为类型类和接口之间的主要区别在于,对于类型类,vtable与对象分开移动,而存在类型将对象和vtable重新打包在一起

但是,与传统的子类型不同,您不必将它们一一配对,因此我们可以编写这样的内容,将一个vtable打包为两个相同类型的值

data twoNumber=forall a。Num a=>两个数字a
f::TwoNumber->TwoNumber
f(两个数x y)=两个数(x+y)(x*y)
list1=mapf[twoonumbers(42::Int)7,twoonumbers(3.14::Double)9]
--==>[TwoNumber(49::Int)294,TwoNumber(12.14::Double)28.26]
甚至是更奇特的东西。一旦我们在包装上进行了模式匹配,我们就回到了
// The type class
trait Show[A] {
   def show(a : A) : String
}

// Syntactic sugar for Show
implicit final class ShowOps[A](val self : A)(implicit A : Show[A]) {
  def show = A.show(self)
}

implicit val intShow    = new Show[Int] {
  def show(i : Int) = "Show of int " + i.toString
}

implicit val stringShow = new Show[String] {
  def show(s : String) = "Show of String " + s
}


val i : Int    = 5
val s : String = "abc"
val list = List(i, s)
for (e <- list) yield e.show
abstract class Ex[TC[_]] {
  type t
  val  value : t
  implicit val instance : TC[t]
}

implicit def ex[TC[_], A](a : A)(implicit A : TC[A]) = new Ex[TC] {
  type t = A
  val  value    = a
  val  instance = A
}
val ex_i : Ex[Show] = ex[Show, Int](i)
val ex_s : Ex[Show] = ex[Show, String](s)
implicit val exShow = new Show[Ex[Show]] {
  def show(e : Ex[Show]) : String = {
    import e._
    e.value.show 
  }
}
val list = List[Ex[Show]](i , s)
for (e <- list) yield e.show