[翻譯] True Scala complexity

看板Translate-CS作者 (coolcomm)時間11年前 (2013/03/26 19:03), 編輯推噓1(102)
留言3則, 3人參與, 最新討論串1/1
可以先從這裡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)

03/26 19:16, , 1F
望你早歸(?)阿.....
03/26 19:16, 1F

03/26 19:50, , 2F
scala 真的很複雜
03/26 19:50, 2F

03/30 10:32, , 3F
“在我的意見中” XD IMO 應可譯為“就我看來”
03/30 10:32, 3F
文章代碼(AID): #1HKO2Ase (Translate-CS)