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

对于如此简单的功能,这是一个令人生畏的签名,我发现自己很难理解。我并不认为斯卡拉会成为下一个Java(或 /C/C++ /C#)-我不相信它的创造者是针对这个市场的-但我认为Scala成为下一个Ruby或Python是/肯定是可行的(即获得大量的商业用户群)

  • 这会让人们不愿意来Scala吗?
  • 这是否会给Scala在商业世界带来只有敬业的博士生才能理解的学术玩具的坏名声?CTO和软件负责人会被吓跑吗?
  • 重新设计图书馆是一个明智的想法吗?
  • 如果您正在商业上使用Scala,您是否担心这一点?您是计划立即采用2.8还是等待看看会发生什么?

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

说明-我应该清楚的是,虽然我相信约书亚·布洛赫在拒绝BGGA关闭提案方面有影响力,我不认为这是因为他诚实地认为该提案代表了一个错误。


不管我的妻子和同事一直告诉我什么,我不认为我是个白痴:我从0开始就有一个很好的数学学位,我已经在商业上编程了将近12年,在1号编程了大约一年(也在商业上)。

这个问题是主观的,但它是一个真实的问题,我已经把它变成了CW,我想听听关于这个问题的一些意见。

115282 次浏览

我不知道该怎么跟你解释,我是剑桥大学的博士,我用2.8就好了。

更严重的是,我几乎没有花任何时间使用2.7(它不会与我正在使用的Java库进行互操作),并且在一个多月前开始使用Scala。我有一些Haskell的经验(不多),但只是忽略了你担心的东西,并寻找与我的经验相匹配的方法Java(我以此为生)。

所以:我是一个“新用户”,我没有被推迟-它像Java一样工作的事实给了我足够的信心来忽略我不理解的部分。

(然而,我看Scala的部分原因是想看看是否要在工作中推动它,我还不打算这样做。让留档不那么令人生畏肯定会有所帮助,但让我惊讶的是它仍在变化和开发中(公平地说,最让我惊讶的是它有多棒,但变化紧随其后)。所以我想我的意思是,我宁愿投入有限的资源来让它进入最终状态——我不认为他们会期待这么快就这么受欢迎。)

Scala有很多疯狂的特性(尤其是涉及隐式参数的地方),看起来非常复杂和学术,但旨在使事情易于使用。最有用的是获得语法糖(像[A <% B],这意味着A类型的对象隐式转换为B类型的对象)以及对它们作用的详细记录解释。但大多数时候,作为这些库的客户端,你可以忽略隐式参数,并相信它们会做正确的事情。

我有一个廉价的“大众市场”美国大学的本科学位,所以我想说我属于用户智能(或至少教育)规模的中间:)我涉足Scala只有几个月,并且已经在两个或三个非平凡的应用程序上工作。

特别是现在IntelliJ已经发布了他们的优秀IDE,IMHO是目前最好的Scala插件,Scala开发相对轻松:

  • 我发现我可以将Scala用作“没有分号的Java”,即我编写的代码看起来与Java相似,并从语法简洁中受益,例如通过类型推断获得的语法简洁。异常处理,当我这样做的时候,更方便。没有getter/setter样板,类定义就不那么冗长了。

  • 偶尔,我设法写一行来完成相当于多行Java的功能。在适用的情况下,一系列功能方法,如map、折叠、收集、过滤等,编写起来很有趣,看起来也很优雅。

  • 我很少发现自己受益于Scala更强大的功能:闭包和部分(或柯里化)函数、模式匹配……诸如此类的东西。

作为一个新手,我继续为简洁和惯用的语法而苦恼。没有参数的方法调用不需要括号,除非它们需要括号;匹配语句中的case需要一个胖箭头(=>),但也有一些地方需要一个细箭头(->)。许多方法的名称很短但相当神秘,比如/:\:——如果我翻阅足够多的手册页,我可以完成我的工作,但我的一些代码最终看起来像Perl或行噪音。具有讽刺意味的是,最流行的语法速记之一在行动中缺失了:我一直被Int没有定义++方法的事实所困扰。

这只是我的看法:我觉得Scala具有C++的力量,结合了C++的复杂性和易读性。语言的语法复杂性也使得API留档难以阅读。

Scala非常0,在许多方面都很出色。我怀疑许多学者会喜欢用它编程。然而,它也充满了聪明和陷阱,它的学习曲线比Java高得多,而且更难阅读。如果我扫描论坛,看看有多少开发人员仍在努力解决Java的细微之处,我无法想象Scala会成为主流语言。没有公司能够证明让开发人员参加为期3周的Scala课程是合理的,而以前他们只需要1周的Java课程。

