我刚刚开始研究即将发布的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,我想就此事发表一些意见。


当前回答

我完全同意这个问题和马丁的回答:)。即使在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。泛型中的协变和逆变类型也会增加大量噪声,我认为这可以以更容易理解的方式呈现给用户。

其他回答

不幸的是,您提供的地图签名对地图来说是不正确的,而且确实存在合理的批评。

第一个批评是,通过颠覆地图的签名,我们得到了更一般的东西。认为这是一种默认的美德是一种常见的错误。它不是。映射函数被很好地定义为一个协变函子Fx->(x->y)->Fy,它遵循复合和恒等两个定律。任何其他归因于“地图”的东西都是一个笑话。

给定的签名是其他东西,但它不是地图。我怀疑它试图成为一个专门的、略有改动的“遍历”签名版本,该签名来自论文《迭代器模式的本质》。以下是其签名:

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]

当然,它失败了——它还不够普遍!此外,它略有不同(请注意,您可以通过运行遍历Identity函子来获得map)。然而,我怀疑,如果库编写者更了解有充分记录的库概括(前面提到的是使用效果的应用编程),那么我们就不会看到这个错误。

第二,map函数在Scala中是一种特殊的情况,因为它用于理解。不幸的是,这意味着一个装备更好的库设计者不能忽视这个错误,而不牺牲理解的语法糖。换句话说,如果Scala库设计者要破坏一个方法,那么这很容易被忽略,但请不要映射!

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

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

我真的希望Scala库能够改进,或者至少可以将错误安全地隐藏在角落里。Java是一种“尝试做任何有用的事情”的代价非常高昂的语言,因此通常不值得这样做,因为大量的错误根本无法避免。我恳求斯卡拉不要重蹈覆辙。

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

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

这是否会让scala在商业界成为一个只有专业的博士生才能理解的学术玩物?首席技术官和软件负责人会被吓跑吗?

有些人可能会。我不认为很多“专业”开发人员可以访问Scala,部分原因是Scala的复杂性,部分原因在于许多开发人员不愿意学习。雇佣此类开发人员的首席技术官们会被吓跑。

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

绝对地它使集合更适合语言和类型系统的其他部分,即使它仍然有一些粗糙的边缘。

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

我没有在商业上使用它。我可能会等到2.8.x系列的至少两个版本之后,再尝试引入它,以便清除bug。我还将拭目以待EPFL在改进其开发和发布过程方面取得了多大成功。我所看到的似乎充满希望,但我在一家保守的公司工作。

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

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

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

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

我拥有一所廉价的“大众市场”美国大学的本科学位,所以我可以说我处于用户智能(或至少是教育)的中等水平:)我涉足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课程。

我认为该方法的主要问题是(隐式bf:CanBuildFrom[Rrep,B,that])没有任何解释。尽管我知道什么是隐式参数,但没有任何信息表明这会如何影响调用。浏览scaladoc只会让我更加困惑(很少有与CanBuildFrom相关的类甚至有文档)。

我认为一个简单的“bf的作用域中必须有一个隐式对象,它为B类型的对象提供了一个构建器”会有所帮助,但当您真正想做的是将a映射到B时,这是一个令人兴奋的概念。事实上,我不确定这是对的,因为我不知道Repr类型是什么意思,而Traversable的文档也毫无头绪。

所以,我有两个选择,都不令人满意:

假设它只适用于旧地图的工作方式以及地图在大多数其他语言中的工作方式深入了解源代码

我认为Scala本质上是在揭示这些东西是如何工作的,最终这是提供了一种实现oxbow_lakes所描述的方法。但签名会分散注意力。