Scala的设计目标——Martin Odersky访谈(二)
Scala是一種新興的通用用途、類(lèi)型安全的Java平臺(tái)語(yǔ)言,結(jié)合了面向?qū)ο蠛秃瘮?shù)式編程。它是洛桑聯(lián)邦理工大學(xué)教授Martin Odersky的心血結(jié)晶。本訪談系列由多部分組成,由Artima網(wǎng)站的Frank Sommers和Bill Venners向Martin Odersky討教Scala。在第一部分Scala起源中(點(diǎn)擊查看《Scala起源》中文翻譯),Odersky講述了導(dǎo)致Scala誕生的那些歷史。在本期中,Odersky討論Scala設(shè)計(jì)中的妥協(xié)、目標(biāo)、創(chuàng)新和優(yōu)勢(shì)。
\\Scala的設(shè)計(jì)妥協(xié)
\\Frank Sommers: 你曾提及,你想要?jiǎng)?chuàng)造一門(mén)語(yǔ)言,存活于Java生態(tài)系統(tǒng),整合Java基礎(chǔ)設(shè)施。為了達(dá)到上述目標(biāo),你讓Scala做了哪些妥協(xié)來(lái)兼容Java平臺(tái)呢?
\\\Martin Odersky: 我們運(yùn)氣很好,不需要做太多妥協(xié)。說(shuō)起來(lái),我們做的妥協(xié)總體上還指不定是好事還是壞事呢。我們不得不做出的妥協(xié)之一:接受Java里的靜態(tài)重載模型。我們可能曾經(jīng)想要試驗(yàn)?zāi)承└みM(jìn)的做法,比如多重方法(multi-methods),然而,在當(dāng)時(shí),多重方法的設(shè)計(jì)尚且無(wú)人充分探索過(guò)。可能時(shí)至今日恐怕也還沒(méi)充分探索,甚至我都不能完全肯定它自始自終算不算個(gè)好東西。它曾經(jīng)具備無(wú)限可能待人探索。但我們沒(méi)去探索,因?yàn)槲覀円3旨嫒軯ava。
\\Scala為人詬病的第二件妥協(xié)是同時(shí)支持特質(zhì)(trait)和類(lèi)。如果能設(shè)計(jì)得更干凈些的話(huà),完全可以只支持特質(zhì)。前人已經(jīng)做過(guò)這種干凈的設(shè)計(jì)。但我們沒(méi)有這樣做,因?yàn)槲覀兿氡WCScala與Java能雙向互操作。 我們希望有辦法讓Java代碼可以很容易地調(diào)用Scala代碼。然而特質(zhì)無(wú)法自然的映射到Java中,因?yàn)镴ava中沒(méi)有特質(zhì)。因此,我們照搬了Java 原有的類(lèi)設(shè)計(jì),因?yàn)槲覀冃枰猄cala到Java的映射,以便讓我們更容易支持雙向互操作。
\\妥協(xié)之三,說(shuō)到底更像是庫(kù)的問(wèn)題而非語(yǔ)言問(wèn)題,即,我們想要去掉null。null是很多很多錯(cuò)誤的罪魁禍?zhǔn)住N覀儽緛?lái)可以設(shè)計(jì)一門(mén)語(yǔ)言,壓根不允許null成為任何類(lèi)型的候選值,因?yàn)镾cala的Option類(lèi)型就能完美替代null。但是顯然很多Java庫(kù)會(huì)返回null,我們總得有個(gè)辦法來(lái)處理這些返回值嘛。
\\\為設(shè)計(jì)Scala而選擇戰(zhàn)場(chǎng)
\\Bill Venners: 為了讓Scala更容易被Java程序員所接受,你往Scala中加入了哪些東西?例如,有沒(méi)有類(lèi)似Java選用花括號(hào)語(yǔ)法的決策(Java的花括號(hào)相比別的分塊語(yǔ)法,能讓C和C++程序員感覺(jué)更溫馨)?
\\\Martin Odersky: 我們沒(méi)有專(zhuān)門(mén)為市場(chǎng)因素而進(jìn)行設(shè)計(jì),不過(guò)我們確實(shí)想避免只為昭顯另類(lèi)而設(shè)計(jì)讓程序員覺(jué)得奇怪的東西。舉例來(lái)說(shuō),我覺(jué)得花括號(hào)用作分隔符相當(dāng)靠譜,于是我就選擇了花括號(hào)。我們本來(lái)也可以任性一點(diǎn),選用begin、end或者其他什么符號(hào),但我覺(jué)得,那并不見(jiàn)得會(huì)更好。
\\有一項(xiàng)Scala特性,最初設(shè)計(jì)得很純粹,但我們后來(lái)就改掉了。開(kāi)始時(shí),我們用冒號(hào)等號(hào)表示賦值(就類(lèi)似Pascal、MODULA和Ada那樣)而用等號(hào)表示等于。許多編程理論家會(huì)認(rèn)為這是正確的做事方式。賦值和等于含義不同,所以你就應(yīng)該用不同的符號(hào)表示賦值。但是后來(lái)我找了些Java前程序員來(lái)做測(cè)試。給我的反饋是“哦,這個(gè)語(yǔ)言看起來(lái)挺有趣呀。但為什么你要用冒號(hào)等號(hào)?這是什么啊?”我解釋說(shuō),它類(lèi)似Pascal。他們說(shuō),“現(xiàn)在我明白了,但我還是不明白你為什么一定得這樣做。”然后我意識(shí)到,這不是我們非得堅(jiān)持的東西。我們不想說(shuō): “我們的語(yǔ)言比別人好,是因?yàn)槲覀兊馁x值操作用了冒號(hào)等號(hào),而不用等號(hào)。”這完全不是重點(diǎn),程序員兩種方式都可以適應(yīng)嘛。因此,我們決定不在這些小地方挑戰(zhàn)慣例,畢竟我們還想在其他方面與眾不同呢。
\\\Bill Venners: 我以前聽(tīng)你講過(guò)一句俗語(yǔ)“選擇戰(zhàn)場(chǎng)”,今天你沒(méi)講這句話(huà)。基本上,你認(rèn)為冒號(hào)等號(hào)并不那么重要,還有其他東西才是你在乎并為之抗?fàn)幍摹D敲从心男┲匾氖虑槟?#xff1f;你想以什么方式來(lái)說(shuō)服人們,改變他們的思考方式和編程方式?
\\\Martin Odersky: 我們關(guān)心的第一件事,就是把函數(shù)式編程和面向?qū)ο缶幊瘫M可能干凈的集成在一起。我們希望讓函數(shù)成為一等公民,要有函數(shù)字面量、閉包。我們也希望擁有函數(shù)式編程的其他性質(zhì),比如類(lèi)型、泛型、模式匹配。我們還希望整合函數(shù)式和面向?qū)ο髢刹糠謺r(shí),實(shí)現(xiàn)的方式能比在以前Pizza語(yǔ)言中更干凈。這正是我們一開(kāi)始就深度關(guān)注的事情。
\\后來(lái),我們才發(fā)現(xiàn),達(dá)成這個(gè)目標(biāo)并不難,因?yàn)楹瘮?shù)式語(yǔ)言都有一組固定的功能。這些功能已經(jīng)研究和證明得很透徹了,所以問(wèn)題只剩下如何以最佳方式整合進(jìn)面向?qū)ο缶幊讨小T赑izza中,我們嘗試得很笨拙,而在Scala中,我認(rèn)為我們達(dá)成了兩者之間更為平滑的整合。但后來(lái)我們發(fā)現(xiàn),在面向?qū)ο蠓矫嫒杂泻芏嗍虑橛写_(kāi)發(fā)。面向?qū)ο缶幊?#xff08;至少在同靜態(tài)類(lèi)型系統(tǒng)扔到一起時(shí))仍然是相當(dāng)神秘的領(lǐng)域。有一些成果我們見(jiàn)過(guò)、用過(guò),然而我們發(fā)現(xiàn)幾乎所有的語(yǔ)言都做了很多妥協(xié)。
\\因此,開(kāi)發(fā)Scala時(shí),我們開(kāi)始探索該如何才能讓對(duì)象兼容特質(zhì)混入(mixin composition),如何才能抽象出自我類(lèi)型(self type),如何才能使用抽象類(lèi)型成員(abstract type member),以及,如何才能把這一切搞到一起。至Scala誕生時(shí),已經(jīng)有幾種學(xué)術(shù)語(yǔ)言,能用特殊方式、在某幾個(gè)方面處理這些問(wèn)題,但尚未有所謂主流語(yǔ)言能全頻段覆蓋般讓上述所有功能都運(yùn)轉(zhuǎn)起來(lái)。最后事實(shí)證明,Scala的主要?jiǎng)?chuàng)新在面向?qū)ο笠幻?#xff0c;而這才是我們真正關(guān)心的。
\\\Scala的面向?qū)ο髣?chuàng)新
\\Bill Venners: 我想弄清楚,你能不能給出一張具體列表,涵蓋你所認(rèn)為的Scala面向?qū)ο髣?chuàng)新之處。
\\\Martin Odersky: 首先,我們想擁有一門(mén)純粹的面向?qū)ο笳Z(yǔ)言,其中的每個(gè)值都是對(duì)象,每個(gè)操作都是方法調(diào)用,每個(gè)變量都是對(duì)象成員。所以盡管我們并不喜歡靜態(tài)成員,但我們?nèi)匀恍枰心承〇|西來(lái)取代它們。為此我們創(chuàng)建單例對(duì)象的概念。即便如此,單例仍然是全局結(jié)構(gòu)。所以,我們面臨的挑戰(zhàn),就是如何盡可能少的使用單例,因?yàn)橹灰o定一個(gè)全局結(jié)構(gòu),就無(wú)法對(duì)它進(jìn)行修改,也無(wú)法創(chuàng)建它的新實(shí)例。對(duì)它測(cè)試非常難;對(duì)它修改,無(wú)論以任何方式,也非常難。
\\因此,我們面臨的挑戰(zhàn)正是如何使復(fù)雜組件得以構(gòu)建,卻不引用靜態(tài)成員或者全局變量。特別是我們還得處理組件間的遞歸依賴(lài)。有兩個(gè)組件,A和B。A使用B、B也使用A。如何讓他們發(fā)現(xiàn)對(duì)方,并讓他們一起運(yùn)作? 我們做的第一件事是混入概念。Java只能繼承單一父類(lèi)。雖然可以繼承一打抽象接口,但卻不能包含代碼。相比之下,Scala既有類(lèi)又有特質(zhì),而特質(zhì)可以包含帶實(shí)現(xiàn)的方法(即函數(shù)體),還可以包含字段。不像Java只有類(lèi)才能實(shí)現(xiàn)接口,Scala有混入功能,可以混合一個(gè)類(lèi)和多個(gè)特質(zhì)的定義內(nèi)容。要做到這一點(diǎn)、準(zhǔn)確找到最終編譯出的類(lèi)有哪些定義項(xiàng),實(shí)現(xiàn)細(xì)節(jié)需要一種叫做線性化的手法。
\\因此,我們必須為Scala定義出線性化排序算法。這件事倒是搞定了,但隨后的問(wèn)題是,一處混入代碼如何找到其他混入代碼。要是它需要其他一些代碼提供的服務(wù)。它該怎么指定服務(wù)方?要做到這一點(diǎn),標(biāo)準(zhǔn)的面向?qū)ο蠓绞揭褂贸橄蟪蓡T。若只是抽象方法的話(huà),這么做效果倒是不錯(cuò),但我們還得處理變量呢。混入代碼之間如何相互找到對(duì)方字段?更重要的是,我們不得不處理類(lèi)型。因?yàn)槲覀円婚_(kāi)始就支持類(lèi)型嵌套。類(lèi)型嵌套有點(diǎn)類(lèi)似內(nèi)部類(lèi),我覺(jué)得這個(gè)特性非常重要。你使用混入時(shí),某個(gè)特質(zhì)如何找出其他所有特質(zhì)中定義的內(nèi)部類(lèi)并使用這些內(nèi)部類(lèi)呢?我們經(jīng)過(guò)摸索(這個(gè)特性主要靠摸索而非設(shè)計(jì)),發(fā)現(xiàn)只要在自我類(lèi)型上增加一層抽象就可以做到這一點(diǎn)。
\\那么,“自我類(lèi)型之上的抽象”到底是什么?比如,類(lèi)中出現(xiàn)的this引用到底是什么類(lèi)型?你會(huì)說(shuō),“哦,就是這個(gè)類(lèi)的類(lèi)型吧。”大多數(shù)人可能都會(huì)這樣說(shuō)。實(shí)際上,并不存在什么壓倒性的理由能讓這一論斷必然成立。this的類(lèi)型也可以是別的東西。唯一成立的邊界條件是在你真正創(chuàng)建某個(gè)類(lèi)的實(shí)例時(shí),你新創(chuàng)建的對(duì)象確實(shí)滿(mǎn)足“this的類(lèi)型就是類(lèi)本身的類(lèi)型”這一論斷。而反例恰恰在你處理抽象方法時(shí)就會(huì)碰到。你可以在類(lèi)中定義抽象方法,而不需要實(shí)現(xiàn)。當(dāng)你創(chuàng)建類(lèi)實(shí)例時(shí),編譯器會(huì)檢查每個(gè)抽象方法是否都有實(shí)現(xiàn)。所以,自我類(lèi)型之上的抽象,其實(shí)只是這種情況推廣開(kāi)來(lái),讓你可以處理抽象字段,以及更重要的抽象類(lèi)型。
\\自我類(lèi)型之上的抽象這一技術(shù),非常值得探索。我們通過(guò)探索,在理論層面學(xué)習(xí)它們,建立了一套演算法。我們把這套理論命名為nu-object演算法(即νObj,第一個(gè)字母是希臘字母ν),發(fā)表于歐洲面向?qū)ο缶幊虝?huì)議2003(European Conference on Object-Oriented Programming in 2003,ECOOP 2003)。這個(gè)概念我們最初發(fā)現(xiàn)時(shí)只是一種技術(shù)手法,能簡(jiǎn)化演算法中的某些處理過(guò)程。但沒(méi)過(guò)多久,我們想起,它可能有助于Scala語(yǔ)言中的某些東西。對(duì)于它有什么用,當(dāng)時(shí)我們所知甚少,但我們決定把它加入Scala試試。由于這是我們自己的演算法,我們就做了決定。沒(méi)過(guò)多久,我們發(fā)現(xiàn)它達(dá)成了一條基本原則:讓每個(gè)特質(zhì)可以聲明,自己需要哪些成員從其他特質(zhì)混入。這正是現(xiàn)在Spring等工具試圖解決的問(wèn)題,即所謂的依賴(lài)注入。但依賴(lài)注入只支持注入字段(也許還能注入方法),而我們還可以支持注入類(lèi)型。而且,Scala能夠靜態(tài)注入,而不需要在運(yùn)行時(shí)注入。再者,Scala還能為注入的內(nèi)部類(lèi)型保證類(lèi)型安全。因此,在某種意義上,我們能做的事情要比目前Spring等工具所做的更進(jìn)一步。
\\\它對(duì)我有什么用?
\\Bill Venners: 在本系列訪談的稍后幾期,我們?cè)俳又勔蕾?lài)注入。現(xiàn)在先不提依賴(lài)注入,本期訪談我們還有一個(gè)問(wèn)題:為什么?你剛才“抽象”地談?wù)摿俗晕翌?lèi)型。你先前提及,要選擇的兩大戰(zhàn)場(chǎng)是:混合函數(shù)式和面向?qū)ο缶幊?#xff0c;以及面向?qū)ο蟮膭?chuàng)新。如果我的工作就是用Java編程,在我實(shí)際在做的工作中,這些東西怎么能幫助我呢?我會(huì)得到什么樣的具體好處?
\\\Martin Odersky: 其一,我們所面臨的挑戰(zhàn)是,我們既想要函數(shù)式,又想要面向?qū)ο蟆N覀兒茉缇徒⒘艘粋€(gè)觀念,我們認(rèn)為,不可變對(duì)象未來(lái)會(huì)非常非常重要。果然現(xiàn)在每個(gè)人都在談?wù)摬豢勺儗?duì)象,因?yàn)槿藗冋J(rèn)為它們是解決多核計(jì)算機(jī)造成的并發(fā)問(wèn)題的關(guān)鍵部分。大家都說(shuō),不管你做什么,你都需要試著盡量地讓你的代碼使用不可變對(duì)象。在 Scala中,我們很早就做到了這一點(diǎn)。五、六年前,我們就開(kāi)始努力思考不可變對(duì)象。實(shí)際上,在Scala發(fā)明以前,只要有大量的面向?qū)ο笞侄?#xff0c;就必然表示可變對(duì)象。對(duì)于他們來(lái)說(shuō),可變狀態(tài)和對(duì)象是一碼事:可變狀態(tài)就是對(duì)象的本質(zhì)組成部分。我們必須從對(duì)象的本質(zhì)里除去可變性的概念。我們一定要做些事才能實(shí)現(xiàn)這一目標(biāo)。
\\例如,對(duì)于一個(gè)標(biāo)準(zhǔn)的Java對(duì)象,你可以創(chuàng)建字段,通常是可變字段。接著你需要定義構(gòu)造函數(shù),接受參數(shù)、給字段賦值。這就是根植于每個(gè)Java類(lèi)中的可變性概念。現(xiàn)在,Java有final字段。這類(lèi)字段不被視為可變字段,因?yàn)樗鼈冎辉跇?gòu)造函數(shù)中賦值一次。但是,你仍然可以看到對(duì)它們賦值的運(yùn)算符。我們希望有一個(gè)更干凈的概念,不需要構(gòu)造函數(shù)、賦值等東西。
\\我們最終在Scala中直接實(shí)現(xiàn)了帶參數(shù)的類(lèi)。你只要在類(lèi)名后面寫(xiě)上參數(shù)列表,里面就是類(lèi)的參數(shù)。額外的可變字段、以及對(duì)字段賦值的構(gòu)造函數(shù),這些概念在Scala中壓根就不存在。這實(shí)際上也引發(fā)了一些我們需要解決的問(wèn)題。其中之一是,如果你想要有多個(gè)構(gòu)造函數(shù),怎么辦?我們必須為此定義某種語(yǔ)法和規(guī)則。除了主要構(gòu)造函數(shù)之外,你還可以額外定義輔助構(gòu)造函數(shù)。另一個(gè)問(wèn)題是,如果稍后你想讓參數(shù)作為字段以供訪問(wèn),怎么辦?你需不需要另外創(chuàng)建一個(gè)字段并給它賦值?或者能否把參數(shù)傳到類(lèi)中,就立刻成為大家都能訪問(wèn)的可用字段?所以我們必須為此創(chuàng)造語(yǔ)法。我相信,我們創(chuàng)造的語(yǔ)法也算是前所未有。一旦你發(fā)明了參數(shù)字段的語(yǔ)法,你就不得不考慮重載它們的規(guī)則,所以我們必須解決這些問(wèn)題。最終,為了讓這些功能運(yùn)轉(zhuǎn),我們?cè)诿嫦驅(qū)ο箢I(lǐng)域發(fā)展出相當(dāng)多的嶄新基石。
\\\Bill Venners: 這對(duì)我們Java程序員有什么益處?
\\\Martin Odersky: 對(duì)你的好處是,你可以用更簡(jiǎn)潔的語(yǔ)法編寫(xiě)類(lèi)。這種語(yǔ)法看起來(lái)與你以前用的可變狀態(tài)截然不同。在Scala中編寫(xiě)類(lèi)時(shí)通常更簡(jiǎn)單、簡(jiǎn)潔,而且不可變對(duì)象的處理也同樣大大簡(jiǎn)化了。因?yàn)镾cala是不可變編程的真正專(zhuān)業(yè)戶(hù)。在Scala中,你仍然可以像Java一樣處理可變對(duì)象,甚至可變對(duì)象也會(huì)比Java簡(jiǎn)潔,盡管Scala真正的亮點(diǎn)是在不可變對(duì)象上。這比你用Java時(shí)更自然、更簡(jiǎn)潔。
\\\查看英文原文:The Goals of Scala's Design
\\感謝魏星對(duì)本文的審校。
\\給InfoQ中文站投稿或者參與內(nèi)容翻譯工作,請(qǐng)郵件至editors@cn.infoq.com。也歡迎大家通過(guò)新浪微博(@InfoQ,@丁曉昀),微信(微信號(hào):InfoQChina)關(guān)注我們,并與我們的編輯和其他讀者朋友交流(歡迎加入InfoQ讀者交流群)。
總結(jié)
以上是生活随笔為你收集整理的Scala的设计目标——Martin Odersky访谈(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 明尼苏达推荐系统导论(第一课 欢迎来到R
- 下一篇: java原理—反射机制