这会让人们不愿意来Scala吗?

是的,但它也可以防止人们被推迟。自从Scala获得对高级类型的支持以来,我一直认为缺乏使用高级类型的集合是一个主要弱点。它使API文档更加复杂,但它确实使使用更自然。

这是否会让scala在商业世界中声名狼藉,成为只有敬业的博士生才能理解的学术玩物?首席技术官和软件负责人会被吓跑吗?

有些人可能会。我不认为许多“专业”开发人员可以使用Scala,部分原因是Scala的复杂性,部分原因是许多开发人员不愿意学习。雇用这些开发人员的CTO理所当然地会被吓跑。

重新设计图书馆是一个明智的想法吗?

当然。它使集合与语言的其余部分和类型系统更好地匹配,即使它仍然有一些粗糙的边缘。

如果您在商业上使用scala,您是否担心这一点?您是打算立即采用2.8还是等待看看会发生什么?

我没有在商业上使用它。我可能会等到2.8. x系列至少有几个版本,然后再尝试引入它,以便清除错误。我也会等着看EPFL在改进其开发和发布流程方面取得了多大成功。我看到的看起来很有希望,但我为一家保守的公司工作。

一个更普遍的话题是“Scala对主流开发人员来说太复杂了吗?”…

大多数开发人员,无论主流还是其他,都在维护或扩展现有系统。这意味着他们使用的大部分内容都是由很久以前做出的决定决定的。仍然有很多人在编写COBOL。

未来的主流开发人员将致力于维护和扩展今天正在构建的应用程序。这些应用程序中的许多不是主流开发人员构建的。明天的主流开发人员将使用当今最成功的新应用程序开发人员使用的语言。

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

然而,我完全可以理解你发布的map函数的签名。在我看来,它与map在我见过的所有其他语言中的签名几乎相同。不同之处在于这个版本更通用。它看起来更像是一个C++的STL东西,而不是Haskell。特别是,它通过只要求参数为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. 您需要大致了解泛型编程是关于什么的。(这并不难弄清楚,因为它基本上都在名称中拼写出来:它实际上只是以泛型方式编程)。

在过去的50年里,map几乎是每种语言设计的标准功能,不同的语言有不同的语法,这一事实对于任何设计了一个带有超文本标记语言和CSS的网站的人来说都是显而易见的,你不能订阅一个甚至远程编程相关的邮件列表,而不会有一些来自St. Stepanov教堂的烦人的C++粉丝解释泛型编程的优点。

是的,Scala复杂。是的,Scala拥有人类已知的最复杂的类型系统之一,可以与Haskell,Miranda,Clean或Cyclone等语言相媲美,甚至超过。但是,如果复杂性是反对编程语言成功的理由,C++早就死了,我们都将编写模式。Scala很可能不会成功的原因有很多,但程序员在坐到键盘前之前懒得打开他们的大脑这一事实可能不是主要原因。

好吧,我能理解你的痛苦,但是,坦率地说,像你和我这样的人——或者几乎任何普通的Stack Overflow用户——都不是规则。

我的意思是……大多数程序员不会关心那个类型签名,因为他们永远也见不到他们!他们不读留档。

只要他们看到代码如何工作的一些示例,并且代码在产生结果期望时没有使他们失败,他们就不会查看留档。当失败时,他们会查看留档并期望在顶部看到使用示例

考虑到这些因素,我认为:

  1. 任何遇到这种类型签名的人(就像大多数人一样),如果他们预先反对Scala,就会无休止地嘲笑它,如果他们喜欢Scala,就会认为它是Scala力量的象征。

  2. 如果留档没有得到增强以提供使用示例并清楚地解释方法的用途以及如何使用它,那么它可能会稍微减损Scala的采用。

  3. 从长远来看,这无关紧要。Scala可以做这样的事情将使为Scala编写的库更强大、使用更安全。这些库和框架将吸引依赖强大工具的程序员。

  4. 喜欢简单和直接的程序员将继续使用PHP或类似的语言。

唉,Java程序员非常喜欢强力工具,所以,在回答这个问题时,我刚刚修改了我对主流Scala采用的期望。我毫不怀疑Scala将成为一种主流语言。不是C主流,但可能是Perl主流或PHP主流。

