艾均 蘇湛
摘要:函數(shù)式編程語(yǔ)言及函數(shù)特性在工業(yè)界逐漸流行,函數(shù)式編程語(yǔ)言教學(xué)具有重要的理論與現(xiàn)實(shí)意義。以提高教學(xué)質(zhì)量為目的,通過(guò)仔細(xì)分析默認(rèn)不可變、高階函數(shù)、模式匹配、數(shù)據(jù)與函數(shù)解耦等編程語(yǔ)言特征,采用討論對(duì)比與實(shí)踐方法,研究函數(shù)式編程語(yǔ)言教學(xué)方法,對(duì)學(xué)生編程思維進(jìn)行訓(xùn)練,并對(duì)未來(lái)編程技術(shù)發(fā)展趨勢(shì)進(jìn)行分析。采用實(shí)例編程教學(xué)與不同語(yǔ)言對(duì)比相結(jié)合的方法,使《函數(shù)式編程語(yǔ)言》教學(xué)質(zhì)量得到有效提升。
關(guān)鍵詞:F#;函數(shù)式編程;編程實(shí)踐;教學(xué)特點(diǎn);教學(xué)內(nèi)容組織
DOI:10.11907/ejdk.191325開(kāi)放科學(xué)(資源服務(wù))標(biāo)識(shí)碼(OSID):
中圖分類(lèi)號(hào):G433文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1672-7800(2019)010-0201-03
0引言
函數(shù)式編程范式,解耦了數(shù)據(jù)和處理數(shù)據(jù)的函數(shù),將數(shù)據(jù)在不同處理函數(shù)之間的流動(dòng)過(guò)程展現(xiàn)給用戶(hù),使用戶(hù)能夠?qū)ψ约旱臉I(yè)務(wù)邏輯始終保持專(zhuān)注,避免了命令式編程不斷向計(jì)算機(jī)描述如何完成某項(xiàng)工作的瑣碎步驟,也避免了面向?qū)ο蟪橄筮^(guò)程中數(shù)據(jù)和方法的耦合以及類(lèi)繼承的復(fù)雜性。函數(shù)式編程與命令式編程范式相比,具有數(shù)學(xué)上的優(yōu)雅性。因?yàn)橛?jì)算機(jī)編程過(guò)程處理的對(duì)象是數(shù)據(jù),函數(shù)才是處理不同有組織數(shù)據(jù)的核心。在初步掌握數(shù)據(jù)結(jié)構(gòu)后,學(xué)生可通過(guò)函數(shù)式編程語(yǔ)言了解利用計(jì)算機(jī)解決計(jì)算問(wèn)題的思想,進(jìn)而將這種思想應(yīng)用于計(jì)算機(jī)編程實(shí)踐。
F#是微軟.NET開(kāi)發(fā)平臺(tái)的一門(mén)編程語(yǔ)言,由微軟倫敦研究院研發(fā),目前是全平臺(tái)的開(kāi)源編程語(yǔ)言,技術(shù)演進(jìn)完全由社區(qū)驅(qū)動(dòng),其最鮮明的特點(diǎn)是對(duì)函數(shù)式編程范式(FP,F(xiàn)unctionalProgramming)的引入,同時(shí)F#對(duì)面向?qū)ο螅∣OP)編程的支持也同樣出色。使用F#語(yǔ)言,開(kāi)發(fā)人員可以自由選擇函數(shù)式編程或面向?qū)ο缶幊虒?shí)現(xiàn)目標(biāo)。此外,F(xiàn)#還可與NET平臺(tái)上的C#、VB等編程語(yǔ)言緊密結(jié)合,通過(guò)相互調(diào)用完成歷史代碼復(fù)用,實(shí)現(xiàn)更大規(guī)模的項(xiàng)目。
函數(shù)式編程思想與傳統(tǒng)的C語(yǔ)言、較新的Python、Go語(yǔ)言不同,具有一定新穎性,帶來(lái)解決問(wèn)題的新思路,尤其是對(duì)于并行與異步編程,相比Java等編程語(yǔ)言,更加利于學(xué)生理解問(wèn)題的關(guān)鍵和核心。近年函數(shù)式編程語(yǔ)言在工業(yè)界流行,在大學(xué)開(kāi)設(shè)相關(guān)課程是教改關(guān)注的熱門(mén)話題。這種范式的編程語(yǔ)言與大眾熟知的面向?qū)ο?、命令式編程語(yǔ)言不同,為編程人員提供了新的問(wèn)題解決思路與思維工具。盡管這種編程語(yǔ)言在工業(yè)界和學(xué)術(shù)圈流行,但尚未在國(guó)內(nèi)高校編程教學(xué)中得到重視。
本文從函數(shù)式編程的新視角出發(fā),研究了函數(shù)式編程特點(diǎn),用不同的方式解決傳統(tǒng)軟件開(kāi)發(fā)的諸多問(wèn)題。啟發(fā)學(xué)生思維,為解決老問(wèn)題提供新思路。
1默認(rèn)不可變
函數(shù)式編程語(yǔ)言強(qiáng)調(diào)量默認(rèn)的不可變性,如C語(yǔ)言中,入門(mén)時(shí)一定會(huì)告訴學(xué)生需要定義一個(gè)整形變量sum,初始值為0,當(dāng)對(duì)1-100的整形數(shù)求和時(shí),可以不斷將1-100中的數(shù)字i加到sum中,改變sum變量的值為舊的sum加上新的i,這樣可以求得最終的和值。在這個(gè)過(guò)程中,編程者向計(jì)算機(jī)仔細(xì)描述了實(shí)現(xiàn)一個(gè)數(shù)列求和功能的函數(shù)。
而在函數(shù)式編程中,因?yàn)榱磕J(rèn)是不可變的,就不能進(jìn)行累計(jì)的求和累加行為,需要換一種思路。從代碼1可以看到,1~4行構(gòu)造了一個(gè)遞歸函數(shù),這個(gè)遞歸函數(shù)有兩個(gè)輸入:第一個(gè)輸入data是一個(gè)要求和的list,第二個(gè)輸入是用來(lái)求和的sum。接下來(lái)的match with關(guān)鍵字是現(xiàn)代編程語(yǔ)言,諸如Scala、Swift等編程語(yǔ)言,都有類(lèi)似的語(yǔ)法特征(如用Switch實(shí)現(xiàn)這一功能)??梢钥吹健?”豎線部分代表一種需要被匹配的情況,如果匹配成功,則執(zhí)行箭頭指向的語(yǔ)句塊。匹配規(guī)則是如果data的list由一個(gè)頭元素head和若干尾元素tail(這里的tail是一個(gè)去掉data頭元素的list)組成,那么函數(shù)會(huì)將頭元素加到sum上,得到新的sum,并對(duì)tail尾進(jìn)行上述操作,不同的是輸入給getSum函數(shù)的sun值已經(jīng)變成在原來(lái)的sum值上加了head值。在第5行中,當(dāng)data的list為空時(shí),可以看到getSum函數(shù)會(huì)將sum輸入的結(jié)果直接返回給用戶(hù)。在代碼1第6行中,可以看到一個(gè)求1到100的和的例子,data輸入對(duì)應(yīng)[1…100],代表一個(gè)從1到100,步長(zhǎng)為1的list,sum輸入一個(gè)整形0作為和的初始值。
代碼1:基于函數(shù)式編程語(yǔ)言的不可變、模式匹配特性進(jìn)行求和。
從這段代碼可以看出,編程人員在實(shí)現(xiàn)過(guò)程中沒(méi)有用到任何可變變量,一切值在所有行里都是不可變的,這保證了當(dāng)代碼在多核或多線程中運(yùn)行時(shí)沒(méi)有任何副作用,是純粹(pure)的。
量默認(rèn)不可變優(yōu)勢(shì)非常明顯,任何一段代碼拿到一個(gè)量都可以放心使用這個(gè)量,不必?fù)?dān)心代碼在其它線程或內(nèi)核中改變這個(gè)值,這在機(jī)制上克服了副作用,避免了加鎖等一系列繁瑣操作。
2高階函數(shù)與聲明式編程
分析例證1:求一個(gè)list的和,可以給一個(gè)初始值,將list中每個(gè)頭元素不斷遞歸與初始值求和,并作為新的初始值送人下一次遞歸,直到list為空,即可求得整個(gè)list的和值,這其實(shí)就是MAP/REDUCE計(jì)算模型中的REDUCE過(guò)程。
代碼2:描述做什么(WHAT),而不是訴說(shuō)怎么做(WHAT)。
分析例證2:通過(guò)一個(gè)計(jì)算實(shí)例向?qū)W生展現(xiàn)函數(shù)編程語(yǔ)言的優(yōu)雅與簡(jiǎn)潔。當(dāng)存在一個(gè)list,從1到100,要求對(duì)其中的每一個(gè)元素做某種運(yùn)算f生成一組新的元素,再對(duì)其中符合某種條件t的元素進(jìn)行保留,最后對(duì)剩余元素求和。
通過(guò)提問(wèn)方式討論傳統(tǒng)編程語(yǔ)言解決這一問(wèn)題的方法。通常發(fā)現(xiàn)需要對(duì)list元素進(jìn)行遍歷,計(jì)算遍歷元素i在廠函數(shù)作用下的結(jié)果f(i),然后判斷f(i)是否符合條件t。如符合,將其值加入一個(gè)累計(jì)變量sum中作為最終的和;如不符合t則不進(jìn)行任何操作。而在F#代碼2中可以看出,第1行構(gòu)造目標(biāo)數(shù)據(jù),第2行對(duì)數(shù)據(jù)中每一個(gè)元素進(jìn)行操作(將每個(gè)元素平方,然后加1),第3行對(duì)新生成數(shù)據(jù)進(jìn)行過(guò)濾,保留其中可以被2整除的元素,第4行對(duì)結(jié)果進(jìn)行求和。第2行和第3行中,直接將一個(gè)需要進(jìn)行的操作和判斷的條件放在map函數(shù)及filter函數(shù)后面,對(duì)需要進(jìn)行操作或過(guò)濾的數(shù)據(jù)進(jìn)行操作或過(guò)濾,這樣可以接受一個(gè)作為輸入的函數(shù),叫做高階函數(shù)。學(xué)生在學(xué)習(xí)到這一特性時(shí),普遍會(huì)覺(jué)得函數(shù)式編程的語(yǔ)法更容易理解,同時(shí)看起來(lái)很優(yōu)雅。
有了高階函數(shù),函數(shù)式編程賦予編程者組合拼接各種功能函數(shù)的能力,從而通過(guò)若干小的功能就可構(gòu)造出更大更多功能。而高階函數(shù)直接避免了指針(或代理)的使用,可以將函數(shù)很輕松地作為像量一樣的東西傳遞,這種將函數(shù)和量保持在同等地位的特性也是函數(shù)式編程獨(dú)有的。
3并行計(jì)算與異步計(jì)算
隨著單純提高CPU頻率滿(mǎn)足摩爾定律的終結(jié),中央處理器的核心數(shù)量越來(lái)越多,并行并發(fā)計(jì)算在各種編程語(yǔ)言中都是學(xué)習(xí)難點(diǎn)。而F#的語(yǔ)法設(shè)計(jì)在學(xué)習(xí)并行與異步計(jì)算課程中異常簡(jiǎn)單。示例代碼如代碼3、代碼4所示。
代碼3:數(shù)組同步并行計(jì)算代碼示例。
代碼3與代碼2的運(yùn)算完全一致,不同的是代碼2的計(jì)算由單個(gè)CPU核心完成,代碼3則會(huì)使用CPU所有的邏輯核心和實(shí)體核心。仔細(xì)比較兩段代碼,可發(fā)現(xiàn)它們高度類(lèi)似,只是list換成了array及額外增加了一個(gè)parallel。之所以將list換成array,是因?yàn)閘ist對(duì)模式匹配的支持更豐富,但array通過(guò)下屬parallel模塊支持同步并行計(jì)算。如示例所示,當(dāng)對(duì)一個(gè)集合中每個(gè)元素作某種變換(用一個(gè)函數(shù)處理元素)時(shí),可以直接使用并行計(jì)算,這樣F#編程中的同步并行計(jì)算就可輕易達(dá)成。講授到這里時(shí),學(xué)生會(huì)感嘆函數(shù)式編程的易用性。但還需補(bǔ)充講解list和array數(shù)據(jù)類(lèi)型的特點(diǎn)和區(qū)別,避免學(xué)生在學(xué)習(xí)中混淆。
代碼4:數(shù)組異步并行計(jì)算代碼示例。
同步并行問(wèn)題在于當(dāng)計(jì)算量較大時(shí),CPU因所有核心都在執(zhí)行目標(biāo)操作,無(wú)暇顧及其它系統(tǒng)請(qǐng)求,會(huì)給用戶(hù)造成卡頓的感覺(jué)。當(dāng)希望CPU進(jìn)行計(jì)算的同時(shí)還可響應(yīng)必要的系統(tǒng)請(qǐng)求,就需要異步并行計(jì)算。還是同樣的計(jì)算任務(wù),代碼4展示了在F#中使用async關(guān)鍵字構(gòu)造一個(gè)異步計(jì)算并完成的過(guò)程。
第3到第5行的代碼將原本要進(jìn)行的計(jì)算包裹在一個(gè)結(jié)構(gòu)async{return something}中,代表一個(gè)要進(jìn)行的異步計(jì)算,something是這個(gè)異步要返回的結(jié)果。構(gòu)造完這個(gè)異步計(jì)算,第2到第6行就將1~100的100個(gè)元素映射成了100個(gè)異步計(jì)算,第7行的Async.Parallel用fork-join模式,將100個(gè)異步計(jì)算合并成一個(gè)異步計(jì)算,在第8行代碼的執(zhí)行命令下,返回這個(gè)異步計(jì)算結(jié)果(注意這時(shí)結(jié)果是一個(gè)數(shù)據(jù)Array)。
講授該知識(shí)點(diǎn)時(shí),可請(qǐng)學(xué)生在課堂上討論各種編程語(yǔ)言是如何實(shí)現(xiàn)異步編程的。在向大家介紹F#的異步實(shí)現(xiàn)方式后,通過(guò)對(duì)比,學(xué)生會(huì)發(fā)現(xiàn)F#不需要構(gòu)造線程、設(shè)計(jì)Runnable類(lèi)對(duì)象、聲明回調(diào)函數(shù)等瑣碎細(xì)節(jié)就可直接構(gòu)造一個(gè)異步運(yùn)算,并可執(zhí)行和獲得結(jié)果。
4結(jié)語(yǔ)
治學(xué)當(dāng)知行合一(王守仁),學(xué)習(xí)計(jì)算機(jī)相關(guān)知識(shí)尤其需要學(xué)生動(dòng)手實(shí)踐。當(dāng)學(xué)生面對(duì)并行、異步、高階函數(shù)、默認(rèn)不可變時(shí),這些概念對(duì)他們來(lái)說(shuō)是陌生的。通過(guò)代碼示例、課堂討論、動(dòng)手實(shí)踐,學(xué)生能掌握函數(shù)式編程的核心概念和解決問(wèn)題的一般思路。聲明式編程將編程人員的焦點(diǎn)從告訴計(jì)算機(jī)如何做轉(zhuǎn)移到告訴計(jì)算機(jī)做什么上。計(jì)算機(jī)編程已經(jīng)到了一個(gè)需要進(jìn)一步向前演化的時(shí)間節(jié)點(diǎn),Scala、F#等函數(shù)式編程語(yǔ)言和函數(shù)式語(yǔ)法已在工業(yè)界逐步流行,這要求學(xué)校具有前瞻性,為學(xué)生儲(chǔ)備相關(guān)知識(shí)和基礎(chǔ)技能。
通過(guò)學(xué)習(xí)《F#函數(shù)式編程》課程,學(xué)生可理解面向?qū)ο缶幊讨獾牧硪环N編程思想,為其將來(lái)工作中解決生產(chǎn)實(shí)踐問(wèn)題提供新的思維工具。通過(guò)不斷探索不同知識(shí)點(diǎn)的教學(xué)方法,學(xué)生更容易理解一些全新概念,豐富知識(shí)儲(chǔ)備和思維方式,對(duì)培養(yǎng)新時(shí)代的計(jì)算機(jī)專(zhuān)業(yè)人才具有重要意義,計(jì)算機(jī)編程教學(xué)質(zhì)量也將得到進(jìn)一步提升。