协慌网

登录 贡献 社区

Scala 2.8 馆藏图书馆是 “历史上最长的遗书” 吗?

我刚刚开始研究即将发布的2.8版本中的Scala 集合库重新实现 。熟悉 2.7 中的库的人会注意到,从使用角度来看,库几乎没有变化。例如...

> List("Paris", "London").map(_.length)
res0: List[Int] List(5, 6)

...... 适用于任何一个版本。 图书馆非常实用 :实际上它太棒了。然而,那些以前不熟悉 Scala 并且想要了解语言的人现在必须理解方法签名,例如:

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That

对于这样简单的功能,这是一个令人生畏的签名,我发现自己很难理解。 并不是说我认为 Scala 有可能成为下一个 Java (或 / C / C ++ / C#) - 我不相信它的创建者会瞄准那个市场 - 但我认为 Scala 成为 / 当然是可行的下一个 Ruby 或 Python(即获得重要的商业用户群)

  • 这会让人们去 Scala 吗?
  • 这是否会让 Scala 在商业世界中成为一个不好的名字,作为学术玩具 ,只有专门的博士生才能理解? CTO和软件负责人会不会受到惊吓?
  • 图书馆重新设计了一个明智的想法吗?
  • 如果您在商业上使用 Scala,您是否担心这一点?您是打算立即采用 2.8 还是等待看看会发生什么?

Steve Yegge 曾经因为他过于复杂的类型系统而攻击 Scala (在我看来是错误的)。我担心有人会在这个 API 上传播FUD (类似于 Josh Bloch 如何通过向 Java 添加闭包来吓唬JCP )。

注意 - 我应该清楚,虽然我相信约书亚布洛赫在拒绝 BGGA 关闭提案方面具有影响力,但我并没有将此归因于他诚实地认为提案代表错误的其他信息。


尽管我的妻子和同事一直在告诉我,我不认为我是一个白痴:我在牛津大学获得了很好的数学学位,而且我已经在商业方面进行了近 12 年的编程,并在斯卡拉进行了大约一年(也是商业上)。

请注意,炎症主题标题是关于 20 世纪 80 年代早期英国政党宣言的引文 。这个问题是主观的,但这是一个真实的问题,我已经成为 CW,我想就此事提出一些意见。

答案

我希望这不是 “遗书”,但我能看出你的观点。你同时发现了 Scala 的优势和问题:它的可扩展性 。这使我们可以实现库中的大多数主要功能。在其他一些语言中,将内置具有mapcollect类的序列,并且没有人必须看到编译器必须经历的所有箍以使它们顺利地工作。在 Scala 中,它都在一个库中,因此是公开的。

事实上,复杂类型支持的map功能非常先进。考虑一下:

scala> import collection.immutable.BitSet
import collection.immutable.BitSet

scala> val bits = BitSet(1, 2, 3)
bits: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> val shifted = bits map { _ + 1 }
shifted: scala.collection.immutable.BitSet = BitSet(2, 3, 4)

scala> val displayed = bits map { _.toString + "!" }
displayed: scala.collection.immutable.Set[java.lang.String] = Set(1!, 2!, 3!)

了解如何始终获得最佳类型?如果将Int s 映射到Int s,则会再次获得BitSet ,但如果将Int s 映射到String s,则会得到一个通用Set 。 map 的结果的静态类型和运行时表示都取决于传递给它的函数的结果类型。即使该集合为空,这也有效,因此该函数永远不会应用!据我所知,没有其他具有同等功能的集合框架。然而,从用户的角度来看,事情应该是如何运作的。

我们遇到的问题是,所有使这种情况发生的聪明技术都会泄漏到类型签名中,这些签名变得庞大而可怕。但也许用户不应该默认显示map的完整类型签名?如果她在BitSet查找map ,她得到了:

map(f: Int => Int): BitSet     (click here for more general type)

文档不会出现在这种情况下,因为从用户的角度来看,map 确实具有类型(Int => Int) => BitSet 。但是map也有一个更通用的类型,可以通过点击另一个链接进行检查。

我们尚未在我们的工具中实现这样的功能。但我相信我们需要做到这一点,以避免吓跑人们并提供更多有用的信息。有了这样的工具,希望智能框架和图书馆不会成为自杀笔记。

我没有博士学位,也没有任何其他类型的学位,既不是 CS,也不是数学,也不是其他任何领域。我之前没有使用 Scala 或任何其他类似语言的经验。我甚至没有远程可比类型系统的经验。事实上,我所拥有的唯一一种语言,不仅仅是一种肤浅的知识,甚至还有一个类型系统,它是 Pascal,并不完全以其复杂的类型系统而闻名。 (虽然它确实有范围类型,AFAIK 几乎没有其他语言,但这在这里并不真正相关。)我知道的其他三种语言是 BASIC,Smalltalk 和 Ruby,其中没有一种甚至都有类型系统。

然而,我毫不费力地理解你发布的map功能的签名。在我看来, map与我见过的其他语言几乎相同。不同之处在于此版本更通用。比起 Haskell,它看起来更像是一个 C ++ STL。特别是,它通过仅要求参数为IterableLike来抽象远离具体集合类型,并且还通过仅要求存在可以从该结果值集合构建某些内容的隐式转换函数来抽象远离具体返回类型。是的,这是非常复杂的,但它实际上只是泛型编程的一般范式的表达:不要假设任何你实际上不需要的东西。

在这种情况下, map实际上不需要将集合作为列表,或者被排序或可排序或类似的东西。 map唯一关心的是它可以一个接一个地访问集合的所有元素,但没有特定的顺序。并且它不需要知道生成的集合是什么,它只需要知道如何构建它。所以,这就是它的类型签名所需要的。

所以,而不是

map :: (a → b) → [a] → [b]

这是map的传统类型签名,它被推广为不需要具体的List而只是一个IterableLike数据结构

map :: (IterableLike i, IterableLike j) ⇒ (a → b) → i → j

然后通过仅要求存在可以结果转换为用户想要的任何数据结构的函数来进一步推广:

map :: IterableLike i ⇒ (a → b) → i → ([b] → c) → c

我承认语法有点笨拙,但语义是一样的。基本上,它从... 开始

def map[B](f: (A) ⇒ B): List[B]

这是map的传统签名。 (注意由于 Scala 的面向对象特性,输入列表参数消失了,因为它现在是单调度 OO 系统中每个方法都具有的隐式接收器参数。)然后它从具体List推广到更多一般IterableLike

def map[B](f: (A) ⇒ B): IterableLike[B]

现在,它将IterableLike结果集合替换为一个可以产生任何东西的函数。

def map[B, That](f: A ⇒ B)(implicit bf: CanBuildFrom[Repr, B, That]): That

我真的相信是不是很难理解。实际上只需要几个智力工具:

  1. 你需要知道(大致)是什么map 。如果你只提供没有方法名称的类型签名,我承认,要弄清楚发生了什么将会困难得多。但既然你已经知道应该做什么map ,并且你知道它的类型签名应该是什么,你可以快速扫描签名并关注异常,例如 “为什么这个map将两个函数作为参数,而不是一个?”
  2. 您需要能够实际读取类型签名。但即使你以前从未见过 Scala,这应该很容易,因为它实际上只是你已经从其他语言中学到的类型语法的混合:VB.NET 使用方括号进行参数多态,并使用箭头表示返回类型和冒号分隔名称和类型,实际上是常态。
  3. 您需要大致了解泛型编程的含义。 (这不是很难搞清楚,因为它的名字基本上都规定了:它真的只是一种通用的方式编程)。

这三者都不应该给任何专业甚至是业余爱好者的程序员带来严重的麻烦。 map已成为过去 50 年来设计的几乎所有语言的标准功能,不同语言具有不同语法的事实应该对任何使用 HTML 和 CSS 设计网站的人都很明显,你甚至无法订阅编程相关的邮件列表没有来自圣斯捷潘诺夫教堂的一些恼人的 C ++ 粉丝,解释了泛型编程的优点。

是的,Scala 复杂。是的,Scala 拥有人类已知的最复杂的类型系统之一,可以与 Haskell,Miranda,Clean 或 Cyclone 等语言相媲美甚至超越。但是,如果复杂性是反对编程语言成功的一个论据,那么很久以前 C ++ 就会死掉,我们都会编写 Scheme。 Scala 很可能不会成功的原因有很多,但程序员在坐在键盘前不能打开他们的大脑这一事实可能不会是主要原因。

C ++ 中的相同之处:

template <template <class, class> class C,
          class T,
          class A,
          class T_return,
          class T_arg
              >
C<T_return, typename A::rebind<T_return>::other>
map(C<T, A> &c,T_return(*func)(T_arg) )
{
    C<T_return, typename A::rebind<T_return>::other> res;
    for ( C<T,A>::iterator it=c.begin() ; it != c.end(); it++ ){
        res.push_back(func(*it));
    }
    return res;
}