说到Java,你曾经替换过类加载器吗?你曾经研究过它涉及到什么吗?Java可能很可怕,如果你看看框架编写者做的地方。只是大多数人不这样做。同样的事情也适用于Scala,IMHO,但是早期采用者倾向于查看他们遇到的每一块岩石下,看看是否隐藏着什么。

我希望这不是“遗书”,但我明白你的意思。你提到了Scala的优势和问题:它的扩展性。这让我们可以在库中实现大多数主要功能。在其他一些语言中,带有mapcollect之类的序列是内置的,没有人需要看到编译器为使它们顺利工作而经历的所有障碍。在Scala中,它都在一个库中,因此是公开的。

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

scala> import collection.immutable.BitSetimport 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映射到Int,你会得到一个BitSet,但是如果你将Int映射到Strings,你会得到一个通用的Set。map结果的静态类型和运行时表示都取决于传递给它的函数的结果类型。即使集合是空的,这也有效,所以这个函数永远不会应用!据我所知,没有其他集合框架具有等效的功能。然而从用户的角度来看,这就是事情的工作方式。

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

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

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

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

不幸的是,你给的地图签名是一个不正确的地图,确实有合法的批评。

第一个批评是,通过颠覆map的签名,我们有了更普遍的东西。认为这是默认的优点是一个常见的错误。事实并非如此。map函数被很好地定义为一个协变仿函数Fx->(x->y)->Fy,遵循组合和同一性两个定律。任何其他归因于“map”的东西都是一种嘲弄。

给定的签名是别的东西,但它不是map。我怀疑它试图成为论文《迭代器模式的本质》中“遍历”签名的一个专门的、稍微修改的版本。这是它的签名:

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

我将它转换为Scala:

def traverse[A, B](f: A => F[B], a: T[A])(implicit t: Traversable[T], ap: Applicative[F]): F[T[B]

当然它失败了——它还不够通用!此外,它还略有不同(请注意,您可以通过运行标识仿函数遍历来获取map)。然而,我怀疑,如果库作者更了解有据可查的库泛化(带有效果的应用编程先于上述),那么我们就不会看到这个错误。

其次,map函数在Scala中是一个特例,因为它在for理解中使用。不幸的是,这意味着装备更好的库设计师不能在不牺牲理解的语法糖的情况下忽略这个错误。换句话说,如果Scala库设计师要销毁一个方法,那么这很容易被忽略,但请不要map!

我希望有人把它说出来,因为事实上,解决Scala坚持犯的错误会变得越来越困难。显然,出于我强烈反对的原因。也就是说,“普通程序员不负责任的反对意见(即太难了!)”的解决方案不是“安抚他们,让他们更容易”,而是提供指导和帮助,让他们成为更好的程序员。我自己和Scala的目标在这个问题上有争议,但回到你的观点。

你可能是在表达你的观点,预测来自“普通程序员”的具体反应。也就是说,那些会声称“但它太复杂了!”或类似的人。你指的是叶格斯或布洛赫。我对反智主义/实用主义运动的这些人的回应相当严厉,我已经预料到了回应的弹幕,所以我将省略它。

我真心希望Scala库能够改进,或者至少,错误可以安全地藏在角落里。Java是一种“尝试做任何有用的事情”的代价非常高的语言,以至于它往往不值得,因为无法避免大量的错误。我恳求Scala不要走上同样的道路。

Scala社区可以帮助缓解刚接触Scala的程序员的恐惧的一种方法是专注于实践并以身作则——很多例子从小处开始,逐渐变大。以下是一些采用这种方法的网站:

在这些网站上花费一些时间后,人们很快意识到Scala及其库虽然可能难以设计和实现,但使用起来并不困难,尤其是在常见情况下。

我认为该方法的主要问题是(implicit bf : CanBuildFrom[Repr, B, That])没有任何解释。即使我知道什么是隐式参数,也没有任何迹象表明这会对调用产生什么影响。追逐caladoc只会让我更加困惑(与CanBuildFrom相关的类中甚至很少有留档)。

我认为一个简单的“在bf的作用域中必须有一个隐式对象,它为返回类型That的类型B的对象提供了一个生成器”会有所帮助,但当你真正想做的只是将A映射到B时,这是一个令人兴奋的概念。事实上,我不确定这是对的,因为我不知道类型Repr是什么意思,Traversable的留档肯定根本没有给出任何线索。

所以,我有两个选择,都不愉快:

  • 假设它只适用于旧map的工作方式以及map在大多数其他语言中的工作方式
  • 进一步挖掘源代码

我知道Scala本质上是在揭示这些东西是如何工作的,最终这是提供了一种方法来做oxbow_lakes描述的事情。但这会分散签名的注意力。

我完全同意这个问题和Martin的回答:)。即使在Java,使用泛型阅读javadoc也比应该的要困难得多,因为有额外的噪音。这在Scala中更加复杂,其中使用了隐式参数,就像问题的示例代码中一样(而隐式参数做了非常有用的集合变形)。

