shapeless(1): 从方法与函数的多态谈起

最近在用spray,它依赖了一个shapeless的库,这个库按照它github上的介绍来说,是一个基于泛型编程的提供了类型类(type class)和依赖类型(dependent type)的库。type classdependent type这两个模式术语都来自于haskell,所以新人难免会感到头大,关于type class模式,我在scala类型系统的系列文章里有提到过,可以参考这里;简单来说,它们要解决的都是多态的问题。

写这篇文章,是我阅读了shapeless作者miles的几篇blog之后的一些体会,原文在:http://www.chuusai.com/2012/04/27/shapeless-polymorphic-function-values-1/,我用的例子也引用他blog的例子。

对于多态的实现,主要有2种方式(可参考我在2013华东scala爱好者聚会时分享的ppt):

1)子类型多态
2)类型参数多态

子类型方式的多态是面向对象系统里天然支持的,我们这里讨论的主要是基于类型参数的多态。

miles的blog里提出了这样一个观点:scala里有一类的(first-class)单态(monomorphic)函数值(function value),有二类的(second-class)多态(polymorphic)方法,但没有一类的多态函数值。至少在标准的scala定义里不行。

所谓的 first-class 是表示可以像变量那样被传递,scala里方法与java里一致,不可传递,只有函数(背后是用对象承载)才可传递。我们看看为什么他说scala的函数不像方法那样支持多态(类型参数化多态)。

用他的例子,方法的多态:

scala> def singleton[T](t : T) = Set(t)

scala> singleton("foo")
res4: Set[java.lang.String] = Set(foo)

scala> singleton(23)
res5: Set[Int] = Set(23)

对方法singleton在运行时传入不同类型,编译在做类型推导时都支持的很好。

然而,当我们把它转换为函数时,类型推导就出了问题:

scala> val singletonFn = singleton _
singletonFn: (Nothing) => Set[Nothing] = <function1>

上面通过下划线把方法转为部分应用函数,但编译器却在类型推导的过程中把原先泛型参数,推导为了Nothing(关于Nothing类型,可参考这篇),或许你会奇怪,为什么原来的泛型参数T,被推导成了Nothing? 这块涉及scala类型推导的细节,参考:泛型方法转换为部分应用函数时的类型推导问题

正因为scala函数类型在构造时必须明确入参和出参类型,所以方法中的泛型参数被使用上限或下限替代(取决于协变还是逆变)。这样导致方法转为函数后,失去了原本具有的多态性:

scala> singletonFn("foo")  // 类型不匹配,要求Nothing类型

如果在对方法转为函数的时候,采用明确的类型声明:

scala> val singletonFn : String => Set[String] = singleton _

这种情况下,也只针对String类型,无法满足Int的情况。

所以miles称scala只是一类的“单态”函数,而非“多态”函数。为了实现一类的“多态”函数,他才开发了shapeless这个库,我们后续再介绍。

shapeless(1): 从方法与函数的多态谈起》上有2条评论

  1. netcomm

    “first-class” 是否还有更好的翻译,感觉“一类”不大合适,hoho。

    回复
    1. hongjiang 文章作者

      “first-class”, “second-class” 我是直译的。可能用“一等”,“二等”,或“第一级别”,“第二级别”更好一些。

      回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注