意想不到的types与简单的老分类
上个星期我的一个朋友提出了一个看似无害的Scala语言问题,我没有很好的答案:是否有一种简单的方法来声明属于某种常见types类的东西的集合。 当然在Scala中没有“types类”的一stream概念,所以我们必须从特征和上下文边界(即含义)的angular度来思考这个概念。
具体来说,给定表示typestypes的一些特征T[_]
以及范围为T[A]
, T[B]
和T[C]
相应含义的typesA
, B
和C
,我们想要声明类似List[T[a] forAll { type a }]
,我们可以将A
, B
和C
实例逍遥法外。 这在斯卡拉当然不存在; 去年的一个问题更深入地讨论了这个问题 。
自然的后续问题是“Haskell怎么做?” 那么,GHC特别是有一个types系统扩展称为Impandicative多态性 ,在“Boxytypes”论文中描述。 简而言之,给定一个typesT
可以合法地构build一个列表[forall a. T a => a]
[forall a. T a => a]
。 给定这种forms的声明,编译器会做一些字典传递的魔法,让我们在运行时保留对应于列表中每个值types的types实例。
事情是,“字典传递魔法”听起来很像“vtables”。 在像Scala这样的面向对象语言中,子types比“Boxytypes”方法更简单,更自然。 如果我们的A
, B
和C
都扩展特性T
,那么我们可以简单地声明List[T]
并且感到高兴。 同样,正如Miles在下面的评论中所指出的那样,如果它们都扩展了T1
, T2
和T3
特征,那么我可以使用List[T1 with T2 with T3]
作为Haskell的等价物[forall a. (T1 a, T2 a, T3 a) => a]
[forall a. (T1 a, T2 a, T3 a) => a]
。
然而,与types类相比,子types的主要缺点是紧密耦合:我的A
, B
和C
types必须有T
行为。让我们假设这是一个重大的破产者,我不能使用子types。 所以Scala的中间层次是隐式转换:给定一些A => T
, B => T
和C => T
的隐式范围,我可以再次愉快地填充一个List[T]
与我的A
, B
和C
值…
…直到我们想要List[T1 with T2 with T3]
。 在这一点上,即使我们有隐式转换A => T1
, A => T2
和A => T3
,我们也不能把A
放到列表中。 我们可以重构我们的隐式转换,从字面上提供A => T1 with T2 with T3
,但我从来没有见过任何人这样做,似乎又一种forms的紧密耦合。
好的,所以我的问题最后是,我想这是以前在这里问过的几个问题的组合: “为什么要避免分类? 和“types分类的优点” …是否有一个统一的理论,说implicit多态性和亚型多态性是一个一样的? 不知何故,隐含的转变是两个秘密的爱情? 有人可以清楚expression一个好的,干净的模式来expression多个边界(如上面的最后一个例子)在斯卡拉?
你把令人费解的types与存在types混淆起来。 意想不到的types允许将多态值放在数据结构中,而不是任意具体的值。 换句话说[forall a. Num a => a]
[forall a. Num a => a]
意味着你有一个列表,其中每个元素都可以作为任何数字types,所以你不能把例如Int
和Double
放在types列表中[forall a. Num a => a]
[forall a. Num a => a]
,但是你可以在其中放入类似0 :: Num a => a
的东西。 意想不到的types不是你想要的。
你想要的是存在types,即[exists a. Num a => a]
[exists a. Num a => a]
(不是真正的Haskell语法),它说每个元素都是一些未知的数字types。 要在Haskell中编写这个,我们需要引入一个包装数据types:
data SomeNumber = forall a. Num a => SomeNumber a
注意从exists
到forall
的变化。 那是因为我们正在描述构造函数 。 我们可以把任何数字types,但是然后types系统“忘记”它是哪种types。 一旦我们把它拿回来(通过模式匹配),我们所知道的是它是一些数字types。 发生了什么是SomeNumber
types包含一个隐藏的字段存储types类字典(又名.vtable /隐式),这就是为什么我们需要包装types。
现在我们可以使用types[SomeNumber]
作为任意数字的列表,但是我们需要将每个数字包装在例如[SomeNumber (3.14 :: Double), SomeNumber (42 :: Int)]
。 每种types的正确字典都会在包装每个数字的地方自动查找并存储在隐藏字段中。
存在types和types类的结合在某种程度上类似于子types,因为类类和接口之间的主要区别在于types类,vtable和对象分离,而存在types又将对象和vtable重新打包在一起。
但是,与传统的子types不同,你并不是要一对一地配对,所以我们可以写出类似这样的东西,用同样types的两个值打包一个vtable。
data TwoNumbers = forall a. Num a => TwoNumbers aa f :: TwoNumbers -> TwoNumbers f (TwoNumbers xy) = 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]
甚至更有趣的东西。 一旦我们在包装上进行模式匹配,我们又回到了types类的地方。 虽然我们不知道x
和y
是哪一种types,但是我们知道它们是相同的,并且我们有正确的字典可以对它们执行数字操作。
以上所有内容与多种types类似。 编译器将简单地为每个vtable生成包装types的隐藏字段,并在模式匹配时将它们全部纳入范围。
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)]
就斯卡拉而言,我是一个初学者,我不确定我可以帮你解决问题的最后部分,但是我希望这至less可以消除一些混乱,给你一些关于如何解决问题的想法继续。
@哈马尔的回答是完全正确的。 这是doint的scala方式。 对于这个例子,我将Show
作为types类,并将值i
和d
打包在一个列表中:
// 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
build立这个列表很容易,但列表不会“记住”每个元素的确切types。 相反,它会将每个元素上传到一个通用的超typesT
。 String
和Int
之间的精确超级超types是Any
,列表的types是List[Any]
。
问题是:忘记什么和记住什么? 我们想要忘记元素的确切types,但是我们要记住它们都是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)
它用相应的types实例打包一个值。
最后我们可以为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
这是非常接近预期的代码。