我不认为这是语言本身的问题——我认为这更像是一个工具问题。虽然我同意Jörg W Mittag所说的,但我认为查看caladoc(或IDE中类型的留档)——它应该需要尽可能少的脑力来摸索一个方法是什么,它需要什么和返回什么。不应该需要在一张纸上写一点代数来得到它:)

当然,IDE需要一种很好的方式来显示任何变量/表达式/类型的所有方法(就像Martin的例子一样,可以内联所有泛型,这样很好,很容易理解)。

举个例子…

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

当在caladoc中查看这个时,我希望默认情况下隐藏通用块[B, that]以及隐式参数(如果你用鼠标悬停一个小图标,它们可能会显示)-作为额外的东西来阅读它,这通常不是那么相关。

def map(f: A => B): That

漂亮、清晰、明显的功能。您可能想知道“那”是什么,如果您将鼠标悬停或单击它,它可能会展开突出显示“那”的[B,那]文本。

也许一个小图标可以用于[]声明和(隐式…)块,所以很明显有一些语句折叠了?很难使用令牌,但我现在使用…

def map.(f: A => B).: That

因此,默认情况下,类型系统的“噪声”隐藏在人们需要查看的主要80%的内容中——方法名称,它的参数类型和它的返回类型,以简单明了的方式——如果你真的关心那么多,几乎没有可扩展的细节链接。

大多数人都在阅读caladoc,以了解他们可以调用什么类型的方法以及他们可以传递什么参数。我们有点用太多的细节重载用户。

这是另一个例子…

def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1]

现在,如果我们隐藏泛型声明,它更容易阅读

def orElse(that: PartialFunction[A1, B1]): PartialFunction[A1, B1]

然后,如果人们悬停在A1上,我们可以显示A1的声明为A1<:泛型中的协变和逆变类型也会增加很多噪音,我认为可以以更容易理解的方式呈现给用户。

完全不了解Scala,但是几周前我还不会读Clojure。现在我可以读大部分,但除了最简单的示例之外,我还不能写任何东西。我怀疑Scala也不例外。你需要一本好书或课程,这取决于你如何学习。只是阅读上面的地图声明,我得到了也许 1/3。

我认为更大的问题不是这些语言的语法,而是采用和内化范式,使它们在日常生产代码中可用。对我来说Java与C++、C、Pascal和Basic等语言相比都不是一个巨大的飞跃……但是用Clojure这样的函数式语言编码是一个巨大的飞跃(无论如何对我来说)。我想在Scala中你可以用Java风格或Scala风格编码。但是在Clojure中,你会把自己的命令式习惯Java弄得一团糟。

我是Scala初学者,老实说,我看不出这种类型签名有什么问题。参数是映射的函数,隐式参数是构建器返回正确集合的参数。清晰易读。

实际上,整件事非常优雅。构建器类型参数让编译器选择正确的返回类型,而隐式参数机制对类用户隐藏了这个额外的参数。我尝试了这个:

Map(1 -> "a", 2 -> "b").map((t) => (t._2) -> (t._1)) // returns Map("a" -> 1, "b" -> 2)Map(1 -> "a", 2 -> "b").map((t) =>  t._2)            // returns List("a", "b")

这是正确的多态性。

当然,这不是一个主流的范例,它会吓跑很多人。但是,它也会吸引许多重视其表现力和优雅的人。

使用网站中的错误消息怎么办?

什么时候需要将现有类型与适合DSL的自定义类型集成在一起的用例呢?必须在关联、优先级、隐式转换、隐式参数、更高类型以及可能存在的类型方面受过良好的教育。

很高兴知道大部分都很简单,但这并不一定足够。如果要设计广泛的图书馆,至少必须有一个人知道这些东西。

同样的事情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;}

我也有牛津大学的数学学位!我花了一段时间才学会新集合的东西。但现在我很喜欢它。事实上,“map”的输入是2.7中困扰我的第一件大事之一(也许是因为我做的第一件事是集合类中的一个子类)。

