875

我刚刚开始研究即将发布的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(在我看来是错误的),因为他认为 Scala 的类型系统过于复杂。我担心有人会用这个 API 来散布FUD(类似于 Josh Bloch 如何让JCP害怕向 Java 添加闭包)。

注意-我应该明确一点,虽然我相信Joshua Bloch在拒绝 BGGA 关闭提案方面具有影响力,但我不会将此归因于他诚实地认为该提案代表错误的信念。


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

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

4

18 回答 18

892

我希望这不是“遗书”,但我明白你的意思。您发现了 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!)

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

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

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

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

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

于 2009-11-13T09:28:46.170 回答
226

我没有博士学位,也没有任何其他学位,既没有计算机科学,也没有数学,也没有任何其他领域。我之前没有使用过 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. 您需要大致了解泛型编程是关于什么的。(这并不难弄清楚,因为它基本上都以名称拼写出来:它实际上只是以通用方式编程)。

这三个都不应该给任何专业甚至业余程序员带来严重的头痛。map在过去 50 年中,几乎所有设计的语言都已成为标准功能,对于使用 HTML 和 CSS 设计网站的任何人来说,不同的语言具有不同的语法这一事实应该是显而易见的,而且您甚至无法订阅远程编程相关的邮件列表,没有来自 St. Stepanov 教堂的令人讨厌的 C++ 粉丝,他们解释了泛型编程的优点。

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

于 2009-11-12T18:56:31.527 回答
175

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;
}
于 2010-03-29T22:07:32.900 回答
71

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

我的意思是......大多数程序员不会关心那种类型签名,因为他们永远不会看到它们!他们不阅读文档。

只要他们看到了代码如何工作的一些示例,并且代码不会使他们无法产生他们期望的结果,他们就永远不会查看文档。如果失败,他们会查看文档并期望在顶部看到使用示例。

考虑到这些事情,我认为:

  1. 任何遇到该类型签名的人(如大多数人)都会嘲笑 Scala,如果他们预先准备好反对它,并且如果他们喜欢 Scala,他们会认为它是 Scala 力量的象征。

  2. 如果文档没有得到增强以提供使用示例并清楚地解释方法的用途以及如何使用它,它可能会影响 Scala 的采用。

  3. 从长远来看,这并不重要。Scala可以做这样的事情,这将使为 Scala 编写的库更强大,使用更安全。这些库和框架将吸引对强大工具感兴趣的程序员。

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

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

说到 Java,你有没有替换过类加载器?你有没有研究过这涉及到什么?如果您查看框架编写者所做的事情,Java 可能会很可怕。只是大多数人没有。同样的事情也适用于 Scala,恕我直言,但早期采用者倾向于查看他们遇到的每一块石头,看看是否隐藏着什么东西。

于 2009-11-12T20:39:48.060 回答
55

这会阻止人们来 Scala 吗?

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

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

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

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

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

如果你在商业上使用 scala,你会担心这个吗?您打算立即采用 2.8 还是等着看会发生什么?

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

“对于主流开发人员来说,Scala 是否过于复杂?”这一更普遍的话题......

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

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

于 2009-11-12T17:28:10.300 回答
46

Scala 社区可以帮助减轻对 Scala 新手程序员的恐惧的一种方法是专注于实践并通过示例进行教学——许多示例从小处开始逐渐变大。以下是一些采用这种方法的网站:

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

于 2009-11-15T11:08:51.417 回答
43

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

尤其是现在 IntelliJ 已经发布了他们的优秀 IDE,恕我直言,这是目前最好的 Scala 插件,Scala 开发相对轻松:

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

  • 偶尔我会设法编写一行代码来完成相当于多行 Java 代码的工作。在适用的情况下,map、fold、collect、filter 等功能方法链编写起来很有趣,而且看起来很优雅。

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

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

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

Scala在许多方面都经过深思熟虑并且非常出色。我怀疑许多学者会喜欢在其中编程。然而,它也充满了聪明和陷阱,它的学习曲线比 Java 高得多,而且更难阅读。如果我浏览论坛,看看有多少开发人员仍在为 Java 的优点而苦苦挣扎,我无法想象 Scala 会成为一种主流语言。以前他们只需要 1 周的 Java 课程,任何公司都无法证明让其开发人员参加 3 周的 Scala 课程是合理的。

于 2009-11-12T15:51:15.037 回答
33

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

我认为一个简单的“范围内必须有一个隐式对象,它为返回类型bf中的类型对象提供了一个构建器”会有所帮助,但当你真正想做的只是 map 时,这是一个令人兴奋的概念的。事实上,我不确定这是否正确,因为我不知道类型是什么意思,并且文档当然根本没有提供任何线索。BThatABReprTraversable

所以,我有两个选择,它们都不令人愉快:

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

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

于 2009-11-23T03:22:55.767 回答
22

我是 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")

这是正确的多态性。

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

于 2010-03-29T02:01:14.277 回答
20

不幸的是,您给 map 的签名是不正确的,并且确实存在合理的批评。

