結(jié)合實(shí)例理解F#函數(shù)式語言中的函數(shù)
經(jīng)常有人覺得F#難懂難用,我覺得一部分原因是F#函數(shù)式語言中的函數(shù)接口(這里的接口指的是function signature, 我習(xí)慣叫它函數(shù)接口,如果對您閱讀帶來什么不便,請見諒). 看起來和我們平常熟悉的很不一樣(比如C#),導(dǎo)致一些朋友在嘗鮮階段遇到困難,進(jìn)而覺得其難懂難用,***徹底將F#打入冷宮。希望下面的內(nèi)容能讓大家覺得F#的函數(shù)不再難懂
還是老規(guī)矩,先來看一個(gè)例子
- let res = Seq.unfold (fun (a,b) ->Some(a,(a+b,a))) (0,1)
- Seq.iter(fun x -> printf "%d " x) ( Seq.take 10 res)
(這個(gè)例子中用到了F#中一個(gè)基本的immutable類型Seq<'T>, 你可簡單的把它等同于C# 3.0中鼎鼎大名的IEnumerable
這段代碼是運(yùn)行結(jié)果是
0 1 1 2 3 5 8 13 21 34 (注意第二行的后面,我只取了sequence的前10個(gè)元素,Seq.take 10 res).看到輸出結(jié)果,我想大家都明白了這個(gè)程序***行是在生成Fibonacci數(shù)列,讓我們來看一下Seq.unfold方法的接口
val unfold: ('State -> ('T * 'State) option) -> 'State -> Seq<'T>
我想很多人在初學(xué)F#時(shí)看過這樣的函數(shù)接口說明會感到很陌生吧,沒關(guān)系,這正是本文的目的,讓你對其不再陌生。
F#使用箭頭 "->"來標(biāo)記函數(shù)接口,比如 int -> string 就表示這是一個(gè)接受一個(gè)int型參數(shù)并返回一個(gè)string值的函數(shù)。比如 let f (x:int) = x.ToString()
了解了這個(gè)好象并不能幫助我們來理解Seq.unfold方法的接口,要理解它,我們還需要了解一些基本概念。
Higher-Order Function. 有可能以前你沒聽說過這種提法,但不要被這個(gè)陌生的名字嚇到,其實(shí)我們大家在中學(xué)時(shí)就接觸過這個(gè)東西了,不信你看這個(gè)sin(x+y) = sinxcosy+cosxsiny。三角和角公式,這個(gè)大家總該有些記憶吧? 有人要問這和Higher-Order Function有什么關(guān)系呢?別急,讓我們先看看Higher-Order Function的定義。SICP 里是這樣定義的:Procedures that manipulate procedures are called higher-order procedures。(定義里的procedures就是我們這里說的Function的意思).定義中提到的操作(manipulate)函數(shù)不由讓我們想入非非,怎么樣操作函數(shù)?把函數(shù)當(dāng)作普通的參數(shù)來理解不就簡單多了,( 簡單點(diǎn)就是一個(gè)函數(shù)接受的參數(shù)可以是函數(shù),它的返回值也可以是函數(shù))。那為什么要叫Higher-Order procedures呢?你的老板可以成天讓你做這個(gè)干那個(gè),你能反過來指揮你老板做事么J 再看下我上面說的三角和角公式,把sin和cos都看成是函數(shù)的話,呵呵,是不是我們在高中就接觸過Higher-Order Function了?
有了Higher-Order Function的概念,好象還是不能讓我們很快看明白Seq.unfold該怎么用。讓我們接著來看一個(gè)簡單的函數(shù)
let add x y = x+y ( 我們看到這個(gè)函數(shù)的接口是val add: int -> int -> int)
讓我們接著分析一下這個(gè)簡單的方法,F(xiàn)#中箭頭"->“來表示一個(gè)函數(shù),并且它是從右到左結(jié)合的,所以我們可以把int->int->int看成int->(int->int),結(jié)合剛剛說過的Higher-Order Function,這個(gè)就變得很易容易理解了,Add接受一個(gè)int型參數(shù),返回一個(gè) int->int的函數(shù)。讓我們根據(jù)這個(gè)把a(bǔ)dd來改寫一下使其更直觀一些
let add x y = (fun x -> (fun y -> x+y) ) (這個(gè)很容易讀懂了吧? 接受一個(gè)參數(shù)x,返回一個(gè)函數(shù) fun y -> x+y,返回的函數(shù)接受一個(gè)參數(shù)y,并且返回x+y的值。)
有了上面的基礎(chǔ),讓我們更進(jìn)一步,
let add10 = add 10
我想這個(gè)大家應(yīng)該都能看明白了吧, let add10 = (fun x -> (fun y -> x+y)) 10 = fun y -> 10+y。類似于add10這種用法,在F#中叫做Currying Fuction, 這里的curry跟咖哩沒任何關(guān)系,它和Haskell語言的命名一樣,都是為了紀(jì)念著名邏輯學(xué)專家Haskell Curry,當(dāng)然currying function也不是F#獨(dú)用的,實(shí)際上你幾乎可以在任何函數(shù)式語言上看到它的身影。
講了這么多了,讓我們回到最開始的例子,
val unfold: ('State -> ('T * 'State) option) -> 'State -> Seq<'T>
現(xiàn)在這個(gè)看起來沒前面那么難了吧, unfold方法接受一個(gè)('State -> ('T * 'State) option)的參數(shù),返回一個(gè)接受'State并返回Seq<'T>的函數(shù)。接受的('State->('T *'State) option)的參數(shù)又是什么? 當(dāng)然是一個(gè)接受'State參數(shù)并返回 ('T * 'State) option的函數(shù)。接著我們采用笨辦法來理解let res = Seq.unfold (fun (a,b) ->Some(a,(a+b,a))) (0,1)一一對應(yīng)之,(a,b)對應(yīng)于'State, Tuple(a,(a+b,a))中逗號前的a對應(yīng)于'T,后面的(a+b,a)對應(yīng)于 'T*'State,因?yàn)槲覀兛吹浇涌谡f明中是('T * 'State) option,所以我們相應(yīng)的加上Some關(guān)鍵字(有關(guān)option type,參見上一篇),后半部分我就不多解釋了,通過一一對應(yīng),我們看到Seq里存的值是Tuple(a,(a+b,a))中的***項(xiàng),也就是逗號前的a了。這下你明白怎么讀懂一個(gè)函數(shù)接口了吧?
如果你剛剛開始學(xué)F#,請做幾個(gè)練習(xí)鞏固一下今天學(xué)的知識吧?你能根據(jù)下面所寫的函數(shù)接口構(gòu)造一個(gè)函數(shù)么?
- 'a -> ('a->'b)->'b
- ('a->'b) ->('c -> 'a) -> 'c -> 'b
- (’T -> bool) -> ‘T list -> ‘T list
F#函數(shù)式語言總結(jié):
1.F#函數(shù)式語言中每個(gè)函數(shù)都有一個(gè)返回值,這個(gè)返回值可以是具體的值,也可以是另一個(gè)函數(shù)(unit表示函數(shù)返回值為空(void)). 當(dāng)在讀一個(gè)函數(shù)接口說明時(shí),最右面箭頭"->"后面的部分,表示的就是該函數(shù)的返回值。F#中的每個(gè)函數(shù)都只能接受一個(gè)參數(shù),同樣,這個(gè)參數(shù)可以是具體的值,也可以是一個(gè)參數(shù)。
2. Higher-Order function 和Currying function是理解并能熟練運(yùn)用函數(shù)式編程的重要基石,希望還不太明白的朋友能好好理解我上面的例子,把基礎(chǔ)打好。
本文來自芭蕉的博客園文章《結(jié)合實(shí)例實(shí)習(xí)F#(三)--理解函數(shù)式語言中的函數(shù)》
【編輯推薦】