阅读Martin关于新2.8集合的论文确实有助于解释隐含的使用,但是,留档本身肯定需要更好地解释不同类型的隐含在核心API的方法签名中的作用。

我更关心的是:2.8什么时候发布?bug报告什么时候会停止出现?scala团队用2.8咬得太多了吗?/试图一次改变太多?

我真的希望在添加任何其他新内容之前优先考虑2.8的稳定发布,并想知道(在旁观的同时)是否可以对scala编译器的开发路线图进行一些改进。

这会让人们不愿意来Scala吗?

我不认为这是影响Scala流行程度的主要因素,因为Scala有很多功能,它的语法对Java/C++ /PHP程序员来说并不像Haskell、OCaml、SML、Lisps等那样陌生。

但我确实认为Scala的受欢迎程度将稳定在Java今天的水平以下,因为我也认为下一个主流语言必须大大简化,我认为实现这一目标的唯一方法是纯粹的不变性,即像超文本标记语言一样的声明性,但图灵完备。然而,我有偏见,因为我正在开发这样一种语言,但我只是在经过几个月的研究后才这样做的,因为我排除了Scala不能满足我需要的。

这是否会让Scala在商业世界中声名狼藉,成为只有敬业的博士生才能理解的学术玩物?首席技术官和软件负责人会被吓跑吗?

我不认为Scala的声誉会受到Haskell复杂性的影响。但我认为有些人会推迟学习它,因为对于大多数程序员来说,我还没有看到强迫他们使用Scala的用例,他们会拖延学习。也许高度可扩展的服务器端是最引人注目的用例。

而且,对于主流市场来说,第一次学习Scala并不是“呼吸新鲜空气”,立即编写程序,例如首先使用超文本标记语言或Python。在一个人从一开始就学习了所有偶然发现的细节之后,Scala往往会在你身上成长。但是,也许如果我从一开始就阅读了Scala编程,我对学习曲线的体验和看法会有所不同。

重新设计图书馆是一个明智的想法吗?

肯定的。

如果您正在商业上使用Scala,您是否担心这一点?您是计划立即采用2.8还是等待看看会发生什么?

我正在使用Scala作为我的新语言的初始平台。如果我在商业上使用Scala,我可能不会在Scala的集合库上构建代码。我会创建自己的基于范畴理论的库,因为有一次我查看时,我发现Scalaz的类型签名比Scala的集合库更冗长和笨重。部分问题可能是Scala实现类型类的方式,这是我创建自己的语言的一个小原因。


我决定写这个答案,因为我想强迫自己研究并比较Scala的集合类设计和我为我的语言所做的设计。不妨分享我的思考过程。

