意想不到的类型与简单的老分类

我的一位朋友上周提出了一个看似无害的Scala语言问题,我没有很好的答案:是否有一种简单的方法来声明属于某种常见类型类的东西的集合。 当然,在Scala中没有“类型类”的一流概念,所以我们必须根据特征和上下文边界(即含义)来考虑这一点。

具体来说,给定一些表示类型类型的特征T[_] ,以及范围为T[A]T[B]T[C]相应含义的类型ABC ,我们想要声明类似List[T[a] forAll { type a }] ,我们可以将ABC实例逍遥法外。 这在斯卡拉当然不存在; 去年的一个问题更深入地讨论了这个问题。

自然的后续问题是“Haskell如何做?” 那么,特别是GHC有一个类型系统扩展,称为impressedic polymorphism,描述在“Boxy Types”论文中。 简而言之,给定一个类型类型T可以合法地构建一个列表[forall a. T a => a] [forall a. T a => a] 。 给定此表单的声明,编译器会执行一些字典传递的魔术,让我们在运行时保留与列表中每个值类型相对应的类型类实例。

事情是,“字典传递魔法”听起来很像“vtables”。 在像Scala这样的面向对象语言中,子类型比“Boxy类型”方法更简单自然。 如果我们的ABC都扩展特性T ,那么我们可以简单地声明List[T]并且很高兴。 同样,正如Miles在下面的评论中指出的那样,如果它们都扩展了特征T1T2T3那么我可以使用List[T1 with T2 with T3]作为等价于Haskell的implusic [forall a. (T1 a, T2 a, T3 a) => a] [forall a. (T1 a, T2 a, T3 a) => a]

然而,与类型类相比,子类型的主要着名缺点是紧密耦合:我的ABC类型必须将它们的T行为引入其中。让我们假设这是一个主要的破坏者,并且我不能使用子类型。 因此,斯卡拉的中间地带是隐式转换:给定一些A => TB => TC => T隐式范围,我可以再次愉快地填充List[T]与我的ABC值...

...直到我们想要List[T1 with T2 with T3] 。 此时,即使我们有隐式转换A => T1A => T2A => T3 ,我们也不能将A放入列表中。 我们可以重构隐式转换,从字面上为A => T1 with T2 with T3提供A => T1 with T2 with T3 ,但是我从来没有见过任何人这样做过,而且看起来又是另一种紧密耦合形式。

好吧,所以我的问题最终是,我想,这是前面提到的几个问题的组合:“为什么要避免分类?” 和“类型分类的优势”......是否有一个统一的理论说implicit多态性和亚型多态性是一样的? 不知何故,隐含的转换是两个人的秘密爱情? 有人可以阐明一个好的,干净的模式来表达多个边界(如上面的最后一个例子)在斯卡拉?


你把令人费解的类型与存在类型混淆起来。 Impandicative类型允许您将多态值放入数据结构中,而不是任意具体的值。 换句话说[forall a. Num a => a] [forall a. Num a => a]意味着你有一个列表,其中每个元素都可以像任何数字类型一样工作,所以你不能把例如IntDouble放在类型列表中[forall a. Num a => a] [forall a. Num a => a] ,但是你可以在其中放入类似0 :: Num a => a的东西。 令人印象深刻的类型不是你想要的。

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

data SomeNumber = forall a. Num a => SomeNumber a

请注意从existsforall的更改。 这是因为我们正在描述构造函数。 我们可以放入任何数字类型,但是类型系统“忘记”它是哪种类型。 一旦我们将其退出(通过模式匹配),我们所知道的仅仅是某种数字类型。 底下发生了什么, SomeNumber类型包含一个存储类型类字典(又名.vtable / implicit)的隐藏字段,这就是为什么我们需要包装类型。

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

存在类型和类型类的组合在某种程度上类似于子类型,因为类类和接口之间的主要区别在于类型类与vtable分别从对象传播,而存在类型将对象和vtable重新打包在一起。

然而,与传统的子类型不同,你不需要将它们一对一地配对,所以我们可以写出类似这样的东西,它将一个vtable打包成两个相同类型的值。

data TwoNumbers = forall a. Num a => TwoNumbers a a

f :: TwoNumbers -> TwoNumbers
f (TwoNumbers x y) = TwoNumbers (x+y) (x*y)

list1 = map f [TwoNumbers (42 :: Int) 7, TwoNumbers (3.14 :: Double) 9]
-- ==> [TwoNumbers (49 :: Int) 294, TwoNumbers (12.14 :: Double) 28.26]

甚至更有趣的东西。 一旦我们在包装上进行模式匹配,我们又回到了类型类的领域。 虽然我们不知道哪种类型的xy ,但我们知道它们是相同的,并且我们有正确的字典可用于对它们执行数字操作。

以上所有内容与多种类型类似。 编译器将简单地为每个vtable的包装类型生成隐藏字段,并在模式匹配时将它们全部纳入范围。

data SomeBoundedNumber = forall a. (Bounded a, Num a) => SBN a

g :: SomeBoundedNumber -> SomeBoundedNumber
g (SBN n) = SBN (maxBound - n)

list2 = map g [SBN (42 :: Int32), SBN (42 :: Int64)]
-- ==> [SBN (2147483605 :: Int32), SBN (9223372036854775765 :: Int64)]

因为我对于Scala来说是一个初学者,我不确定我能帮你解决问题的最后部分,但我希望这至少可以消除一些混乱,并给你一些关于如何解决问题的想法。继续。


@哈马尔的回答是完全正确的。 这是doint的scala方式。 例如,我将Show作为类型类,并将值id在列表中:

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

建立列表很简单,但列表不会“记住”每个元素的确切类型。 相反,它会将每个元素上传到一个通用的超类型TStringInt之间的更精确的超级超类型是Any ,列表的类型是List[Any]

问题是:忘记什么和记住什么? 我们想要忘记元素的确切类型,但是我们想要记住它们都是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)

它用相应的类型实例打包一个值。

最后,我们可以为Ex[Show]添加一个实例

implicit val exShow = new Show[Ex[Show]] {
  def show(e : Ex[Show]) : String = {
    import e._
    e.value.show 
  }
}

import e._需要将实例引入范围。 感谢implicits的魔力:

val list = List[Ex[Show]](i , s)
for (e <- list) yield e.show

这与预期的代码非常接近。

链接地址: http://www.djcxy.com/p/43511.html

上一篇: Impredicative types vs. plain old subtyping

下一篇: Why can I not make String an instance of a typeclass?