第一个批评是,通过颠覆 map 的签名,我们得到了更通用的东西。认为这是默认的美德是一个常见的错误。它不是。map 函数被很好地定义为一个协变函子 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 函子运行 traverse 来获取地图)。但是,我怀疑如果库编写者更了解有据可查的库泛化(具有效果的应用程序编程在上述内容之前),那么我们就不会看到这个错误。

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

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

您可能正在表达自己的观点,预测“普通程序员”的具体反应。也就是那些会声称“但它太复杂了!”的人。或一些这样的。这些是您所指的 Yegges 或 Blochs。我对这些反智主义/实用主义运动的人的反应是相当严厉的,我已经预料到会有一连串的反应,所以我就省略了。

我真的希望 Scala 库有所改进,或者至少,错误可以安全地藏在角落里。Java 是一种语言,“尝试做任何有用的事情”的成本如此之高,以至于它通常不值得,因为根本无法避免大量错误。我恳求 Scala 不要走同样的路。

于 2009-11-14T21:34:47.663 回答
15

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

我认为这不是语言本身的问题——我认为它更多的是工具问题。虽然我同意 Jörg W Mittag 所说的,但我认为查看 scaladoc(或 IDE 中类型的文档) - 它应该需要尽可能少的脑力来了解方法是什么,它需要什么以及返回什么。不需要在一张纸上破解一些代数来得到它:)

毫无疑问,IDE 需要一种很好的方式来显示任何变量/表达式/类型的所有方法(与 Martin 的示例一样,它可以内联所有泛型,因此它很好且易于理解)。我也喜欢 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

很好,很清楚,很明显它做了什么。您可能想知道“That”是什么,例如,如果您将鼠标悬停或单击它,它可以展开 [B, That] 文本以突出显示“That”。

也许一个小图标可以用于 [] 声明和 (implicit...) 块,所以很明显声明的一小部分折叠了?很难为它使用令牌,但我会使用 . 目前...

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

于 2010-03-04T10:22:18.380 回答
11

我不知道如何打破它,但我有剑桥的博士学位,我使用 2.8 就好了。

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

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

(然而,我看 Scala 的部分原因是想看看是否在工作中推动它,我现在还不打算这样做。让文档不那么令人生畏当然会有所帮助,但令我惊讶的是它仍然有多少变化和发展(公平地说,最让我惊讶的是它有多棒,但变化紧随其后)。所以我想我想说的是,我宁愿将有限的资源投入到实现它最后的状态——我认为他们没想到会这么受欢迎。)

于 2009-11-12T15:02:14.933 回答
10

根本不了解 Scala,但是几周前我无法阅读 Clojure。现在我可以阅读大部分内容,但除了最简单的示例之外,还不能写任何东西。我怀疑 Scala 也不例外。根据您的学习方式,您需要一本好书或一门课程。只是阅读上面的地图声明,我大概得到了1/3。

我相信更大的问题不是这些语言的语法,而是采用和内化使它们可用于日常生产代码的范式。对我来说,Java 不是从 C++ 的巨大飞跃,也不是从 C 的巨大飞跃,它根本不是从 Pascal 或 Basic 等的飞跃......但是用像 Clojure 这样的函数式语言进行编码一个巨大的飞跃(对于反正我)。我猜在 Scala 中,您可以使用 Java 风格或 Scala 风格进行编码。但是在 Clojure 中,你会制造出相当混乱的东西,试图让你的命令式习惯远离 Java。

于 2010-03-28T19:56:38.527 回答
7

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

于 2009-11-12T15:02:37.613 回答
6

这会阻止人们来 Scala 吗?

我不认为它是影响 Scala 流行程度的主要因素,因为 Scala 具有强大的功能,并且它的语法对于 Java/C++/PHP 程序员来说并不像 Haskell、OCaml、SML、Lisps、 ETC..

但我确实认为 Scala 的受欢迎程度将低于今天的 Java,因为我还认为下一个主流语言必须大大简化,而我看到的唯一方法是纯不变性,即像 HTML 一样的声明性,但图灵完备. 然而,我有偏见,因为我正在开发这样一种语言,但我只是在排除了几个月的研究后才这样做,Scala 不能满足我的需要。

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

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

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

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

确实。

