Vimscript 函數(shù)式編程

2018-02-24 16:03 更新

現(xiàn)在讓我們小憩一下,聊一聊一種你可能聽過的編程風(fēng)格:函數(shù)式編程。

如果你用過Python,Ruby或Javascript,_甚或_Lisp,Scheme,Clojure或Haskell, 你應(yīng)該會覺得把函數(shù)作為變量類型,用不可變的狀態(tài)作為數(shù)據(jù)結(jié)構(gòu)是平常的事。 如果你沒用過,你可以放心地跳過這一章了,但我還是鼓勵你找機(jī)會去試試并拓寬自己的視野。

Vimscript具有使用函數(shù)式風(fēng)格進(jìn)行編程的潛力,不過會有點(diǎn)吃力。 我們可以創(chuàng)建一些輔助函數(shù)來讓這個(gè)過程少些痛苦。

繼續(xù)前進(jìn)并創(chuàng)建functional.vim文件,這樣你就不用反復(fù)地重新?lián)舸蛎恳恍写a。 這個(gè)文件將會成為這一章的草稿本。

不可變的數(shù)據(jù)結(jié)構(gòu)

不幸的是,Vim沒有類似于Clojure內(nèi)置的vector和map那樣的不可變集合, 不過通過一些輔助函數(shù),我們可以在一定程度上模擬出來。

在你的文件加上下面的函數(shù):

function! Sorted(l)
    let new_list = deepcopy(a:l)
    call sort(new_list)
    return new_list
endfunction

保存并source文件,然后執(zhí)行:echo Sorted([3,2,4,1])來試試看。 Vim輸出[1,2,3,4]。

這跟調(diào)用內(nèi)置的sort()函數(shù)有什么區(qū)別呢?關(guān)鍵在于第一行:let new_list = deepcopy(a:l)。 Vim的sort()_就地_重排列表,所以我們先創(chuàng)建一個(gè)列表的副本,并排序副本, 這樣原本的列表不會被改變。

這樣就避免了副作用,并幫助我們寫出更容易推斷和測試的代碼。讓我們加入更多同樣風(fēng)格的輔助函數(shù):

function! Reversed(l)
    let new_list = deepcopy(a:l)
    call reverse(new_list)
    return new_list
endfunction

function! Append(l, val)
    let new_list = deepcopy(a:l)
    call add(new_list, a:val)
    return new_list
endfunction

function! Assoc(l, i, val)
    let new_list = deepcopy(a:l)
    let new_list[a:i] = a:val
    return new_list
endfunction

function! Pop(l, i)
    let new_list = deepcopy(a:l)
    call remove(new_list, a:i)
    return new_list
endfunction

除了中間的一行和它們接受的參數(shù),每一個(gè)函數(shù)都是一樣的。保存并source文件,在一些列表上試試它們。

Reversed()接受一個(gè)列表并返回一個(gè)新的倒置了元素的列表。

Append()返回一個(gè)在原列表的基礎(chǔ)上增加了給定值的新列表。

Assoc()("associate"的縮寫)返回一個(gè)給定索引上的元素被替換成新值的新列表。

Pop()返回一個(gè)給定索引上的元素被移除的新列表。

作為變量的函數(shù)

Vimscript支持使用變量儲存函數(shù),但是相關(guān)的語法有點(diǎn)愚鈍。執(zhí)行下面的命令:

:let Myfunc = function("Append")
:echo Myfunc([1, 2], 3)

Vim意料之中地顯示[1, 2, 3]。注意我們使用的變量以大寫字母開頭。 如果一個(gè)Vimscript變量要引用一個(gè)函數(shù),它就要以大寫字母開頭。

就像其他種類的變量,函數(shù)也可以儲存在列表里。執(zhí)行下面命令:

:let funcs = [function("Append"), function("Pop")]
:echo funcs[1](['a', 'b', 'c'], 1)

Vim顯示['a', 'c']。funcs變量_不_需要以大寫字母開頭,因?yàn)樗鼉Υ娴氖橇斜?,而不是函?shù)。 列表的內(nèi)容不會造成任何影響。

高階函數(shù)

讓我們創(chuàng)建一些用途廣泛的高階函數(shù)。如果你需要解釋,高階函數(shù)就是接受_別的_函數(shù)并使用它們的函數(shù)。

