最容易令初學(xué)者混亂的F#命令
而對(duì)于F#的初學(xué)者,或是C#和F#混用的程序員來說,我認(rèn)為F#中最容易令人混亂的命令是Reference Cells的取值操作了。下面便詳細(xì)談?wù)勥@么說的原因,及建議的應(yīng)對(duì)辦法。
F#是一門函數(shù)式編程語言,函數(shù)式編程語言的特點(diǎn)之一便是No Side Effect(無副作用),Immutable(不可變)。但是在很多場景下,Mutable(可變)可以給我?guī)砗芏啾憷绕涫窃诮Y(jié)合命令式編程的場景中。因此F#提供了將某個(gè)“標(biāo)識(shí)符”定義為“可變”的方式,主要有兩種:使用mutable關(guān)鍵字或是Reference Cells。
在大部分情況下,我推薦(微軟也這么推薦的)使用mutable關(guān)鍵字,因?yàn)檫@樣標(biāo)識(shí)符在使用上也已經(jīng)和普通變量沒有任何區(qū)別了。與之相對(duì),使用Reference Cell進(jìn)行讀寫操作都需要一些特殊的操作/指令。不過的確有一些場景必須使用Reference Cells,您可以關(guān)注MSDN上的說明。例如,在mutable的標(biāo)識(shí)符在讀取和賦值時(shí),和普通的屬性沒有什么區(qū)別:
- let mutable a = 0
- a <- 1 // assign mutable variable
- let request = WebRequest.Create("http://m.ekrvqnd.cn")
- request.ContentType <- "text/xml" // assign property
但是對(duì)于Reference Cells來說,它的讀取和寫入就需要使用!與:=操作符了:
- let a = ref 0
- a := 1 // assign value
- printfn "%i" !a // retrieve value
這個(gè)感嘆號(hào)便是引起混亂的源泉,且看以下代碼:
- let transfer (streamIn: Stream) (streamOut: Stream) buffer =
- let hasData = ref true
- while !hasData do
- let lengthRead = streamIn.Read(buffer, 0, buffer.Length)
- if lengthRead > 0 then
- streamOut.Write(buffer, 0, lengthRead)
- else
- hasData := false
上面的代碼定義了一個(gè)transfer函數(shù),將一個(gè)數(shù)據(jù)流中的數(shù)據(jù)全部傳輸?shù)搅硪粋€(gè)數(shù)據(jù)流中。在這里我們使用了命令式的編程方式,并使用一個(gè)名為hasData的Ref Cell來表明是否讀完了數(shù)據(jù)。
不過,您看到while語句中的!hasData是什么感覺?至少對(duì)于我這樣混寫C#和F#的人來說,我的***反應(yīng)是“嗯,取反?”,然后才是“哦,只是Ref Cells的取值操作”。對(duì)于其他一些場景下可能這點(diǎn)不會(huì)成為問題,但如果這個(gè)Ref Cell是個(gè)布爾值,然后又放在if或while的時(shí)候,混亂就這樣開始了。因此,我目前可能會(huì)傾向于使用這樣的方式:
- let transfer (streamIn: Stream) (streamOut: Stream) buffer =
- let hasData = ref true
- while hasData.Value do
- let lengthRead = streamIn.Read(buffer, 0, buffer.Length)
- if lengthRead > 0 then
- streamOut.Write(buffer, 0, lengthRead)
- else
- hasData := false
在F#中,一個(gè)Ref Cell其實(shí)是一個(gè)Ref<'a>類型的對(duì)象,它有一個(gè)'a類型(泛型類型)的Value屬性,可讀寫。因此,如果我們?cè)谏厦娴拇a中直接使用Value屬性,那么我想就不會(huì)讓任何人混亂了。當(dāng)然,***的辦法可能還是寫一些immutable的代碼吧,例如:
- let rec transfer (streamIn: Stream) (streamOut: Stream) buffer =
- let lengthRead = streamIn.Read(buffer, 0, buffer.Length)
- if lengthRead > 0 then
- streamOut.Write(buffer, 0, lengthRead)
- transfer streamIn streamOut buffer
對(duì)于F#來說,這樣的(尾)遞歸和之前的實(shí)現(xiàn)方式可以說是完全等價(jià)的。
【編輯推薦】