如果你在商业上使用 Scala,你会担心这个吗?您打算立即采用 2.8 还是等着看会发生什么?

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


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

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

  1. 只写代码:写完本节后,我阅读了Carl Smotricz 的评论,该评论与我期望的权衡一致。James Strachan 和 davetron5000 的评论一致认为 That(甚至不是 That[B])的含义和隐含的机制不容易直观地掌握。请参阅下面问题 #2 中我对 monoid 的使用,我认为这更加明确。Derek Mahar 的评论是关于编写 Scala,但是如何阅读其他人的 Scala 不是“在常见情况下”。

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

  2. 特设收藏设计:写完本节后,我读了托尼莫里斯的评论,意识到我的观点几乎相同。也许我更详细的阐述会使这一点更清楚。

    在 Odersky & Moors 的“使用类型对抗钻头腐烂”中,介绍了两个用例。它们是 BitSet 对 Int 元素的限制,以及 Map 对元组元素的限制,并且作为通用元素映射函数 A => B 必须能够构建替代目标集合类型的原因提供。然而,从范畴论的角度来看,这是有缺陷的。为了在类别理论中保持一致并因此避免极端情况,这些集合类型是函子,其中每个态射 A => B 必须在同一函子类别 List[A] => List[B]、BitSet 中的对象之间映射[A] => 位集[B]。例如,Option 是一个函子,可以看作是 Some(object) 和 None 的集合。没有从 Option 的 None 或 List 的 Nil 到其他不存在的函子的一般映射

    这里有一个折衷的设计选择。在我的新语言的集合库的设计中,我选择将所有东西都变成函子,这意味着如果我实现一个 BitSet,它需要支持所有元素类型,通过在呈现非位域内部表示时使用非位域内部表示。整数类型参数,并且该功能已经在它从 Scala 中继承的 Set 中。而我设计中的 Map 只需要映射它的值,它可以提供一个单独的非函子方法来映射它的 (key,value) 对元组。一个优点是每个函子通常也是一个应用程序,也许也是一个单子。因此,元素类型之间的所有函数,例如 A => B => C => D => ...,都会自动提升到提升的应用类型之间的函数,例如 List[A] => List[B] => List[ C] => 列表[D] => .... 对于从仿函数到另一个集合类的映射,我提供了一个映射重载,它采用一个幺半群,例如 Nil、None、0、""、Array() 等。所以构建器抽象函数是一个幺半群的 append 方法和作为必要的输入参数显式提供,因此没有不可见的隐式转换。(Tangent:这个输入参数还允许附加到非空幺半群,这是 Scala 的地图设计无法做到的。)这种转换是同一迭代过程中的地图和折叠。此外,我还提供了一个可遍历的类别,即“具有效果的应用程序编程”McBride & Patterson,它还可以在从任何可遍历到任何应用程序的单次迭代中启用 map + fold,其中大多数集合类都是两者。

    因此,从某种意义上说,Scala 集合是“临时的”,它不以范畴论为基础,而范畴论是高级指称语义的本质。尽管 Scala 的隐式构建器乍一看比函子模型 + 幺半群构建器 + 可遍历 -> 适用性“更通用”,但它们并没有被证明与任何类别一致,因此我们不知道它们遵循什么规则最一般的意义以及极端情况将给出什么,它们可能不服从任何类别模型。添加更多变量会使事物更一般化是不正确的,这是范畴论的巨大好处之一,它提供了在提升到更高级别语义的同时保持一般性的规则。集合是一个类别。

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


此外,我的脑海里有一个不完整的想法,即 Scala 的一些权衡是由于试图成为一种可变和不可变的语言,这与 Haskell 或我正在开发的语言不同。这与托尼莫里斯关于理解的评论一致。在我的语言中,没有循环,也没有可变结构。我的语言将位于 Scala 之上(目前),并且非常感谢它,如果 Scala 没有通用类型系统和可变性,这将是不可能的。但这可能不是真的,因为我认为 Odersky & Moors(“Fighting Bit Rot with Types”)说 Scala 是唯一具有更高种类的 OOP 语言是不正确的,因为我(我自己和通过 Bob Harper)验证了标准ML 有它们。也出现 SML 的类型系统可能同样灵活(自 1980 年代以来),这可能不容易理解,因为其语法与 Java(和 C++/PHP)不像 Scala 那样相似。无论如何,这不是对 Scala 的批评,而是试图对权衡进行不完整的分析,我希望这与这个问题密切相关。Scala 和 SML 不会因为 Haskell 的无能为力而苦恼菱形多重继承,这很关键,我理解这就是为什么 Haskell Prelude 中的这么多函数针对不同的类型重复。

于 2011-09-13T05:51:00.417 回答
5

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

重点:

这会阻止人们来 Scala 吗?

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

如果这种教育是可用的,每个人都可以做到。去年我和一群学童在 SCALA 中构建了一台国际象棋计算机!他们有他们的问题,但他们最终做得很好。

如果你在商业上使用 Scala,你会担心这个吗?您打算立即采用 2.8 还是等着看会发生什么?

我不会担心的。

于 2013-04-21T10:09:41.900 回答
4

我也有牛津的数学学位!我花了一段时间才“得到”新的收藏品。但我现在非常喜欢它。事实上,'map' 的输入是 2.7 中让我烦恼的第一件大事(也许因为我做的第一件事是子类化集合类)。

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

我主要关心的是:2.8 什么时候发布?错误报告何时会停止出现?scala 团队在 2.8 版本中咬得比他们能咀嚼的多吗/试图一次改变太多?

我真的很希望看到 2.8 稳定版作为优先发布,然后再添加任何新内容,并且想知道(在旁观的同时)是否可以对 scala 编译器的开发路线图的管理方式进行一些改进。

于 2010-03-30T23:54:46.630 回答
0

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

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

很高兴知道大多数情况下它很简单,但不一定足够。如果要设计广泛的库,至少必须有一个人知道这些东西。

于 2010-03-29T11:18:20.493 回答