為什麼說 Scala 是 JVM 上的 C++?
如題。以及請大家分析下未來靜態語言的主流,C++、java、scala三門語言未來的發展方向
scala本身只是語法糖多,只看錶象的人自然就會覺得複雜,其實隨著學習的深入你會發現不然。scala
里,包括中綴運算(1.+(2))加法只是整數對象的一個函數,for,from,
to等等,都是語法糖,可以理解為嵌入在Scala里的DSL,之前在畢業設計里見到導師如此定義一個向量,from
v1 to v2 along v3,這樣的性質都是本身scala靈活的結果,Scala自己加糖這一特點比Haskell還要突出,在實踐中可以極大地接近自然語言。樓上也有說看各種Langauge
Reference的,我深深表示懷疑。Scala在設計上是精簡的,遠遠精簡於C++與Java,語法總結10頁都不到。這個在連城在北京TW公司的會上也說過,Scala本身應該是一個比較簡單的語言,只是雜糅了兩OO與FP所以顯得複雜,如果大家有OO跟FP分別的基礎再來審視Scala其實會發現Scala並不是想像的那麼複雜,並且恰恰相反。所以勸大家不了解不多就隨意評論,最好只評論自己長期以來每天用的工具跟語言,因為作為一個研究Haskell的人見太多對Haskell跟GHC的錯誤評價與不公對待。一面說純函數式的Haskell慢一面解釋運行著Python跟Ruby。
至於本貼的題目,我想多半是吸引眼球的,我的理解Scala跟C++在各個方面都沒什麼太大關係,Clojure是JVM上的Lisp說得過去,C#是.Net的Java也沒人跟你爭,但是Scala是JVM上的C++就太扯了,無邏輯可言。
回應一下Scala編譯慢的問題,首先相信大公司百萬行的代碼並不是用PC編譯的。另外,更多人沒有考慮到實現同樣的東西Scala會比Java少多少代碼,當然這個需要根據實際項目而定,不過我覺得少1~10倍之前都是可能的,這在TW那次會上吳雪峰談到了。那程序員是想花比較少的時間寫Scala代碼然後花1天編譯,還是想多花一倍的時間再多寫一倍的代碼然後花半天時間編譯?
回應Haskell有的東西Scala里都有。我懷疑說這種話的人對於Haskell與Scala有多少了解。首先GHC現在已經有100左右的Extension了,Scala看了一段時間,恕我Scala學得不多,不過Haskell里的任意Rank類型,Kind
polymorphism,Type family在Scala里我都沒看到。更加不知道怎麼在Scala里做dependent
type。Scala是一直向Haskell學習的一個語言,說Haskell有的Scala都有這種話太過不負責任與輕浮。現在Scala還年輕,不說別的,單說GHCi的功能可以甩Scala解釋器幾條街了,且不說Haskell凝聚了多少大師跟PhD的結晶,單說Haskell進化的包袱就比Scala少很多,很多特性GHC說不要就不要,很多庫說deprecate就deprecate,Haskell的設計者也不在乎Haskell能否遊行,Scala想追上這速度進化的Haskell短期內是不太可能的,Scala革命未成仍需努力。
C++已經根深樹大,相信未來還需要使用他,但是一直羨慕GC、FP的特性,於是有了很多庫來干這些事情,但是增加了學習的陡峭程度,性能上也有無法取代的優勢,所以相信很長時間都不會死。
Java自Oracle收購不思進取,已經被C#甩出很遠,但是一直鼓吹的可移植,以及各種Java開發者職位什麼的會騙更多人進來。但是長久下來不思進取被C#跟Scala瓜分不無可能。
Scala會不斷地讓那些用Java煩躁的人進來,越來越大,相信Java用久了的人都會有煩躁的一天的。但想動搖Java成熟的社區已經開發氛圍,短期內也是不可能的。
被一個做NodeJS後端開發的盆友轉發了鏈接,所以來稍微系統一點的回答一下這個問題。首先vczh大神表示對標題的贊同,認為Scala和C++的複雜度等同,這個說法不是沒有依據,但不能說全對。依據可以參照這位麻省理工大神@yaaang(Yang Zhang)在2009年發的帖子:True Scala complexity(真實的Scala複雜度)。
一直以來關於Scala究竟是簡單還是困難,在Scala使用者之中都有不少爭議。Yang Zhang作為Scala的愛好者,委婉的提出Scala內部核心構造過於複雜,他發布這篇文章時已經使用了3年的Scala,之前更是從Haskell,Lisp處出發的。先撇開他的文章不談,談談為什麼Scala可能會比較複雜。
Scala是一門給函數式語言和命令式語言搭橋的語言,它同時保有兩者的性格。它的發明者Martin Odersky真是編譯器的大神,不僅是教授,還是天才。Java目前的編譯器javac的現代版本,就是採用他的版本。他當年受邀給javac寫一個新版本叫java-generics,最後發現他的編譯器不僅比Sun公司團隊的更穩定,更易於維護,一個人虐過一個團隊,絕對是大神級別。從Javac1.3版本開始,都沿用了馬丁的javac編譯器。這件事發生在2000年。
馬丁·奧德斯基是函數式語言的愛好者,他終生一直在JVM平台上工作。他的第一個語言叫Pizza(1996年),是講泛型(Generics)帶到了JVM上,當時人們宣稱把函數式語言的功能移植到JVM平台是不可能,但是馬丁就做到了,用了一個叫Pizza的實驗語言。正是因為這個語言,才引起了Sun公司的注意。Sun公司核心開發團隊的Gilad Bracha and David Stoutamire聯繫了馬丁·奧德斯基,表示了他們的興趣,讓馬丁開發Generic Java項目(1998年)。6年之後,馬丁·奧德斯基開發的Generic Java (GJ),終於被加入到了Java1.5之中。有人知道為什麼Java中的基礎數組array[]是沒有泛型的嗎?馬丁在採訪中回答說因為最開始寫Java編譯器的幾個人偷懶取巧,最後導致基礎數組沒法(或者說很難)實現泛型。
如果這裡有早期Java用戶,就應該知道Java開始攻佔大面積市場,成為很穩定而有名的主流語言,正是在1.5(也就是Java5)之後。如果還想了解他的故事,可以去讀讀Artima雜誌對他的採訪:The Origins of Scala 這個採訪一共有5個部分(叫作Scala的起源)。
目前討論的比較多的Scala的主流難度一共有兩點。第一點是Scala的可讀性。很多人,尤其是未曾使用過語言,或者新手抱怨Scala很難讀懂,可讀性差。這點基本已經被Scala社區給推翻了。理由很簡單,如果你不會法語,自然看不懂法文寫成的書。不懂Scala自然就讀不懂Scala,這很簡單。至於新人難以讀懂高手寫的代碼,一些Scala社區高手們說這樣最好不過,免得公司實習生亂改他們的代碼(笑談笑談)。這個的原因主要是高手傾向於寫更Scala風格(函數式風格)的代碼,而新手(根據不同的背景)可能更習慣於過程式或者命令式的代碼。
比如新手寫這樣一個代碼:
val autoList = for (i &<- 1 to 10) { //do this task 10 times}
但稍有經驗一點的編程者就會寫成:
val autoList = (1 to 10).map(i =&> /*do this task 10 times*/)
這兩者基本等同,但不熟悉map用法的新手就會覺得這樣的寫法非常奇特。
重新回到馬丁·奧德斯基的故事。他是函數式編程的愛好者,也想要將更多的函數式編程法寶帶入到JVM上。當年他在Pizza(1996年出現,在Java出現僅一年後)就已經將higher-order function(高階函數),pattern matching(模式匹配)和generics(泛型)帶入了JVM。但由於各種原因,Pizza過於實驗性質,最終沒能流行起來。
但大神非同於常人的地方就是他沒有放棄,在2005年的時候推出了Scala,先是一系列的學術論文,An overview of the Scala programming language,Type inference with constrained types,Scalable component abstractions
尤其是這最後一篇,探討了幾種OOP的實現,Trait-based OOP和Class-based OOP以及孰優孰劣的討論非常有趣。Scala基本採用了Trait-based OOP,並且衍生出了很棒的Cake Pattern(蛋糕設計模式),不過這已經不在這個問題的討論範圍內。於是在Scala中,馬丁·奧德斯基走得更遠一步,將Type Inference(類型推斷),Higher-kindered Type(高階型類)等等帶入了Scala。換言之,Scala基本就是一個在JVM上實現了的Haskell,但一不如Haskell簡單,二不如Haskell功能完整。這點在這個30分鐘的YouTube視頻,Rúnar Bjarnason 講得很清楚:https://www.youtube.com/watch?v=hzf3hTUKk8U 。不過他在最後還是告訴觀眾,要對Scala抱有期望,它還是個年輕的語言,會更好的。如果看不到視頻,他的意思基本就是:1. Scala的高階型類支持有限;2. Scala的類型推斷不如Haskell強; 3.Scala的尾遞歸需要額外標註。我對此的回答是,Scala的類型推斷基於JVM的限制上,能做到這樣的程度已然不錯,日後還會改善。Java連尾遞歸都不優化,所以能夠優化一下已經很不錯了。
所以說Scala是一門特地為Haskell和Lisp等函數式語言編程者設計的,在JVM上運行的語言,但是大量吸引來的卻是Java和C++領域的編程者。這跟Go語言很像,Go語言最初希望吸引C/C++的程序員,最終釣上來一派Python眾。語言創造大神們大叫一聲蒼天何負我。
本來人們認為類型推斷等函數式語言的特徵不能移植到JVM上,但正如Pizza的存在,馬丁·奧德斯基再一次證明人們錯了。正如Pizza帶來了Java1.5,Scala帶來了Java1.8(Java 8)。和Pizza年代不同的是,馬丁·奧德斯基非常打算將Scala變得更加普及,不僅成立了公司TypeSafe,還不遺餘力的進行推廣,同時Java 8隻實現了Scala的一小部分功能,並不能完全作為替代品。
所以說Scala困難的第二點就是說Scala的函數式部分困難,命令式程序員轉變思維難,了解概念也不容易。這也是vczh為何推薦那兩本書的原因。但這也是等同於在說,學會Java的高階部分難,說Java泛型難,說Java 8的Lambda表達式難,這樣的宣稱是沒有意義的。
所以函數式編程真的困難嗎?不困難。這裡引用劉慈欣在《黑暗森林》對四維世界的描述,就是大,太大了,太自由了,重回三維世界感覺像要窒息一般。這點基本是函數式語言編程者對使用命令式語言的描述。Haskell和Lisp程序員笑稱Java為BD(Bondage Disipline 捆綁與管教——SM的一種)語言。他們也經常告訴外界人士,使用函數式語言能讓你真正快樂起來,而不是整天抓狂。國內現在很暢銷的《黑客與畫家》作者Paul Graham 保羅·格拉漢姆就是忠實的Common Lisp愛好者。他在90年代還寫了兩本Lisp暢銷書,現在都絕版,原價20美元,但亞馬遜上舊書已經炒到了150美元。
扯遠了一點。原問題想預測語言的未來發展方向。我不敢說C++和Java(暗地裡我是希望Java趕緊消失掉的,但畢竟不現實)。Scala的未來發展方向有兩個。第一,Scala會像Haskell和Lisp一樣,成為學術界高智商精英的寵兒。目前已經有在斯坦福、加州伯克利領導下的Scala-nlp項目(ScalaNLP),還有由塔夫茨(Tufts)大學開發的概率編程庫Figaro(p2t2/figaro · GitHub),內部消息稱UMass正打算髮布新一代機器學習庫叫Factory,也是全程用Scala編寫(上一代叫作Mallet,java編寫,以難用著稱)。第二個是由於Scala最為出色的Collection(數據結構)操作,以及極快的速度(谷歌的論文宣稱它在某些情況比Java更快(Loop Recognition in C++/Java/Go/Scala)),Scala將會成為大數據時代下的專用處理語言而進入大公司(目前競爭者有Python,但Python太慢,有R,但R只是部分數學、統計學家的寵兒,還有MatLab、Octave、Mathematica一類的最多做「原型」,不能大規模實踐的軟體)。這樣的潮流已經有著名的Apache Spark項目,Spark作為新一代Hadoop資料庫,由加州伯克利實驗室開發,比Hadoop快100倍,而且其中一個分項目是直接在Spark上跑機器學習演算法。同樣,Twitter,Foursquare公司已經開始大量使用Scala,國內據說豌豆莢也開始採用Scala和Go等語言(這裡是豌豆莢平台架構師鄧草原 在Scala母公司TypeSafe接受的採訪September 9, 2014)
但這些遠不是Scala的未來,基本只能算是庸人自擾。Scala的未來發展起源於Scala-Macro,2010年馬丁·奧德斯基和他的一位PhD學生合作撰寫的論文,將把Lisp最為有名的宏(Macro)帶到Scala世界中。Scala-Macro給予語言能直接操作編譯器的逆天能力,讓類啊,限制啊通通失去意義,真正達到隨心所欲的地步,目前Scala-Macro還在試驗期,新實驗版本叫作Paradise(樂園)。
基於Scala-Macro,斯坦福實驗室Persavie Parallelism Laboratory(Stanford PPL)正在將Scala作為DSL(Domain-Specific Language 領域語言)語言,利用Scala-Macro將Scala與底層硬體語言搭起橋樑。這些硬體曾經是C++甚至是彙編語言的領域。這個技術叫作Language Virtualization(並不是Virtual Machine,而是Virtual Language)。如果懶得閱讀他們的論文,可以看這一個他們製作的幻燈片:http://on-demand.gputechconf.com/gtc/2012/presentations/S0365-Delite-A-Framework-for-Implementing-Heterogeneous-Parallel-DSLs.pdf
最後我們繞回開頭,那位麻省大神所寫的,Scala的真實複雜度。它的複雜Scala確實複雜,內部實現也過於誇張,直接導致編譯時間過長。但這過長的原因不是因為Scala編譯器爛,正是它太強了。不要忘了馬丁·奧德斯基是能一人虐翻Sun公司Java團隊的大神,為了在JVM上實現函數式功能,他也被迫做出了很多妥協。
同樣,Scala的複雜度也源於它對OOP風格的徹底貫徹。Scala的標準庫和Java的標準庫類似,幫程序員簡單的幾乎執行任何事情。大量的重載運算符,依靠類型推斷(雖然飽受Haskell眾的詬病),讓靜態語言Scala幾乎和動態語言類似,但同時又保留了編譯時的類型檢查功能。這些方便的功能極大的縮減了編程者的時間,讓Scala超越Haskell、Lisp等語言成為最具有工程實踐潛力的語言,同時又比另一個Lisp在JVM上的實現Clojure快上不止一點。
Scala也許複雜,但不代表學習困難。相較而言,把Python和Scala放在一起,Scala肯定更難,把VB和Java放在一起,Java肯定更難。最後我只能說,使用何種語言,真的只憑個人興趣愛好,以及與語言是否「合拍」。我有見過用R用的很開心的普林斯頓統計學博士,我也有見過用JavaScript很開心的某國內工程師。
這個問題呢,你要從幾個方面來看
首先不能糾纏於細節,如果我們只看語法的特性,那就太細化了,不利於我們宏觀地把控一些東西
稍微高層次一點,從paradigm這個層次來看
計算機語言大概三個階段嘛
從paradigms出現的時間先後來說,先有fp,再有oop,最後有pp(過程式)
但是呢,從我們熟悉的,得到工業應用的編程語言來看,則是反過來
現有pp得到大規模應用,which就是c了
然後是oop開始出來拯救世界,就是java啦
c是過程式的編程語言,其特點就是不封裝,過程式特別適合機器執行,因為機器執行就是一條一條指令,從上往下執行,就是一個執行的過程,這就是pp,但是不封裝的代碼,隨著加入的人越來越多,會造成一定程度上的混亂,總不能大家都寫一個文件吧?要分文件,那隨著文件的劃分越來越細,越來越多,人們就開始思考,能否封裝一些常見的功能,然後復用這部分代碼?那既然要復用代碼,就要把變化的和不變的分離,那不變的,可以復用的抽象一下就是對象了
所以呢,人們就開始在c的基礎上,加上封裝後的對象,這就是c++,c++同時具有pp和oop的特性,它既可以寫得跟c一樣,也可以寫得跟java一樣
它全部封裝起來也行,不封裝也行,這就是問題,當參與的人數越來越多之後,每個人的代碼風格是不一樣的,那到底要不要封裝呢?這個時候就特別容易吵架,因為人都有獨立的思想,有些人認為應該封裝,有些人認為不封裝,封裝個屁啊,而且一個人寫代碼,總不能老安排別人去盯著吧?雖然理論上要寫測試,要做code review這些,其實很多公司是不做的,因為成本馬上翻番了嘛,而且見效慢,所以很多項目就執行不到位,實際上就沒有執行到位的項目,除非老闆真那麼有錢,那到底封裝不封裝呢?每個人就按照自己的理解搞了,搞到後面就亂了,互相之間看對方代碼不順眼,改來改去,這就是pp+oop時候造成的軟體危機,所以後來搞出了沒有銀彈,人月計劃blablabla,那個時候,項目多數是失敗的,最終就出現了軟體工程,從計算機科學過度到軟體工程,因為paradigm的衝突,表面上看上去似乎很簡單,很容易解決,但是實踐證明,人是無法控制的,只要你給了它犯錯的機會,它幾乎是一定會犯錯,你是技術管理者說一定要封裝成類,你信不信,只要你沒有從源頭上禁止他不封裝,一定會有人在寫完代碼之後,就不給封裝成類,那天天找人盯著也不現實,一個人寫代碼還要再找兩個人伺候著不成?
那軟體工程講究什麼呢?別廢話了,大家都封裝吧,所以有了java,你寫java的時候,封裝是強制的,所有代碼,哪怕是main,都要放到一個class裡面去,class以外沒有東西了,而且一個文件對應一個public class,這都是強制的,不這麼做就是錯的,所以其實java是c++--,這也是java最開始的名字,--是什麼呢?就是把過程式的東西給減掉,變成相對pure的oop,這犧牲是很大的,一個最典型的犧牲就是性能上,早期java是比較慢的,後面在拚命優化。java獲得了巨大成功,大型項目開始變得不那麼糟糕了,項目用java做,是可以做成的,因為如果程序員他不封裝,代碼根本無法編譯,犯錯的空間就小了很多很多,就便於管理了,於是java,軟體工程,oop大發展,現在基本上是處於這個階段,你可以看到,比較pure的c和java,長時間佔據著編程語言的top 2的位置,相對不那麼pure的c++在後期就開始逐步蕭條了
c和java都很成功,pp和oop都有東西搞了,pp後期還通過腳本,比如js等發揚光大了,腳本的搞法跟c很接近,也是不封裝,你就寫吧,反正從上往下執行,所以腳本也有跟c一樣的問題,項目一旦做大了,就亂了,表面上看,動態類型blablabla,其實最大的問題就是不強制封裝
java的oop也有一點點遺留,就是static,原始數據類型這些,也都屬於pp的遺留,所以後期java也在糾正這些遺留,比如通過boxing/autoboxing來解決原始數據類型不是對象的問題,static則通過框架來解決,在框架中填代碼的時候,多數時候是不用static成員的,好了這個問題解決到一定程度之後,人們開始看了,如果pp和oop都是正確的,還有fp呢?那既然可以在pp上加入oop的特性,為什麼我們不嘗試著在oop上加入fp的特性呢?
於是就有了scala,which是oop+fp的語言,說到這裡,就能解釋為什麼說,scala其實是jvm上的c++了,c++是pp+oop,scala則是oop+fp,寫scala的時候,你還是要封裝,但是呢,你可以封裝成對象,也可以封裝成函數,它的top/1st level成員,可以是對象也可以是函數,這個時候就輪到fp出場了,就像是c++把oop加到了c上一樣,scala也把fp的東西加到了java上
實際上scala可以認為是java+fp的東西,實際上,現在fp這一波,scala vs java+clojure有著長期的鬥爭,用scala的,總是直接替換掉java,但是呢,用clojure的,則多數會跟java一起用,因為fp的語言,單純用在一些io啊這些東西上的時候,遠沒有直接上java簡單,如果是oop+fp的scala的話,這個不是問題,但是如果是相對pure一點的lisp/haskell這些的話,那就要上monad了,那這樣做就麻煩,如果跟java配合,讓java處理io,然後再用fp寫邏輯,這就相對合理很多了,實際上fp適合寫組件,其實很多人都沒有意識到,spring裡面的component,強調無狀態的函數,這就是fp的pure function的要求啊,再把exception之類的封裝一下,就是pure function了,書寫fp的條件逐步都滿足了
這個時候有一個pattern:c(pp)-&>c++(pp+oop)-&>java(oop)-&>scala(oop+fp)-&>?
如果這個pattern是對的話,那下一個應該是pure fp,pure fp候選不多,按照後者依賴前者的情況看,估計是一個通過scala造出來的這麼一個fp的runtime或者是engine這樣的一個東西,那目前滿足這個要求的,似乎比較有可能是http://eta-lang.org:
當然也有可能是clojure了,lisp在ai領域也有長期的應用
那如果我們用vert.x來製作這麼一個框架的話,我們會發現一點,寫到後期,全部都是函數,vert.x的組件是verticle,我們寫verticle的時候,其實就是override start方法,同時寫大量的lambda來填入邏輯,當然不用lambda也可以了,用普通的實名函數也行,那既然寫的都是函數,為什麼我們不加上一個fp的runtime,乾脆就用fp語言來寫函數不就行了?這個時候我們再強化一些各種要求,比如純函數,不能出現副作用,verticle之間immutable,不就符合pure fp語言的要求了嘛?那這個時候就可以將這個pattern的最後一個環節給補上:
c(pp) -&> c++(pp+oop) -&> java(oop) -&> scala/kotlin(oop+fp) -&> haskell/lisp(fp)
當然如果你有興趣,可以把這個給補充得更加完整,比如c++那一塊可以加上go和rust,scala還可以繼續擴充成ceylon,latte這些
現在就處於這個階段,就是jvm上我們要把pure fp的這些東西給找回來這麼一個階段,目前vert.x做到的就是把pp的腳本,e.g. js,ruby,groovy和oop的java還有oop+fp的scala, kotlin, ceylon這些都搞到vert.x上去,但是還缺少pure fp的支援,那在前一段解決了各種噁心的callback問題之後,下一個階段的重點就是希望把這些fp語言都給找回來
如果你想知道的是將來的話,我猜測,應該會出現一個jvm上haskell/lisp方言爆發,因為寫pure function的環境現在基本上已經搭建好了,可以開始搞了,缺少的只是一個runtime,如何把vert.x的api包裝成haskell/lisp更為熟悉的樣子,以及maven/gradle compiler,ide支持這些周邊都給做好,差不多就可以搞了,如果你認為靜態是future,那就是haskell(eta?),如果是動態,那就是lisp(clojure)
呼籲有識之士貢獻社區,表只打嘴炮,光說不練都是假把式
jvm上vert.x是對polyglot以及新語言最為友好的框架
目前為止沒有看到其他框架能像vert.x這樣支持如此之多語言,已經官方搞了7個了
非官方的就更多了,所以號召fp志士貢獻社區,為fp的發揚光大貢獻一把力
如果我們的猜測是對的話,haskell(eta)要在jvm這個成熟的eco裡面發揚光大的話
還需要以下東西完成才行:
- IDE Plugin support(eta的support haskforce正在製作中)
- gradle,maven的plugin,也就是compiler,eta官方製作中
- 框架的支援,vert.x的支援,eta官方製作中
vert.x官方網站:Eclipse Vert.x
haskell(eta):Eta Programming Language 在紐約的那個印度人已經準備在eta0.2中加入vert.x support,中國社區有沒有辦法把clojure給搞上去?
Clojure:Clojure clojure的圖標還是中國的太極圖
更多的信息可以關註:白木城全棧
trait stack + contravariant,完美組合!
說個於提問無關的
我覺得scala是個加密語言,正因為它是jvm上的既函數式又面向對象
一個項目一個功能java要寫300行,而同樣的功能scala要寫30行而且與項目無縫連接,但是在濃縮成這30行可能需要java寫450行的時間,唯一的好處就是在一個完全沒有fp、不需要fp的項目里,其他人根本看不懂你的代碼,隨著你的加入,更高效的開發,這個項目可能會逐漸成為scala的主導,然後卻幾乎沒有人能接手。。
而C++就是也有這種嫌疑(先說一下,C++和C是兩種語言,只是C++兼容了C而已),因為在效率相當的情況下C++語法可以和C混著寫,我自己就喜歡malloc和new混著寫,因為個人習慣,導致了代碼很亂。。
亂扯一些,Scala的設計、實現..
主要內容- Scala的大一統
- Scala的自由與代價
- 靜態語言的未來
一、Scala的大一統
1.1 命令式和函數式的統一
首先引用上面回答中的例子:// 命令式
val autoList = for (i &<- 1 to 10) { //do this task 10 times}
// 函數式
val autoList = (1 to 10).map(i =&> /*do this task 10 times*/)
不夠?
Scala支持First class function, first class continuation;Scala標準庫有很多Immutable 數據結構..Scala支持call-by-value, call-by-name, call-by-need..通過ScalaZ可以有寫Haskell的快感;Scala有強大的類型系統;1.2. 函數和對象的統一
請記住這句話:"Every value is an object in Scala and functions are first class values"如何實現的?看例子def inc(x: Int): Int = {
x + 1
}
inc(3)
// 內部實現
// 參數數量為2的trait
trait Function[-T1, +R] extends AnyRef {self =&>
def apply(v1: T1): R
...
}
class Inc extends Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}
val inc = New Inc
inc(3) // 自動調用apply方法
Scala2.8核心定義了Function1, Function2,..貌似直到Function22。為什麼要這樣」繞「?Scala是靜態類型的,這麼做是方便的對每個」函數對象「的原型—類做硬編碼,提高效率
JavaScript也有函數和對象的統一,但它是動態類型的,設計上也比較粗糙,類型系統設計、實現的壓力要小得多。
1.3. ADT和class的統一
SML/Ocaml/Haskell的ADT很強大:datatype tree = Leaf of int | Node of (tree * int * tree);
val t = Node( Node (Leaf 2, 3, Leaf 10), 3, Leaf 1);
fun sumtree (Leaf n) = m
| sumtree (Node(t1, n, t2)) = sumtree t1 + n + sumtree t2;
abstract case class Tree
case class Node(t1: Tree, n: Int, t2: Tree) extends Treecase class Leaf(n: Int) extends Tree
val tree = Node( Node (Leaf (2), 3, Leaf (10)), 3, Leaf(1))
def sumTree(t: Tree): Int = t match {
case Leaf(n) =&> n
case Node(t1, n, t2) =&> sumTree(t1) + n + sumTree(t2)
}
case class的設計實現除了不清爽,還有很多問題的,比如和模式匹配結合發生的錯誤不好排查。。比如。。
1.4 模塊和對象的統一
"Object are modules"object module A {
class A
}
class ModuleB {
class A
}
def main(args: Array[String]) = {
val moduleB = new ModuelB
val a1 = new moduleA.A
val a2 = new moduleB.A
println(a1.getClass == a1.getClass)
println(a1.getClass == a2.getClass)
}
二、Scala的自由與代價
2.1 從for comprehension說起
」大一統「帶來諸多自由:比如強大的控制流,模式匹配、for coprehension等等。
某種程度上,for comprehension比List comprehension更強大:for (x &< c1; y &< c2; z &<- c3) {...}
// 內部實現
c1.foreach(x =&> c2.foreach(y =&> c3.foreach(z =&> {...})))
for(x &<- c1; y &<- c2; z &<- c3) yield {...}
// 內部實現
c1.flatMap(x =&> c2.flatMap(y =&> c3.map(z =&> {...})))
大家想想為什麼低效....
2.2 多態——重載、泛型、繼承
待續....
,,
三、靜態語言的未來PS :我覺得C#和Scala比較有可比性。。單純就是複雜而已,別想太多。
另外,我是土鱉碼農,不懂預測。
預測還是找分析師吧。作為一個看完過scala和c++的language reference的人,我只能感嘆說,終於有語言比c++複雜了。但是複雜的方向卻不一樣。c++複雜是為了想把若干個編程模型揉在一起,起碼你想學門檻還是很低的。scala的複雜卻在於你要是搞不定《計算模型》和《類型系統》就別想完全學會scala,門檻更上一層樓……
scala的確入門時候不好讀。說件好玩的事,我和同學常常在學校餐廳吃完飯碰到老馬時,跟在老馬背後假裝討論說scala sucks。。。覺得scala複雜的,真心幫你們噴過了,匿了。
題主是想說Scala和C++一樣難學么?個人也覺得很難學。至少學了兩年左右也沒熟練掌控的感覺。為什麼這麼難學?個人覺得是因為Scala的門檻高。簡單來看是FP和OOP的整合,但是對於從Java過來認為自己的OOP應該沒問題的人很快就會被打擊,covariant是什麼?可以吃么?OOP沒有完全掌握是一方面,FP思想沒有也是很大的問題。所以第二年學Scala的時候有意識的學了Haskell一個月。作為一個沒有FP經驗的人很難理解foldLeft是什麼,為什麼要用foldLeft。學了Haskell之後,雖然沒有學Monad等更高級的東西,但是對於常見的集合上的常見操作已經可以理解了。承認自己OOP沒學好其實沒那麼難,不過學習那些缺失的東西很費勁。理解trait還可以有ruby的mixin可以參考,雖然很不一樣;理解類型的協變前後花了我多次,為什麼不可變的LIst是可協變的,但是可變的卻不行。FunctionX的輸入參數和輸出參數的協變是為什麼?老實說,如果你能夠寫出Scala Collection,我覺得已經是熟練掌握了。實際看別人代碼的時候,還有一個問題,就是那些隱式參數,方法。老實說這東西很好,對寫代碼的人來說,但是讀代碼超級費勁。sbt的那個%個人就找了半天,最後感嘆誰要是全部import foo.bar._全導入或者喜歡相對路徑導入,我和他急。換句話說,語法糖太折騰人了。不過,如果你不在意你每天寫東西只不過是語言的10%的功能的話,那麼用用命令式語法寫Scala也是可以的。被別人寫FP的人輕視時,請不要在意。寫代碼何苦互相鄙視呢。
Scala 難不難?這得看你怎麼用。
一個語言的功能性和可讀性之間存在一定的trade off。一個語言越強大,能解決的問題就越多,可是也就越容易濫用。過去我們有goto,強大無比,想跳哪裡跳哪裡,比什麼for什麼while不知道強到哪裡去了。但這麼寫出來的程序最後就沒法讀。最後大家忍無可忍,直接把goto從語言里刪掉了。C/C++有指針,強大無比,指到哪裡讀哪裡(或者寫哪裡),最後也是濫用的一塌糊塗,所以Java就沒有指針了。
Scala給了你很多很強大的東西,所以它像C++,被濫用的潛力也像C++。比如上面有人舉的例子:val list = for (i &<- 1 to 10) { /* do this 10 times */ }
這個寫法就是有問題的。因為Scala里的for循環只返回Unit。它和下面這句
val list = (1 to 10) map { x =&> /* do this 10 times */ }
是不等價的。
我個人的做法,就是需要返回一個新List的時候,就寫map。不需要返回新List 的時候,就寫for。這樣功能就精確了,可讀性就增強了。
但是也有的時候,用一個for yield效果是最好的。比如做排列組合val list = for (i &<- 1 to 10; j &<- 1 to 10; l &<- 1 to 10) yield (i, j, l)
如果你非要用map來寫就得寫成這樣:
val list = (1 to 10) flatMap {
i =&> (1 to 10) flatMap {
j =&> (1 to 10) map {
(i, j, _)
}
}
}
非常不直觀。
所以Scala這個語言,強大是很強大,但是要用好不容易。這不是說什麼函數式理解困難,那個真不困難。但是理解不等於全部都得用上。如果你的程序用到了Scala的所有功能,那肯定是複雜的一塌糊塗。
有抬扛的來了就得說,Scala語法糖多啊,增強可讀性啊。這個我建議你你去讀True Scala complexity 等你真想自己去創建語法糖的時候,問題就來了。
[T]he problem is that each feature has substantial depth, intersecting in numerous ways that are riddled with nuances, limitations, exceptions and rules to remember.
Yang Zhang舉了很多例子。implicit conversion看起來是個好東西,實際用起來困難重重。解決一個問題看似有很多辦法,實際上每一個辦法都會讓你遇上一些解決不了的corner case。這才是最要命的。
I also don』t buy that the individual concepts in Scala are
what』s stumping smart programmers. Traits, pattern matching, closures,
higher kinds, typeclasses, functors, monads, etc. might induce a
paradigm shift and require some digestion time if you』ve only ever
learned Java, but these aren』t inherently over-complex. I』m saying Scala—that particular recipe for mixing all these concepts—is complex.
Cpp在當時統一了面向過程和面向對象編程,Scala則統一了函數式編程和面向對象編程。
不說難度和複雜度,從設計的思路講,我覺得和c++類似的是給程序員靈活的擴展性和多範式的支持(雖然實現的方式不太一樣),提供兩點:
大家知道 java 的數組協變是一個令人遺憾的設計,這是因為早期木有泛型,又想做應該由泛型實現的那些功能。而完全去掉協變又讓類型系統變成了弱雞,在特定情況下協變是沒有問題又能提高抽象度的。
那 scala 為 java 填坑的方式是,讓類型的協變、逆變是可選的,同時還有針對的檢查,原則是 LSP (Liskov substitution principle) 。以數組 (Array)協變為例,那個讓數組協變出問題的操作本質是一個 update(index: Int, wrongItem: T)。scala 的解決方法是,根據 lsp,輸入只能是逆變的(-T),輸出只能是協變的(+T)。再比如 immutable 的 List,它沒有 Array 的那個問題,自然可以做成 List[+T]。當然還有避開檢查的方法,也就是定義 U &>: T 讓類型推導去找共同的 supertype。還有就是多範式,"everything is an object and functions are first class citizens" 具體的細節就不多說了。
另外 symbolic identifier (比如你可以定義自己的 &>&>= 等函數),apply 之類的設計也給了你自己造語法糖的能力。
所以 scala 給了程序員很多的靈活性,多範式的支持,這當然就會帶來語言的複雜度,讓一個沒有在學 scala 之前學過這些範式、對類型系統一點都不懂、認為語法糖毫無價值的程序員比較難入門。這也是比較像 c++ 的,多少人跪在 template 面前了。
不過我覺得區別在於 c++ 的靈活性: "這都要自己造?",scala 的靈活性: "這都可以自己造?"
其實我不會編程,上面都是我瞎說的。終於看完以上大大們的全部對話,我還是去搞html去吧
scala本身只是語法糖多,只看錶象的人自然就會覺得複雜,其實隨著學習的深入你會發現不然。scala 里,包括中綴運算(1.+(2))加法只是整數對象的一個函數,for,from, to等等,都是語法糖,可以理解為嵌入在Scala里的DSL,之前在畢業設計里見到導師如此定義一個向量,from v1 to v2 along v3,這樣的性質都是本身scala靈活的結果,Scala自己加糖這一特點比Haskell還要突出,在實踐中可以極大地接近自然語言。
最近也看過關於各種Langauge Reference的,我對此持保留態度。Scala在設計上是精簡的,遠遠精簡於C++與Java,語法總結10頁都不到。Scala本身應該是一個比較簡單的語言,只是雜糅了兩OO與FP所以顯得複雜,如果大家有OO跟FP分別的基礎再來審視Scala其實會發現Scala並不是想像的那麼複雜,並且恰恰相反。所以最好只評論自己長期以來每天用的工具跟語言,因為作為一個研究Haskell的人見太多對Haskell跟GHC的錯誤評價與不公對待。一面說純函數式的Haskell慢一面解釋運行著Python跟Ruby。
筆者的理解Scala跟C++在各個方面都沒什麼太大關係,Clojure是JVM上的Lisp說得過去,C#是.Net的Java也沒人跟你爭論,但是Scala是JVM上的C++就不太正確了。
回應一下Scala編譯慢的問題,首先相信大公司百萬行的代碼並不是用PC編譯的。另外,更多人沒有考慮到實現同樣的東西Scala會比Java少多少代碼,當然這個需要根據實際項目而定,不過我覺得少1~10倍之前都是可能的。那程序員是想花比較少的時間寫Scala代碼然後花1天編譯,還是想多花一倍的時間再多寫一倍的代碼然後花半天時間編譯?
回應Haskell有的東西Scala里都有。我懷疑說這種話的人對於Haskell與Scala有多少了解。首先GHC現在已經有100左右的Extension了,Scala看了一段時間,恕我Scala學得不多,不過Haskell里的任意Rank類型,Kind polymorphism,Type family在Scala里我都沒看到。更加不知道怎麼在Scala里做dependent type。Scala是一直向Haskell學習的一個語言,說Haskell有的Scala都有這種話太過不負責任與輕浮。現在Scala還年輕,不說別的,單說GHCi的功能可以甩Scala解釋器幾條街了,且不說Haskell凝聚了多少大師跟PhD的結晶,單說Haskell進化的包袱就比Scala少很多,很多特性GHC說不要就不要,很多庫說deprecate就deprecate,Haskell的設計者也不在乎Haskell能否遊行,Scala想追上這速度進化的Haskell短期內是不太可能的,Scala革命未成仍需努力。
C++已經根深樹大,相信未來還需要使用他,但是一直羨慕GC、FP的特性,於是有了很多庫來干這些事情,但是增加了學習的陡峭程度,性能上也有無法取代的優勢,所以相信很長時間都不會倒下。
講個笑話:
cpp寫完一個月忘記寫啥了,看了2天理順了
scala寫完一個月忘記寫啥了,看了1個星期理順了
寫scala兩年多,並沒有感覺像c++,scala很像pipeline。面向函數。但比純函數要靈活
scala感覺有點過度設計了,太靈活複雜了,跟goLang完全相反的設計思路,如果有能力精通,會靈活使用,那麼爽翻天,開發效率相當高,再加上高並發,調用java龐大生態,前途無量啊
總有人喜歡裝逼,scala的複雜度與生產力完全不成正比
不清楚scala的內部實現,類lisp語言?反正,python和node.js的生命力都是靠C++, C++也有引入高級語言特性作為新標準的趨勢。個人覺得JVM的特點就是跨平台和虛擬化,Java只會是日薄西山,Scala也就只有spark一個拿得出手的項目。概率編程,與C++結合得更好的,明顯是lua,還輪不到scala。
推薦閱讀:
※C++ 中 cout 是個對象,包含頭文件後可以直接用,那麼它是在哪裡定義的呢?
※學習c++多線程編程主要用pthread還是c++11中的thread類?
※Boost.Asio成為C++標準庫一部分的機會大嗎?
※為什麼C/C++的預處理指令#include不自動讓所包含的文件只包含一次?
※gcc編譯大文件非常慢,是有什麼限制嗎?
TAG:Scala | Java虛擬機JVM | C | 函數式編程 |