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


当前回答

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

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

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

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

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

其他回答

我希望这不是“遗书”,但我明白你的意思。您发现了Scala的优点和问题:它的可扩展性。这使我们能够实现库中的大多数主要功能。在其他一些语言中,带有map或collect之类的序列将被内置,没有人需要了解编译器要使其顺利工作所需的所有步骤。在Scala中,它都在一个库中,因此是公开的。

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

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!)

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

我们面临的问题是,所有实现这一点的巧妙技术都会泄露到类型签名中,而这些签名变得又大又吓人。但也许默认情况下不应该向用户显示地图的完整类型签名?如果她在BitSet中查找地图,她会得到:

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

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

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

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

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

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

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

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

第一个批评是,通过颠覆地图的签名,我们得到了更一般的东西。认为这是一种默认的美德是一种常见的错误。它不是。映射函数被很好地定义为一个协变函子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是一种“尝试做任何有用的事情”的代价非常高昂的语言,因此通常不值得这样做,因为大量的错误根本无法避免。我恳求斯卡拉不要重蹈覆辙。