我們將從map函數(shù)開始。在你的文件中添加這個(gè):

function! Mapped(fn, l)
    let new_list = deepcopy(a:l)
    call map(new_list, string(a:fn) . '(v:val)')
    return new_list
endfunction

保存并source文件,執(zhí)行下面命令試試看:

:let mylist = [[1, 2], [3, 4]]
:echo Mapped(function("Reversed"), mylist)

Vim顯示[[2, 1], [4, 3]],正好是對列表中的每一個(gè)元素應(yīng)用了Reversed()的結(jié)果。

Mapped()是如何起作用的?我們又一次用deepcopy()創(chuàng)建新的列表,修修改改,返回修改后的副本 —— 沒什么是新的。有門道的是中間的部分。

Mapped()接受兩個(gè)參數(shù):一個(gè)funcref("儲存一個(gè)函數(shù)的變量"在Vim里的說法)和一個(gè)列表。 我們使用內(nèi)置的map()函數(shù)實(shí)現(xiàn)真正的工作?,F(xiàn)在就閱讀:help map()來看它怎么工作的。

現(xiàn)在我們將創(chuàng)建一些通用的高階函數(shù)。把下面的代碼加入到你的文件:

function! Filtered(fn, l)
    let new_list = deepcopy(a:l)
    call filter(new_list, string(a:fn) . '(v:val)')
    return new_list
endfunction

用下面的命令嘗試Filtered()

:let mylist = [[1, 2], [], ['foo'], []]
:echo Filtered(function('len'), mylist)

Vim顯示[[1, 2], ['foo']]。

Filtered()接受一個(gè)謂詞函數(shù)和一個(gè)列表。它返回一個(gè)列表的副本, 而這個(gè)列表只包括將自身作為謂詞函數(shù)的輸入?yún)?shù)并返回真值的元素。 這里我們使用了內(nèi)置的len(),讓它過濾掉所有長度為0的元素。

最后我們創(chuàng)建了Filtered()的好基友(counterpart):

function! Removed(fn, l)
    let new_list = deepcopy(a:l)
    call filter(new_list, '!' . string(a:fn) . '(v:val)')
    return new_list
endfunction

像使用Filtered()一樣試一下:

:let mylist = [[1, 2], [], ['foo'], []]
:echo Removed(function('len'), mylist)

Vim顯示[[], []]。Removed()就像Filtered(),不過它只保留謂詞函數(shù)返回_非_真值的元素。

代碼中的唯一不同在于調(diào)用命令前面的'!' .,它把謂詞函數(shù)的結(jié)果取反。

效率

考慮到Vim不得不持續(xù)地創(chuàng)建新的副本并垃圾回收舊的對象,你可能會認(rèn)為不停地制造副本是種浪費(fèi)。

是的,你是對的!Vim的列表不像Clojure的vector那樣支持結(jié)構(gòu)共享(structural sharing), 所以這里所有的復(fù)制操作是昂貴的。

有時(shí)這的確是個(gè)問題。如果你需要使用龐大的列表,程序就會因此變慢。 在現(xiàn)實(shí)世界,你可能會吃驚地發(fā)現(xiàn)你幾乎不會注意到其中的差別。

想想看吧:當(dāng)我正寫下本章時(shí),Vim占用了80M內(nèi)存(而且我可是裝了_一堆_插件)。 我的筆記本總共有_8G_內(nèi)存。有一些列表的副本被創(chuàng)建出來,這會造成可被察覺的不同嗎? 當(dāng)然這取決于列表的大小,但在大多數(shù)情況下答案將會是"No"。

作為比較,我的Firefox打開了五個(gè)tab,現(xiàn)在正饕餮著_1.22G_內(nèi)存。

你將需要自己判斷,什么時(shí)候這種編程風(fēng)格會導(dǎo)致不可接受的低效率。

練習(xí)

閱讀:help sort()。

閱讀:help reverse()。

閱讀:help copy()。

閱讀:help deepcopy()。

閱讀:help map(),如果你未曾讀過。

閱讀:help function()。

修改Assoc(),?Pop(),?Mapped(),?Filtered()Removed()來支持字典類型。 你可能需要閱讀:help type()來幫助自己。

實(shí)現(xiàn)Reduced()。

倒給自己一杯最喜歡的飲料。這一章真激烈(intense)!

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號