2.8 Scala集合使用构建器抽象是一个合理的设计原则。我想在下面探索两个设计权衡。

  1. WRITE-ONLY CODE:写完本节后,我读到了卡尔·斯莫特里茨的评论,它同意我所期望的权衡。James Strachan和davetron5000的评论一致认为that(它甚至不是That[B])的含义和隐含的机制不容易直观地掌握。请参阅我在下面的问题#2中对monoid的使用,我认为这更明确。Derek Mahar的评论是关于编写Scala,但是阅读其他人的Scala怎么样,不是“在常见情况下”。

    我读到的关于Scala的一个批评是,编写它比阅读别人编写的代码更容易。我发现出于各种原因(例如编写函数的许多方法、自动闭包、DSL的单元等),这偶尔是真的,但我不确定这是否是主要因素。在这里,使用隐式函数参数有优点和缺点。从好的方面来看,它减少了冗长并自动选择构建器对象。在Odersky的示例中,从BitSet,即Set[Int]到Set[String]的转换是隐式的。熟悉代码的读者可能不容易知道集合的类型是什么,除非他们能够很好地推理当前包范围中可能存在的所有潜在的不可见的隐式构建器候选者。当然,有经验的程序员和代码编写者会知道BitSet仅限于Int,因此到String的映射必须转换为不同的集合类型。但是哪种集合类型?它没有明确指定。

  2. AD-HOC系列设计:写完这一节后,我读了托尼·莫里斯的评论,意识到我的观点几乎是一样的。也许我更详细的阐述会使观点更清楚。

    在《用类型对抗比特腐烂》中,Odersky&Moors提出了两个用例。它们是BitSet对Int元素的限制,以及Map对元组元素的配对,并且是因为通用元素映射函数A=>B必须能够构建替代目标集合类型。然而,从范本论的角度来看,这是有缺陷的。为了在范本论中保持一致,从而避免角落情况,这些集合类型是函子,其中每个态射,A=>B,必须在相同仿函数类别中的对象之间映射,List[A]=>List[B], BitSet[A]=>BitSet[B]。例如,Option是一个仿函数,可以看作是一个一些(对象)和无的集合。没有从Option的无或List的Nil到其他没有“空”状态的函数的通用映射。

    这里有一个权衡的设计选择。在我的新语言的集合库的设计中,我选择将所有内容都设为仿函数,这意味着如果我实现一个BitSet,它需要支持所有元素类型,当使用非整数类型参数时,它需要使用非位字段内部表示,并且该功能已经在Set中,它在Scala中继承了它。在我的设计中,Map只需要映射它的值,它可以提供一个单独的非仿函数方法来映射它的(key, value)元组。一个优点是每个仿函数通常也是一个应用程序,也许也是一个单子。因此,元素类型之间的所有函数,例如A=>B=>C=>D=>…,都会自动提升到提升的应用类型之间的函数,例如List[A]=>List[B]=>List[C]=>List[D]=>……为了从一个仿函数映射到另一个集合类,我提供了一个映射重载,它需要一个单半群,例如Nil、无、0、""、Array()等…因此,构建器抽象函数是单半群的追加方法,并作为必要的输入参数显式提供,因此没有不可见的隐式转换。(切线:这个输入参数也可以附加到非空的单元格,这是Scala的map设计无法做到的。)这样的转换是同一迭代传递中的map和折叠。我还提供了一个可遍历的,在类别意义上,“带有效果的应用编程”McBride&Patterson,它还可以在一次迭代中从任何可遍历传递到任何应用,其中大多数每个集合类都是两者。状态单子也是一个应用,因此是从任何可遍历的完全广义的构建器抽象。

    因此,从某种意义上说,Scala集合是“特别的”,因为它没有建立在范畴理论的基础上,而范畴理论是更高层次指称语义学的本质。尽管Scala的隐式构建器乍看起来比仿函数模型+单半群构建器+可遍历->适用性“更广义”,但它们没有被证明与任何范畴一致,因此我们不知道它们在最普遍的意义上遵循什么规则,也不知道它们将给出什么角落案例,它们可能不遵守任何范畴模型。添加更多的变量使事物更具普遍性是不正确的,这是范畴理论的巨大好处之一,因为它提供了保持普遍性的规则,同时提升到更高层次的语义学。

    我在某处读到,我认为是Odersky,作为库设计的另一个理由,纯函数式编程在不使用尾递归的情况下具有有限递归和速度的成本。到目前为止,我还没有发现在我遇到的每种情况下使用尾递归是困难的。


此外,我脑海中还有一个不完整的想法,即Scala的一些权衡是由于试图成为一种可变和不可变的语言,不像Haskell或我正在开发的语言。这与Tony Morris关于理解的评论一致。在我的语言中,没有循环,也没有可变结构。我的语言将坐在Scala之上(目前)并归功于它,如果Scala没有通用的类型系统和可变性,这是不可能的。不过,这可能不是真的,因为我认为Odersky&Moors(“用类型对抗比特腐烂”)说Scala是唯一具有更高种类的OOP语言是不正确的,因为我(自己和通过Bob Harper)验证了Standard ML拥有它们。此外,SML的类型系统似乎也相当灵活(自20世纪80年代以来),这可能不容易被理解,因为语法与Scala不像Java(和C++ /PHP)那么相似。无论如何,这不是对Scala的批评,而是试图呈现对权衡的不完整分析,我希望这与问题密切相关。Scala和SML没有受到Haskell无法执行菱形多重继承的影响,这是至关重要的,我理解为什么Haskell前奏中的这么多函数会针对不同类型重复。

似乎有必要在这里说明学位:政治学学士学位和计算机科学B.ed。

开门见山:

这会让人们不愿意来Scala吗?

Scala很难,因为它的底层编程范式很难。函数式编程吓坏了很多人。在PHP中构建闭包是可能的,但人们很少这样做。所以,不,不是这个签名,而是所有其他的签名都会让人望而却步,如果他们没有接受过特定的教育,让他们重视底层范式的力量。

如果这种教育是可行的,每个人都可以做到。去年我和一群学生用SCALA做了一台国际象棋电脑!他们有他们的问题,但最终他们做得很好。

如果您正在商业上使用Scala,您是否担心这一点?您是计划立即采用2.8还是等待看看会发生什么?

我不会担心。