我刚刚开始研究即将发布的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,你会担心吗?您是否计划立即采用2.8版本,还是等待结果?

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

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


尽管我的妻子和同事一直在告诉我,我并不认为自己是个白痴:我在牛津大学获得了很好的数学学位,我已经在商业编程近12年,在Scala编程大约一年(也是商业编程)。

请注意,煽动性主题标题引用了20世纪80年代初英国一个政党的宣言。这个问题是主观的,但这是一个真实的问题,我已经把它改成了CW,我想就此事发表一些意见。


当前回答

这会让人们不去斯卡拉吗?

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

但我确实认为Scala的流行程度将比现在的Java要低,因为我也认为下一个主流语言必须要简化得多,而我认为实现这一点的唯一方法是纯不变性,即声明性的HTML,但图灵是完整的。然而,我有偏见,因为我正在开发这样一种语言,但我只是在几个月的研究中排除了Scala不能满足我的需求后才这么做的。

这会不会让斯卡拉在商业界名声扫地,因为它是一种只有专业的博士生才能理解的学术游戏?首席技术官和软件负责人会被吓跑吗?

我认为斯卡拉的声誉不会因为哈斯克尔情结而受损。但我认为有些人会推迟学习,因为对于大多数程序员来说,我还没有看到一个用例迫使他们使用Scala,他们会拖延学习。也许高度可扩展的服务器端是最引人注目的用例。

而且,对于主流市场来说,首先学习Scala并不是“新鲜空气”,而是立即编写程序,比如首先使用HTML或Python。当一个人从一开始就了解了所有的细节之后,Scala会逐渐发展壮大。然而,如果我从一开始就阅读了Scala编程,我对学习曲线的经验和看法可能会有所不同。

图书馆重新设计是否明智?

肯定

如果你在商业上使用Scala,你会担心吗?您是否计划立即采用2.8版本,还是等待结果?

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


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

2.8 Scala集合使用生成器抽象是一个合理的设计原则。我想探讨以下两个设计权衡。

只写代码:写完本节后,我阅读了卡尔·斯莫特里茨的评论,这与我期望的权衡一致。James Strachan和davetron5000的评论一致认为,that(甚至不是that[B])的含义和隐含的机制不容易直观地理解。参见我在下面第2期中对幺半群的使用,我认为这一点更加明确。Derek Mahar的评论是关于编写Scala的,但阅读其他人的Scala又如何呢。我读过的关于Scala的一个批评是,写它比读别人写的代码更容易。我发现,由于各种原因(例如,编写函数的许多方法、自动闭包、DSL的单元等),这偶尔是正确的,但我不确定这是否是主要因素。在这里,隐式函数参数的使用有利有弊。另一方面,它减少了冗长的内容,并自动化了构建器对象的选择。在Odersky的示例中,从BitSet(即Set[Int])到Set[String]的转换是隐式的。不熟悉代码的读者可能不太清楚集合的类型,除非他们能够很好地解释当前包范围中可能存在的所有潜在的不可见隐式构建器候选项。当然,经验丰富的程序员和代码编写者会知道BitSet仅限于Int,因此到String的映射必须转换为不同的集合类型。但哪种集合类型?未明确指定。AD-HOC系列设计:写完本节后,我阅读了托尼·莫里斯的评论,意识到我的观点几乎相同。也许我更详细的阐述会让这一点更清楚。在Odersky&Moors的“用类型对抗比特腐烂”中,介绍了两个用例。它们是BitSet对Int元素和Map对元组元素的限制,并且是通用元素映射函数A=>B必须能够构建替代目标集合类型的原因。然而,从范畴论的角度来看,这是有缺陷的。为了在范畴理论中保持一致,从而避免角落情况,这些集合类型是函子,其中每个态射A=>B必须映射在同一函子类别中的对象之间,List[A]=>List[B],BitSet[A]=>BitSet[B]。例如,Option是一个函子,可以视为一个Some(对象)和None的集合的集合。没有从Option的None或List的Nil到其他没有“空”状态的函子的一般映射。这里有一个折衷的设计选择。在我的新语言的集合库的设计中,我选择将所有东西都变成一个函子,这意味着如果我实现了一个BitSet,它需要支持所有元素类型,在使用非整数类型参数时使用非位字段内部表示,并且该功能已经存在于它在Scala中继承的Set中。在我的设计中,Map只需要映射它的值,它可以提供一个单独的非函子方法来映射它的(键,值)对元组。一个优点是每个函子通常也是一个应用的,也许也是一个单元。因此,元素类型之间的所有函数,例如A=>B=>C=>D=>。。。,自动提升至提升应用类型之间的功能,例如列表[A]=>列表[B]=>列表[C]=>列表[D]=>。。。。对于从一个函子到另一个集合类的映射,我提供了一个映射重载,它接受一个幺半群,例如Nil、None、0、“”、Array()等。因此,生成器抽象函数是幺半群的append方法,并作为必要的输入参数显式提供,因此没有不可见的隐式转换。(Tangent:这个输入参数还允许附加到非空幺半群,这是Scala的映射设计无法做到的。)这样的转换是同一迭代过程中的映射和折叠。此外,我还提供了一个可遍历的,从类别意义上讲,“带效果的应用编程”McBride&Patterson,它还允许在从任何可遍历到任何应用的单个迭代过程中进行map+折叠,其中大多数集合类都是两者。此外,状态monad是一个应用程序,因此是一个来自任何可遍历的完全通用的构建器抽象。因此,Scala集合是“ad-hoc”的,因为它不是基于范畴理论的,而范畴理论是更高级指称语义的本质。尽管Scala的隐式构造器一开始看起来比函子模型+幺半群构造器+可遍历->应用性“更通用”,但它们并没有被证明与任何类别一致,因此我们不知道它们在最一般的意义上遵循什么规则,也不知道会给出什么样的角情况——它们可能不遵守任何类别模型。添加更多的变量会使事情变得更一般,这是不正确的,这是范畴理论的一个巨大好处,因为它提供了在提升到更高级语义的同时保持通用性的规则。集合是一个类别。我在某个地方读到,我认为这是Odersky,作为库设计的另一个理由,纯函数式编程的代价是在不使用尾部递归的情况下,递归和速度有限。到目前为止,我发现在我遇到的每一种情况下使用尾部递归并不困难。


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

