作者coolcomm (coolcomm)
看板Translate-CS
标题[翻译] True Scala complexity
时间Tue Mar 26 19:03:33 2013
可以先从这里copy
http://ideone.com/wOD714
再贴到这里
http://homepage.univie.ac.at/werner.robitza/markdown/
还有很多不会翻译=口=
--
# Scala 中真实的复杂性 #
*更新1: 另可参考[Hacker News的讨论
](
https://news.ycombinator.com/item?id=3443436)*
*更新2: Sorry for the downtime. Leave it to the distributed systems guy to
make his blog unavailable. Nginx saves the day.*
有关 Scala 的胡言乱语总是令人泄气──他们根本没有指出核心语言中的复杂性。
Understandable—this post is intended fill that gap, and it wasn’t exactly
easy to put together. But there’s been so much resistance to the very
thought that the complexity exists at all, even from [on up high][], that I
thought it would be constructive to provide a clearer illustration of why it’
s real and how it manifests itself.
[on up high]:
http://lamp.epfl.ch/~odersky/
这仍是一篇关於 Scala 的胡言乱语,由一位自称[Scala 拥护者][Scala advocate]的人
所写成。这对某些孤单的人来说会是个非常敏感的话题,而请你们在面对那些死亡威胁开
枪前先在每句话的开头加上"在我的意见中......"。
[Scala advocate]:
https://twitter.com/#!/yaaang/status/73902613296455680
* * *
自从2006年开始,我就持续研究*(译注:原文是"hack")*着 Scala 。那时候,编译器抛
出 exception 是例行性事件,标准 library 尚未完全成形,当时的网页甚至*完完全全*
是由忙碌的程式语言学者所设计出来的;另外,投身於社群也比现在寂寞多了。(我当时
还真是个受虐狂。)
在这之前,我已经用过 Haskell, Lisp ... 等,因此,我并非和大多数人一样对於
functional programming 或 category theory 感到陌生。但在所有我挑选的语言之中
,於一旁观看着 Scala 的成长是件最迷人的事了,我注视着一个我未曾幻想过会"成为主
流"的小小语言缓慢地在晦暗中描绘出了一条道路,在每个转折处面对数量看似不寻常的
批评,而 Martin 在信徒们哭喊着"[FUD][Addressing the Scala FUD]!"的同时仍有耐心
地驱使着 Scala 前行。
[Addressing the Scala FUD]:
http://www.theserverside.com/feature/Ending-the-Scala-Fud
我常看到以下这些 Scala 被批评的问题点,而这份列表同时也包含了 Typesafe [最优先
的目标][top priorities],另外,我还加上了些自己的拙见。
[top priorities]:
http://blog.typesafe.com/getting-down-to-work
- 效能:是的,I’ve been bitten by `for` loops and whatnot. 鱼与熊掌难以兼得。
对於那些最重视效能*(译注:原文是"to-the-metal")*的 hotspot ,我必须回去撰写
Java 等级的冗长语法,而我可以与此共存。将来,[编译器][compiler]或 VM 优化或许
能让我们*(译注:在不牺牲效能的同时)*享有更简洁的语法。
[compiler]:
http://code.google.com/p/scalacl/
- IDE 支援: IDE 的小毛病从来都不少,但它正以坚实的脚步逐渐成形且已经非常有用
。
- 学习曲线:从何而来决定了学习曲线的斜率,不过相关资源和社群支援不但还在持续进
步且都已经相当稳健。话说回来,这也是和本篇文章有着最大相关的问题点。
- 编译时间
- Binary 兼容性:在 Scala 2.9 时,版本计划中已经导入健全性和可被预测性。
- Frameworks
- 社群
- SBT:"SBT is confusing as shit."──最近一次[Scala SF meetup][]的发问者([其
他很多人][plenty others]也用了不同的字眼)这麽说。在各方面上,阿门。感谢上帝
Typesafe 已经承认之而且正试图解决。
[Scala SF meetup]:
http://www.ustream.tv/channel/ign-meetups
[plenty others]:
http://www.quora.com/Scala/Is-sbt-the-best-way-to-manage-Scala-projects-if-your-first-priority-is-developer-efficiency
- 不成熟的 library 生态:对於一个相对新的语言,这并不让人感到意外,而你*确实*
还拥有着 Java library 的广阔世界。
- `/:` 是啥鬼?为什麽我在发送 HTTP 请求时需要参考一个[周期表][periodic table]
?: 这些名称/设计实在是种不幸。还好包含 Scala 开发团队的 [library 作者们][]学
到了健康的一课而且正在远离这类做法。未来,我们大概会在 library 中见到更适当的
名称。
[periodic table]:
http://www.flotsam.nl/dispatch-periodic-table.html
[library 作者们]:
http://code.technically.us/post/13548980134/the-gist-of-it
毫无疑问的,这些是重大的问题, but these aren’t the ones keeping me up. 希望在
足够的时间与努力下,人们终会有办法解决它。
真正困扰我的是核心语言本身,某些更难解决的东西。 Typesafe 的议程中并未接触到的
是所谓*语言的复杂性*,一旦考虑之, Typesafe 试图控制特性上的采用并维持兼容性的
目标大概就会减缓语言的进化,而我们便不太可能看到语言在根本方向上的修正。
精确上来说,我所谓的"复杂性"指的是什麽?和许多事一样,这"只能意会,不能言传"啊
。复杂性是[难以被精确解释的概念][a tricky thing to articulate],无论我用多少种
方法切割它都不会比把你自己的头埋入其中一寸更有效。
[a tricky thing to articulate]:
http://lamp.epfl.ch/~odersky/blogs/isscalacomplex.html
因此,让我们在 Scala 的世界中漫步吧。值得记住的是我们将要遇到的问题并非我为了
个人论点凭空捏造的案例;它们都是在建构真实产品时会面临的状况(的简化版)。
* * *
## 操作 Collection ##
(我已经试着让不会 Scala 但有丰富语言经验的老到开发者也能藉由自己的方式猜测语法
的意义,但你大概要对 Scala 有着最起码的认识才会真正对那些挫折感到钦佩。如果你*
是*一个 Scala 的开发者,试着自己先想办法解决我们遇到的每个问题!)
作为第一步,我们先忽略泛型(generic),使用 Scala 当中一些基本 collection 的超级
简化版模型。假定我们已经有了 Java 的 `Array` type 和 Scala 中漂亮的 `Seq`
type 。 `head` 是 Scala collection 很多方法中的其中一个,存在於 `Seq` 但非
`Array` 中,这时我们会想要 [enrich][] `Array`s to `Seq`s 以在 `Array`s 上呼叫
`head` 之类的方法。
[enrich]:
https://groups.google.com/d/msg/scala-user/tIWGHcvQqH8/DW5bi7YZYNsJ
scala> class Array
defined class Array
scala> class Seq { def head = 0 } // dummy 实作
defined class Seq
scala> class WrappedArray(xs: Array) extends Seq // 一个 Array 至 Seq 的转接
器
defined class WrappedArray
下个方法被称作一个*隐式转换(implicit conversion)*,因为它在我们将一个 `Array`
当成一个 `WrappedArray` (或一个 `Seq` )使用时会被魔术般地自动调用,这样的自动
匹配被称为 "enrichment" 。
scala> implicit def array2seq(xs: Array): Seq = new WrappedArray(xs)
array2seq: (xs: Array)WrappedArray
现在我们能做这之类的事:
scala> new Array().head
res0: Int = 0
这很基本。现在我们想要用自己的方法──例如 `ident` (a dummy that’s just the
identity function) ──延伸 `Seq` ,我们将 `Seq` 隐式转换为一个包含我们扩充方
法*(译注:指 `ident` )*的匿名类别(anonymous class):
scala> implicit def seq2richseq(xs: Seq) = new { def ident = xs }
seq2richseq: (xs: Seq)java.lang.Object{def ident: Seq}
因此我们能这样做:
scala> new Seq().ident
res1: Seq = Seq@de81d48
以下的范例并不能运作*(译注:此指通过编译)*,因为 `Array` 不是 `Seq` 的子型别
(subtype):
scala> new Array().ident
<console>:13: error: value ident is not a member of Array
new Array().ident
^
问题是隐式转换并不会被多次套用。取而代之的是使用 `view bounds`。藉由 `[A <%
Seq]` 这样的符号,我们允许任何可被隐式转换为 `Seq` 的型别 `A`。
scala> implicit def seq2richseq[A <% Seq](xs: A) = new { def ident = xs }
seq2richseq: [A](xs: A)(implicit evidence$1: A => Seq)java.lang.Object{def
ident: A}
scala> new Array().ident
res3: Array = Array@3e55a58f
* * *
但各 collection 当然是带有泛型的。
scala> class Array[A]
defined class Array
scala> class Seq[A] { def head = 0 }
defined class Seq
scala> class WrappedArray[A](xs: Array[A]) extends Seq[A]
defined class WrappedArray
scala> implicit def array2seq[A](xs: Array[A]) = new WrappedArray[A](xs)
array2seq: [A](xs: Array[A])WrappedArray[A]
我们再度试着像以前一样用 view bounds 写出我们的转换方法:
scala> implicit def seq2richseq[A, I <% Seq[A]](xs: I) = new { def ident =
xs }
seq2richseq: [A, I](xs: I)(implicit evidence$1: I =>
Seq[A])java.lang.Object{def ident: I}
试试看:
scala> new Seq[Int]().head
res1: Int = 0
scala> new Array[Int]().head
res2: Int = 0
scala> new Seq[Int]().ident
<console>:13: error: No implicit view available from Seq[Int] => Seq[A].
new Seq[Int]().ident
^
还不行。为了更进一步,我们能用 *higher-kinded types* (`Function[I[_], ...]`)
代替 view bounds (後者会带来没有用的 `error: type I takes type parameters`)。
我们必须在 *curried argument list* 中明确的将转换方法以一个*隐式参数(implicit
parameter)*传入以便第一个引数列(argument list)能将型别推断(type inference)由型
别参数列(type parameter list)传入第二个引数列。
scala> implicit def seq2richseq[A, I[_]](xs: I[A])(implicit f: I[A] =>
Seq[A]) = new { def ident = xs }
seq2richseq: [A, I[_]](xs: I[A])(implicit f: I[A] =>
Seq[A])java.lang.Object{def ident: I[A]}
scala> new Seq[Int]().ident
res3: Seq[Int] = Seq@417d26fc
scala> new Array[Int]().ident
res4: Array[Int] = Array@280bca
*(译注:以下几段原始码的说明完全是意译......)*
当原本的类别带有多个型别参数时,型别匹配仍旧会失败:
scala> class DbResultSet[A, DbType]
defined class DbResultSet
scala> implicit def db2seq[A](db: DbResultSet[A,_]) = new Seq[A]
db2seq: [A](db: DbResultSet[A, _])Seq[A]
scala> new DbResultSet[Int, Nothing]().ident
<console>:17: error: value ident is not a member of DbResultSet[Int,Nothing]
new DbResultSet[Int, Nothing]().ident
^
我们必须要为带有任意数量泛型参数的型别提供相应的多载(overload)方法:
scala> implicit def seq2richseq2[A, I[_,_]](xs: I[A,_])(implicit f: I[A,_]
=> Seq[A]) = new { def ident = xs }
seq2richseq2: [A, I[_,_]](xs: I[A, _])(implicit f: I[A, _] =>
Seq[A])java.lang.Object{def ident: I[A, _]}
scala> new DbResultSet[Int, Nothing]().ident
res8: DbResultSet[Int, _] = DbResultSet@770fba26
对於每个泛型参数位置也要有不同的多载:
scala> class DbResultSet[DbType, A]
defined class DbResultSet
scala> implicit def db2seq[A](db: DbResultSet[_,A]) = new Seq[A]
db2seq: [A](db: DbResultSet[_, A])Seq[A]
scala> new DbResultSet[Nothing, Int]().ident
<console>:21: error: value ident is not a member of DbResultSet[Nothing,Int]
new DbResultSet[Nothing, Int]().ident
^
scala> implicit def seq2richseq22[A, I[_,_]](xs: I[_,A])(implicit f: I[_,A]
=> Seq[A]) = new { def ident = xs }
seq2richseq22: [A, I[_,_]](xs: I[_, A])(implicit f: I[_, A] =>
Seq[A])java.lang.Object{def ident: I[_, A]}
scala> new DbResultSet[Nothing, Int]().ident
res10: DbResultSet[_, Int] = DbResultSet@51274873
不然,你还可以为每个你想要应用在隐式转换中的特定型别*(译注:此指包含编译时期型
态的型别)*建立一个只带有单一型别参数的新型别来摆脱麻烦:
scala> class DbResultSet[DbType, PoolType, A]
defined class DbResultSet
scala> class MakeDbResultSet[A] extends DbResultSet[Nothing, Nothing, A]
defined class MakeDbResultSet
scala> implicit def db2seq[A](db: DbResultSet[_,_,A]) = new Seq[A]
db2seq: [A](db: DbResultSet[_, _, A])Seq[A]
scala> new MakeDbResultSet[Int]().ident
res11: MakeDbResultSet[Int] = MakeDbResultSet@5e0e4436
* * *
当然, `ident` 只是一个占位符,否则我们不需要将型态限制为 `Seq` 。我们事实上想
在这里有一个真正的方法──就这样吧, `runs` ,which returns the number of
runs of contiguous groups of elements, where groups are defined by the given
predicate for adjacent elements. 举例来说, `Seq(1,1,1,2,3,3) runs (_==_)` 应
该要传回 `3`。为了将事情简化,我们将永远只传回 `0` 。
scala> implicit def seq2richseq[A, I[_]](xs: I[A])(implicit f: I[A] =>
Seq[A]) = new {
| def runs(f: (A,A) => Boolean) = 0
| }
seq2richseq: [A, I[_]](xs: I[A])(implicit f: I[A] =>
Seq[A])java.lang.Object{def runs(f: (A, A) => Boolean): Int}
scala> new Seq[Int]().runs(_ == _)
res12: Int = 0
scala> new Array[Int]().runs(_ == _)
res13: Int = 0
现在加上 `isMajority(x)` ,当 `x` 是这个 `Seq` 中重复最多次的元素时传回
`true` ,不然传回 `false` 。(为了简单性,我们永远只传回 `false`)
scala> implicit def seq2richseq[A, I[_]](xs: I[A])(implicit f: I[A] =>
Seq[A]) = new {
| def runs(f: (A,A) => Boolean) = 0
| def isMajority(x: A) = false
| }
<console>:27: error: Parameter type in structural refinement may not refer
to an abstract type defined outside that refinement
def isMajority(x: A) = false
^
哎呀。当使用/传回 `new { ... }` 时,我们事实上使用了 *refinement types*,
which won’t work because on the JVM, where we have type erasure: Scala
generates a single generic implementation of the isMajority method, and it
calls methods on refinement types via reflection, so it has to fill in
something for the `?` in `seq2richseq(xs).getClass.getMethod("isMajority",
Array(?))`, and we don’t know what that is. Whereas for `runs`, we actually
do know what it is—this time, because of type erasure, we know it’s just a
`(_,_) => _` or `Function2[_,_,_]`—that it’s a `Function2[A,A,Boolean]`
doesn’t matter. Did you get that? [Here]’s a more long-winded explanation.
[Here]:
http://scala-programming-language.1934581.n4.nabble.com/scala-Structural-types-with-generic-type-question-td1992248.html#a1992249
此外,我们使用反射时会持续对效能造成重大的影响。
解决方案很简单:只要把原本的程式码塞进一个非匿名的类别就好了。
scala> class RichSeq[A](xs: Seq[A]) {
| def runs(f: (A,A) => Boolean) = 0
| def isMajority(x: A) = false
| }
defined class RichSeq
scala> implicit def seq2richseq[A, I[_]](xs: I[A])(implicit f: I[A] =>
Seq[A]) = new RichSeq(xs)
seq2richseq: [A, I[_]](xs: I[A])(implicit f: I[A] => Seq[A])RichSeq[A]
耶:
scala> new Seq[Int]().isMajority(3)
res16: Boolean = false
scala> new Array[Int]().isMajority(3)
res17: Boolean = false
* * *
现在在我们的 collection 继承树(hierarchy)中添加几个型别:
scala> class CharSequence extends Seq[Char]
defined class CharSequence
scala> class String extends CharSequence
defined class String
这是可行的:
scala> new CharSequence().isMajority('3')
res19: Boolean = false
scala> new CharSequence().isMajority(3) // Scala 将 ints 自动转换为 chars
res20: Boolean = false
但这却不能运作!
scala> new String().isMajority('3')
<console>:33: error: value isMajority is not a member of String
new String().isMajority('3')
^
问题出在哪?原来 Scala 在搜寻隐式转换时只会往继承树上方寻找至多一层。
试着明确地允许子型别:
scala> implicit def subseq2richseq[A, I <: Seq[A]](xs: I) = new RichSeq(xs)
subseq2richseq: [A, I <: Seq[A]](xs: I)RichSeq[A]
scala> new CharSequence().isMajority('3')
res22: Boolean = false
scala> new String().isMajority('3')
<console>:32: error: value isMajority is not a member of String
new String().isMajority('3')
^
还是不行!你能指出问题吗?
*Generalized type constraints* 要来拯救我们了!我们能使用几乎没被任何文件记载(
也不可能 Google 到)的 `<:<` 当成一个作为凭证(译注:原文是"evidence")的隐式参数
。
scala> implicit def subseq2richseq[A, I](xs: I)(implicit evidence: I <:<
Seq[A]) = new RichSeq(xs)
subseq2richseq: [A, I](xs: I)(implicit evidence: <:<[I,Seq[A]])RichSeq[A]
scala> new CharSequence().isMajority('3')
res24: Boolean = false
scala> new CharSequence().isMajority(3)
res25: Boolean = false
scala> new String().isMajority('3')
res26: Boolean = false
scala> new String().isMajority(3)
<console>:36: error: Cannot prove that String <:< Seq[Int].
new String().isMajority(3)
^
第四个案例依旧失败了!但我们还可以说是乐意於目前的进展。继续吧。
* * *
真实的状况更加复杂,因为 `CharSequence` 和 `String` 并不是 `Seq[Char]` 的子型
别,他们是被隐式转换为 `Seq[Char]` 的。
scala> class String
defined class String
scala> implicit def str2seq(xs: String) = new Seq[Char]
str2seq: (xs: String)Seq[Char]
未完成......
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 118.160.199.146
※ 编辑: coolcomm 来自: 118.160.199.146 (03/26 19:08)
1F:推 PsMonkey:望你早归(?)阿..... 03/26 19:16
2F:→ firose:scala 真的很复杂 03/26 19:50
3F:→ AmosYang:“在我的意见中” XD IMO 应可译为“就我看来” 03/30 10:32