其他回答

我根本不懂Scala,但几周前我还不会读Clojure。现在我能读到大部分,但除了最简单的例子之外,我还写不出任何东西。我想Scala也没什么不同。你需要一本好书或一门课程,这取决于你的学习方式。只是看了上面的地图声明,我大概得到了其中的1/3。

我认为,更大的问题不在于这些语言的语法,而在于采用和内化使它们在日常生产代码中可用的范例。对我来说,Java并不是C++的巨大飞跃,也不是C的巨大飞跃。Pascal和Basic等根本不是Java的飞跃。但用Clojure这样的函数式语言编写代码是一个巨大的飞跃(对我来说)。我想在Scala中,你可以用Java风格或Scala风格编写代码。但在Clojure中,您将创建一个相当混乱的环境,试图让您的命令习惯远离Java。

Scala有很多疯狂的特性(特别是在隐式参数方面),看起来非常复杂和学术,但设计的目的是使其易于使用。最有用的是语法糖(如[A<%B],这意味着A类型的对象隐式转换为B类型的对象)和对它们所做操作的详细说明。但大多数时候,作为这些库的客户,您可以忽略隐式参数,并相信它们会做正确的事。

我拥有一所廉价的“大众市场”美国大学的本科学位,所以我可以说我处于用户智能(或至少是教育)的中等水平:)我涉足Scala仅几个月,并开发了两三个非平凡的应用程序。

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

我发现我可以使用Scala作为一个“没有分号的Java”,也就是说,我编写的代码与我在Java中所做的代码相似,并且从语法简洁(比如通过类型推断获得的简洁)中获益匪浅。异常处理,当我做的时候,更方便。没有getter/setter样板,类定义就不那么冗长了。偶尔我会写一行代码,以实现相当于多行Java代码的功能。在适用的情况下,诸如map、fold、collect、filter等功能方法链的组成既有趣又美观。我很少能从Scala更强大的功能中获益:闭包和部分(或curried)函数、模式匹配。。。那种事。

作为一个新手,我继续努力学习简洁和惯用的语法。没有参数的方法调用不需要括号,除非它们需要括号;match语句中的case需要一个粗箭头(=>),但也有一些地方需要一个细箭头(->)。许多方法都有简短但相当隐晦的名称,如/:或\:-如果我翻了足够多的手册页,我可以完成我的工作,但我的一些代码看起来像Perl或行噪声。具有讽刺意味的是,最流行的语法速记之一在实际操作中缺失了:我一直被Int没有定义++方法这一事实所困扰。

这只是我的看法:我觉得Scala具有C++的强大功能,同时又具有C++的复杂性和可读性。语言的语法复杂性也使得API文档难以阅读。

Scala在很多方面都是经过深思熟虑的。我想很多学者都会喜欢用它编程。然而,它也充满了智慧和陷阱,它比Java有更高的学习曲线,更难阅读。如果我浏览论坛,看到有多少开发人员仍在为Java的精细之处而挣扎,我无法想象Scala会成为主流语言。没有一家公司能够证明派遣开发人员参加为期3周的Scala课程是合理的,因为他们以前只需要一周的Java课程。

我完全同意这个问题和马丁的回答:)。即使在Java中,由于额外的噪声,使用泛型读取javadoc也比应该的要困难得多。这在Scala中是复杂的,其中隐式参数被用作问题的示例代码(而隐式参数做了非常有用的集合变形)。

我不认为这是语言本身的问题——我认为这更多是工具问题。虽然我同意Jörg W Mittag所说的,但我认为查看scaladoc(或IDE中某一类型的文档)应该需要尽可能少的脑力来探索方法是什么、它需要什么和它的回报。不需要在一张纸上拼凑出一点代数就可以了:)

当然,IDE需要一种很好的方式来显示任何变量/表达式/类型的所有方法(就像Martin的例子一样,可以内联所有泛型,这样很好,也很容易找到)。我也喜欢马丁默认隐藏隐式的想法。

以scaladoc为例。。。

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

当在scaladoc中看到这个时,我希望默认情况下隐藏通用块[B,That]以及隐式参数(如果你用鼠标悬停一个小图标,它们可能会显示)-作为阅读它的额外内容,这通常并不相关。例如,想象一下这看起来像。。。

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

很好,很清楚,很明显。你可能想知道“那”是什么,如果你将鼠标放在上面或单击它,它可以展开[B,That]文本,突出显示“那”。

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

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

因此,默认情况下,类型系统的“噪音”被隐藏在人们需要查看的80%的主要内容中-方法名称、参数类型和返回类型,非常简单简洁-如果你真的非常关心,那么就很少有可扩展的链接来查看细节。

大多数人都在阅读scaladoc,以了解他们可以对类型调用什么方法以及可以传递什么参数。

这是另一个例子。。。

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<:A。泛型中的协变和逆变类型也会增加大量噪声,我认为这可以以更容易理解的方式呈现给用户。

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;
}