




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
函數(shù)式程序設(shè)計語言
第0章導論“Alanguagethatdoesn’teffecthowyouthinkaboutprogrammingisnotworthlearning”AlanPerlis如果你給別人講解程序時,看到對方點頭了,那你就拍他一下,他肯定睡覺了。EpigramsonProgramming編程范式(ProgrammingParadigm)令一門編程語言區(qū)別于其它語言的根本在于它所提供的編程范式編程范式指的是編程活動中所遵循的根本風格。它包括選取何種基本元素來構(gòu)成程序(比如對象,函數(shù),變量,約束條件等等)以及用何種方式來表達計算過程(比如賦值、表達式求值,數(shù)據(jù)流等等)。--維基百科你知道并用過多少種編程范式?ImperativeDeclarativeObject-OrientedAspect-OrientedEvent-DrivenFunctionalGenericLogicConcurrent二大類“命令式聲明式命令式、函數(shù)式和邏輯式是最核心的范式命令式將程序看成自動機:通過自動機完成狀態(tài)的轉(zhuǎn)變函數(shù)式將程序看成數(shù)學函數(shù),通過表達式的變換完成計算;邏輯式將程序看成邏輯證明,通過邏輯推理完成證明函數(shù)一個函數(shù)具有一些輸入(arguments,parameters)并返回一個輸出值(result).設(shè)A是函數(shù)f的輸入值集合,稱之為f的定義域(thedomainofthefunctionf).設(shè)B是函數(shù)f的結(jié)果值的集合,稱之為f的值域(thecodomainofthefunctionf.)稱f是從集合A到集合B的函數(shù)。用A->B表示A到B的所有函數(shù)的集合。
函數(shù)程序設(shè)計在FP這種編程范式里,計算是通過對函數(shù)的求值(evaluation)來組織的,它盡量避免涉及狀態(tài)與可變(mutable)數(shù)據(jù)。FP強調(diào)的是函數(shù)的施用(application),關(guān)注的是值value,與之相對的命令式編程強調(diào)的則是狀態(tài)的改變。
FP中變量(variable)一旦被指定,就不可以更改了。幾乎所有的主流語言都是偏向命令式的。和命令式編程相比,函數(shù)式編程強調(diào)程序的執(zhí)行結(jié)果比執(zhí)行過程更重要,倡導利用若干簡單的執(zhí)行單元讓計算結(jié)果不斷漸進,逐層推導復(fù)雜的運算,而不是設(shè)計一個復(fù)雜的執(zhí)行過程。函數(shù)程序設(shè)計?C,Java,AdaandPascal是命令式(imperative
)語言:一個程序時一些命令的序列,程序運行時命令按順序執(zhí)行。關(guān)注的焦點:如何進行計算.一個函數(shù)程序定義了如何將輸入數(shù)據(jù)轉(zhuǎn)換為輸出數(shù)據(jù),它是一個表達式:程序的運行通過計算表達式實現(xiàn)。關(guān)注焦點:計算什么.函數(shù)程序設(shè)計基于一種不同于其他程序設(shè)計語言的程序觀:一個程序是從輸入到輸出的函數(shù),程序的執(zhí)行是表達式的計算,而非命令的執(zhí)行。Letthemachinedomachine’sworkandletthehumandohuman’swork.函數(shù)程序設(shè)計?表達式由函數(shù)和基本值構(gòu)成.例:計算從1至10的和.
命令式語言:total=0;for(I=1;I<=10,I++)total+=I;Haskell語言:sum[1..10]sum是一個函數(shù),[1..10]表示1至10的整數(shù).簡單來說,Haskell是一門通用的、靜態(tài)強類型的、pure(該叫純粹還是純潔呢?)的、函數(shù)式編程語言。它提供的最基本的編程范式是FP,另外還能支持其它多種范式。函數(shù)程序設(shè)計的特點簡潔、優(yōu)美.函數(shù)程序更簡潔,容易理解。例如:qsort快速排序
qsort[]=[]qsort(x:xs)=qsortless++[x]++qsortmorewhereless=[y|y<-xs,y<=x]more=[y|y<-xs,y>x]運行過程無副作用(sideeffect):純函數(shù)式編程語言中的函數(shù)能做的唯一事情就是利用引數(shù)計算結(jié)果,不會產(chǎn)生所謂的“副作用(sideeffect)”。若以同樣的參數(shù)調(diào)用同一個函數(shù)兩次,得到的結(jié)果一定是相同。這被稱作“引用透明(ReferentialTransparency)”如此一來編譯器就可以理解程序的行為,你也很容易就能驗證一個函數(shù)的正確性,繼而可以將一些簡單的函數(shù)組合成更復(fù)雜的函數(shù)。函數(shù)程序設(shè)計的特點ReferentialTransparent能用2*f(x)來替換f(x)+f(x)嗎?inty=10;intf(inti){returni+y++;}f(5)+f(5);//15+16=312*f(5);//2*15=30Purity:variable一旦和某個數(shù)據(jù)綁定了就不可再修改了,所以它不再是可變的量,而僅僅是一個不可變的值的名字。puritya=3a=19Purity函數(shù)不會有sideeffect,不會對數(shù)據(jù)做破壞性的修改,不會改變外部世界的狀態(tài),它的輸出只依賴它的輸入,任何時刻,給定同樣的輸入,一定得到同樣的輸出。數(shù)學中的函數(shù)從來就具有這樣的性質(zhì),可在大多數(shù)編程語言中這種美好的性質(zhì)卻被毀了。能用來做事嗎?如果連狀態(tài)都不能改變,那它有個鳥用!不要誤會,所謂不能改變狀態(tài)指的是函數(shù)求值的時候不允許改變狀態(tài),并不意味著程序運行的時候不能改變狀態(tài)這是什么意思?使用函數(shù)而非變量來保存狀態(tài),狀態(tài)變量保存在函數(shù)的參數(shù)值紅,堆棧上。遞歸函數(shù)MonadIO()是一種IO動作,它是普通的first-class的數(shù)據(jù),如果它被執(zhí)行就會做某些輸入輸出操作,IO動作只能被其它的IO動作嵌套,main是最外層的IO動作,也是程序的入口點一個IO動作在創(chuàng)建的時候并不會執(zhí)行,只有在嵌套它的外層動作被執(zhí)行時才會跟著執(zhí)行,所以執(zhí)行和函數(shù)求值是不同的所有的IO動作都是由運行程序開始執(zhí)行main而引發(fā)的所有會引發(fā)狀態(tài)改變的動作(包括IOAction)都是一種Monad。聽起來象是Monster!Haskell是惰性(lazy)的:也就是說若非特殊指明,函數(shù)在真正需要結(jié)果以前不會被求值。加上引用透明,你就可以把程序僅看作是數(shù)據(jù)的一系列變形。有了很多有趣的特性,如無限長度的數(shù)據(jù)結(jié)構(gòu)函數(shù)程序設(shè)計的特點假設(shè)你有一個List:xs=[1,2,3,4,5,6,7,8],還有一個函數(shù)doubleMe若是在命令式語言中,把一個List乘以8,執(zhí)行doubleMe(doubleMe(doubleMe(xs))),得遍歷三遍xs才會得到結(jié)果。在惰性語言中,調(diào)用doubleMe時并不會立即求值,它會說“嗯嗯,待會兒再做!”。不過一旦要看結(jié)果,第一個doubleMe就會對第二個說“給我結(jié)果,快!”第二個doubleMe就會把同樣的話傳給第三個doubleMe,第三個doubleMe只能將1乘以2得2后交給第二個,第二個再乘以2得4交給第一個,最終得到第一個元素8。也就是說,這一切只需要遍歷一次list即可,而且僅在你真正需要結(jié)果時才會執(zhí)行。惰性語言中的計算只是一組初始數(shù)據(jù)和變換公式。函數(shù)程序設(shè)計的特點靜態(tài)類型(staticallytyped)
:Haskell擁有一套強大的類型系統(tǒng),支持自動類型推導(typeinference)。這一來你就不需要在每段代碼上都標明它的類型,像計算a=5+4,你就不需另告訴編譯器“a是一個數(shù)值”,它可以自己推導出來。類型推導可以讓你的程序更加簡練。假設(shè)有個函數(shù)是將兩個數(shù)值相加,你不需要聲明其類型,這個函數(shù)可以對一切可以相加的值進行計算。函數(shù)程序設(shè)計的特點First-ClassFunction&HighOrderFunction函數(shù)是語言中的一等公民,意味著它享有和Int、Float這些基本類型一樣的公民權(quán)利:函數(shù)本身可以作為參數(shù)傳遞函數(shù)本身可以作為返回值函數(shù)本身可以作為復(fù)合類型數(shù)據(jù)的一部分高階函數(shù)是這樣一個東東:或者它的參數(shù)是另外的函數(shù)或者它的返回值是一個函數(shù)代碼重用:Polymorphismenhancereusability.Forexample,qsortcanbeusedtolistsofint,listsofdouble,listsofanytypethathas<and>=defined.模塊化:Agoodprogramminglanguagesupportmodularprogramming,andthisrequiresgoodglue.Functionalprogrammingprovidestwokindsofglue-higherorderfunctionsandlazyevaluation.函數(shù)程序設(shè)計的特點并發(fā)與并行Google打造了經(jīng)典的Map/Reduce編程框架(盡管采用了C++來實現(xiàn)),F(xiàn)P里司空見慣的pattern)和GoogleFileSystem、BigTable一道成為Google搜索技術(shù)的三大基石其實我們每天都在享受著FP提供的服務(wù)絕大多數(shù)的個人電腦中都有一個強勁的并行處理器:顯卡中的GPU!GPU的執(zhí)行模型天生就適合FPGHCHaskell是對并發(fā)支持得最完善的語言FP正在深刻地影響工業(yè)界。比如Scala,它的目的是將Haskell的思考方式帶給Java程序員,已經(jīng)成為JVM上的主流語言,正在成為其上的統(tǒng)治性語言。haskell思考問題的方式和數(shù)學家簡直是一樣的;擅長基于數(shù)理邏輯的應(yīng)用;適合并發(fā)程序、不停頓應(yīng)用,包括web;無論你常用的是什么語言,掌握一門FP尤其是Haskell這樣的純粹的FP,都會使你成為更好的programmer隨著函數(shù)式語言設(shè)計與實現(xiàn)技術(shù)的不斷發(fā)展,在保持非常優(yōu)雅的抽象機制的前提下,它們在速度上已經(jīng)越來越接近C了,1/5。為何學習Haskell課程材料講義:
u: p:sysulogic教材:Learnyouahaskellforgreatgood第一章Haskell入門Haskell入門Haskell2010language+extensions.JJJHaskell入門Haskell入門恭喜您已經(jīng)進入了ghci了!目前它的命令行提示是prelude>,不過它在你裝載一些模塊之后會變的比較長。為了美觀起見,我們會輸入指令:setprompt"ghci>"把它改成ghci>。交互式批處理模式標準引導庫標準引導庫Prelude.hs包含一些常用的算術(shù)函數(shù)和列表函數(shù)。Haskell還提供一些其他標準函數(shù)庫。?"llama"并不是數(shù)值類型,所以它不知道該怎樣才能給它加上56+0.5?函數(shù)的調(diào)用從命令式編程語言走過來的人們往往會覺得函數(shù)調(diào)用與括號密不可分Haskell中,函數(shù)的調(diào)用必使用空格bar(bar3)?Ghci>92div10究竟是哪個數(shù)是除數(shù),哪個數(shù)被除?使用中綴函數(shù)的形式打開你最喜歡的編輯器,輸入如下代碼,它的功能就是將一個數(shù)字乘以2。doubleMe
x
=
x
+
x函數(shù)的聲明與它的調(diào)用形式大致相同,都是先函數(shù)名,后跟由空格分隔的參數(shù)表。但在聲明中一定要在=后面定義函數(shù)的行為。保存為baby.hs或任意名稱,然后轉(zhuǎn)至保存的位置,打開ghci,執(zhí)行:lbaby.hs我們的第一個函數(shù)ghci>
:l
baby
[1
of
1]
Compiling
Main
(
baby.hs,
interpreted
)
Ok,
modules
loaded:
Main.
ghci>
doubleMe
9
18
ghci>
doubleMe
8.3
16.6
增加一行:保存后重新載入:可以在其他函數(shù)中調(diào)用你編寫的函數(shù),如此一來我們可以將doubleMe函數(shù)改為:doubleUs
x
y
=
doubleMe
x
+
doubleMe
y
函數(shù)應(yīng)用通常表示為函數(shù)名后接包含在括號內(nèi)的參數(shù),如f(x,y),f(2,4).在Haskell中,函數(shù)應(yīng)用表示為函數(shù)名后接依次用空格分隔的參數(shù)Haskell中的函數(shù)并沒有順序,所以先聲明doubleUs還是先聲明doubleMe都是同樣的。函數(shù)應(yīng)用比其他運算符具有更高的優(yōu)先級,如f2+4等價于(f2)+4編寫一些簡單的函數(shù),然后將其組合,形成一個較為復(fù)雜的函數(shù),這樣可以減少重復(fù)工作。注意:39ExamplesMathematicsHaskellf(x)f(x,y)f(g(x))f(x,g(y))f(x)g(y)fxfxyf(gx)fx(gy)fx*gy函數(shù)程序一個函數(shù)程序由一些函數(shù)和值構(gòu)成。一個函數(shù)通常是用其他函數(shù)定義的。一個主函數(shù)計算整個程序在輸入下的輸出。注釋--單行注釋{-多行注釋
…-}腳本格式LayoutHaskell也使用{,}和;組織定義.但是,更多的是使用版面格式規(guī)則。基本規(guī)則:如果新的一行開始于前一行的右邊,則新行是前一行說明(定義)的繼續(xù),否則是另一個說明(定義)的開始。例如,fxy=x+y+123gx=fxx簡單規(guī)則:一系列的同層定義應(yīng)該始于同一列。43Layout規(guī)則Inasequenceofdefinitions,eachdefinitionmustbegininpreciselythesamecolumn:a=10b=20c=30a=10b=20c=30a=10b=20c=3044meansThelayoutruleavoidstheneedforexplicitsyntaxtoindicatethegroupingofdefinitions.a=b+cwhereb=1c=2d=a*2a=b+cwhere{b=1;c=2}d=a*2implicitgroupingexplicitgroupingHaskell中的命名函數(shù)名和變量名以小寫字母開始。類型名和構(gòu)造符名以大寫字母開始。如Int,True.保留字(Reservedwords):caseclassdatadefaultderivingdoelseifimportininfixinfixlinfixrinstanceletmodulenewtypeofthentypewhere我們編寫一個函數(shù),它將小于100的數(shù)都乘以2,因為大于100的數(shù)都已經(jīng)足夠大了!另外一個例子doubleSmallNumber
x
=
if
x
>
100
then
x
else
x*2
Haskell中if
語句的else
部分是不可省略。在Haskell中,每個函數(shù)和表達式都要返回一個結(jié)果Haskell中的if語句的另一個特點就是它其實是個表達式,表達式就是返回一個值的一段代碼由于else是強制的,if語句一定會返回某個值如果要給剛剛定義的函數(shù)的結(jié)果都加上1,可以如此修改:doubleSmallNumber'
x
=
(if
x
>
100
then
x
else
x*2)
+
1函數(shù)名最后的那個單引號,它沒有任何特殊含義,只是一個函數(shù)名的合法字符罷了。通常,我們使用單引號來區(qū)分一個稍經(jīng)修改但差別不大的函數(shù)定義這樣的函數(shù)也是可以的:conanO'Brien
=
"It's
a-me,
Conan
O'Brien!"
沒有參數(shù)的函數(shù)通常被稱作“定義”(或者“名字”)另外一個例子能修改嗎?Haskell中,List是最常用的數(shù)據(jù)結(jié)構(gòu),并且十分強大,靈活地使用它可以解決很多問題。ghci下,我們可以使用let關(guān)鍵字來定義一個常量。在ghci下執(zhí)行l(wèi)eta=1與在腳本中編寫a=1是等價的。ghci>
let
lostNumbers
=
[4,8,15,16,23,48]
ghci>
lostNumbers
[4,8,15,16,23,48]
Haskell中,List是一種單類型的數(shù)據(jù)結(jié)構(gòu),可以用來存儲多個類型相同的元素。我們可以在里面裝一組數(shù)字或者一組字符,但不能把字符和數(shù)字裝在一起。字串實際上就是一組字符的List,"Hello"只是['h','e','l','l','o']的語法糖而已List入門可以通過++運算符實現(xiàn)ghci>
[1,2,3,4]
++
[9,10,11,12]
[1,2,3,4,9,10,11,12]
ghci>
"hello"
++
"
"
++
"world"
"hello
world"
ghci>
['w','o']
++
['o','t']
"woot“使用++運算符處理長字串時要格外小心,Haskell會遍歷整個的List(++符號左邊的那個)用:運算符往一個List前端插入元素會是更好的選擇。ghci>
'A':"
SMALL
CAT"
"A
SMALL
CAT"
ghci>
5:[1,2,3,4,5]
[5,1,2,3,4,5]
運算符可以連接一個元素到一個List或者字串之中[]表示一個空List,[],[[]],[[],[],[]]是不同的兩個List合并按照索引取得List中的元素,可以使用!!運算符ghci>
"Steve
Buscemi"
!!
6
ghci>
[9.4,33.2,96.2,11.2,23.25]
!!
1
List同樣也可以用來裝List,甚至是List的List的List:ghci>
let
b
=
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci>
b
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci>
b
++
[[1,1,1,1]]
[[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3],[1,1,1,1]]
ghci>
[6,6,6]:b
[[6,6,6],[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
ghci>
b
!!
2
[1,2,2,3,4]List中的List可以是不同長度,但必須得是相同的類型。List運算List內(nèi)裝有可比較的元素時,使用>和>=可以比較List的大小。ghci>
[3,2,1]
>
[2,1,0]
True
ghci>
[3,2,1]
>
[2,10,100]
True
ghci>
[3,4,2]
>
[3,4]
True
ghci>
[3,4,2]
>
[2,4]
True
ghci>
[3,4,2]
==
[3,4,2]
True
List運算head
返回一個List的頭部,也就是List的首個元素。ghci>
head
[5,4,3,2,1]
5tail
返回一個List的尾部,也就是List除去頭部之后的部分。ghci>
tail
[5,4,3,2,1]
[4,3,2,1]
last
返回一個List的最后一個元素。ghci>
last
[5,4,3,2,1]
1
List運算List運算init
返回一個List除去最后一個元素的部分。ghci>
init
[5,4,3,2,1]
[5,4,3,2]ghci>
head
[]
head,tail,last
和init
時要小心別用到空的List上ength
返回一個List的長度。ghci>
length
[5,4,3,2,1]
5
null
檢查一個List是否為空。如果是,則返回True,否則返回False。應(yīng)當避免使用xs==[]之類的語句來判斷List是否為空,使用null會更好。ghci>
null
[1,2,3]
False
ghci>
null
[]
True
reverse
將一個List反轉(zhuǎn):ghci>
reverse
[5,4,3,2,1]
[1,2,3,4,5]
List運算take
返回一個List的前幾個元素,看:ghci>
take
3
[5,4,3,2,1]
[5,4,3]
ghci>
take
1
[3,9,3]
[3]
ghci>
take
5
[1,2]
[1,2]
ghci>
take
0
[6,6,6]
[]
drop與take的用法大體相同,它會刪除一個List中的前幾個元素。ghci>
drop
100
[1,2,3,4]
[]
List運算maximum返回一個List中最大的那個元素。minimun返回最小的。ghci>
minimum
[8,4,2,1,5,6]
1
ghci>
maximum
[1,9,2,3,4]
9
sum返回一個List中所有元素的和。product返回一個List中所有元素的積。ghci>
sum
[5,2,1,6,3,2,5,7]
31
ghci>
product
[6,2,1,2]
24
ghci>
product
[1,2,5,6,7,9,2,0]
0
elem判斷一個元素是否在包含于一個List,通常以中綴函數(shù)的形式調(diào)用它。ghci>
4
`elem`
[3,4,5,6]
True
ghci>
10
`elem`
[3,4,5,6]
False
List運算如果想得到一個包含1到20之間所有數(shù)的List,你會怎么做?可以將它們一個一個用鍵盤打出來?Range是構(gòu)造List方法之一,而其中的值必須是可枚舉的,像1、2、3、4...字符同樣也可以枚舉,字母表就是A..Z所有字符的枚舉。要得到包含1到20中所有自然數(shù)的List,只要[1..20]即可使用Range手寫一個非常長的List:很笨??!
ghci>
[1..20]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
ghci>
['a'..'z']
"abcdefghijklmnopqrstuvwxyz"
ghci>
['K'..'Z']
"KLMNOPQRSTUVWXYZ“Range的特點是他還允許你指定每一步該跨多遠。譬如說,今天的問題換成是要得到1到20間所有的偶數(shù)或者3的倍數(shù)該怎樣?ghci>
[2,4..20]
[2,4,6,8,10,12,14,16,18,20]
ghci>
[3,6..20]
[3,6,9,12,15,18]僅需用逗號將前兩個元素隔開,再標上上限即可。使用Range盡管Range很聰明,但它恐怕還滿足不了一些人對它的期許。你就不能通過[1,2,4..100]這樣的語句來獲得所有2的冪。一方面是因為步長只能標明一次,另一方面就是僅憑前幾項,數(shù)組的后項是不能確定的。要得到20到1的List,[20..1]是不可以的。必須得[20,19..1]。在Range中使用浮點數(shù)要格外小心!出于定義的原因,浮點數(shù)并不精確。ghci>
[0.1,
0.3
..
1]
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999]使用Range:小心!可以不標明Range的上限,從而得到一個無限長度的List。取前24個13的倍數(shù)該怎樣?恩,你完全可以[13,26..24*13],但有更好的方法:take24[13,26..]。Haskell是惰性的,它不會對無限長度的List求值,否則會沒完沒了的。它會等著,看你會從它那兒取多少。在這里它見你只要24個元素,便欣然交差。生成無限List的函數(shù)cycle接受一個List做參數(shù)并返回一個無限Listghci>
take
12
(cycle
"LOL
")
"LOL
LOL
LOL
“repeat
接受一個值作參數(shù),并返回一個僅包含該值的無限List。這與用cycle處理單元素List差不多。ghci>
take
10
(repeat
5)
[5,5,5,5,5,5,5,5,5,5]無限listsetcomprehension十分的相似ListComprehensionghci>
[x*2
|
x
<-
[1..10]]
[2,4,6,8,10,12,14,16,18,20]
取50到100間所有除7的余數(shù)為3的元素該怎么辦?ghci>
[
x
|
x
<-
[50..100],
x
`mod`
7
==
3]
[52,59,66,73,80,87,94]
給這個comprehension再添個限制條件(predicate),它與前面的條件由一個逗號分隔。在這里,我們要求只取乘以2后大于等于12的元素。ghci>
[x*2
|
x
<-
[1..10],
x*2
>=
12]
[12,14,16,18,20]
從一個List中篩選出符合特定限制條件的操作也可以稱為過濾(flitering)。即取一組數(shù)并且按照一定的限制條件過濾它們。如我們想要一個comprehension,它能夠使List中所有大于10的奇數(shù)變?yōu)?BANG",小于10的奇數(shù)變?yōu)?BOOM",其他則統(tǒng)統(tǒng)扔掉。List過濾boomBangs
xs
=
[
if
x
<
10
then
"BOOM!"
else
"BANG!"
|
x
<-
xs,
odd
x]
comprehension的最后部分就是限制條件,使用odd
函數(shù)判斷是否為奇數(shù):返回True,就是奇數(shù),該List中的元素才被包含。ghci>
boomBangs
[7..13]
["BOOM!","BOOM!","BANG!","BANG!"]
可以加多個限制條件。若要達到10到20間所有不等于13,15或19的數(shù),可以這樣:ghci>
[
x
|
x
<-
[10..20],
x
/=
13,
x
/=
15,
x
/=
19]
多個限制條件之外,從多個List中取元素也是可以的。這樣的話comprehension會把所有的元素組合交付給我們的輸出函數(shù)。假設(shè)有兩個List,[2,5,10]和[8,10,11],要取它們所有組合的積,ghci>
[
x*y
|
x
<-
[2,5,10],
y
<-
[8,10,11]]
只取乘積大于50的結(jié)果該如何?取個包含一組名詞和形容詞的Listcomprehension吧,寫詩的話也許用得著。ghci>
let
nouns
=
["hobo","frog","pope"]
ghci>
let
adjectives
=
["lazy","grouchy","scheming"]
ghci>
[adjective
++
"
"
++
noun
|
adjective
<-
adjectives,
noun
<-
nouns]
["lazy
hobo","lazy
frog","lazy
pope","grouchy
hobo","grouchy
frog",
"grouchy
pope","scheming
hobo",
"scheming
frog","scheming
pope"]Listcomprehension我們編寫自己的length函數(shù)吧_表示我們并不關(guān)心從List中取什么值這個函數(shù)將一個List中所有元素置換為1,并且使其相加求和。得到的結(jié)果便是我們的List長度。字串也是List,完全可以使用listcomprehension來處理字除去字串中所有非大寫字母的函數(shù):Listcomprehensionlength'
xs
=
sum
[1
|
_
<-
xs]
removeNonUppercase
st
=
[
c
|
c
<-
st,
c
`elem`
['A'..'Z']]操作含有List的List,使用嵌套的Listcomprehension也是可以的。假設(shè)有個包含許多數(shù)值的List的List,讓我們在不拆開它的前提下除去其中的所有奇數(shù):含有List的Listghci>
let
xxs
=
[[1,3,5,2,3,1,2,4,5],[1,2,3,4,5,6,7,8,9],[1,2,4,2,1,6,3,1,3,2,3,6]]
ghci>
[
[
x
|
x
<-
xs,
even
x
]
|
xs
<-
xxs]
[[2,2,4],[2,4,6,8],[2,4,2,6,2,6]]
Tuple(元組)很像List--都是將多個值存入一個個體的容器。一組數(shù)字的List就是一組數(shù)字,它們的類型相同,且不關(guān)心其中包含元素的數(shù)量。而Tuple則要求你對需要組合的數(shù)據(jù)的數(shù)目非常的明確,它的類型取決于其中項的數(shù)目與其各自的類型。Tuple中的項由括號括起,并由逗號隔開。Tuple中的項不必為同一類型,在Tuple里可以存入多類型項的組合。在Haskell中表示二維矢量該如何?可以由[[1,2],[8,11],[4,5]]的代碼來實現(xiàn)。但問題在于,[[1,2],[8,11,5],[4,5]]也是同樣合法的!Tuple(元組)一個長度為2的Tuple(也可以稱作序?qū)?,Pair),是一個獨立的類型,這便意味著一個包含一組序?qū)Φ腖ist不能再加入一個三元組,所以說把原先的方括號改為圓括號使用Tuple會更好:[(1,2),(8,11),(4,5)]。[(1,2),(8,11,5),(4,5)]?Tuple(元組)Couldn't
match
expected
type
`(t,
t1)'
against
inferred
type
`(t2,
t3,
t4)'
In
the
expression:
(8,
11,
5)
In
the
expression:
[(1,
2),
(8,
11,
5),
(4,
5)]
In
the
definition
of
`it':
it
=
[(1,
2),
(8,
11,
5),
(4,
5)]
Tuple可以用來保存多個數(shù)據(jù),如,我們要表示一個人的名字與年齡,可以使用這樣的Tuple:("Christopher","Walken",55)。Tuple中也可以存儲List。使用Tuple前應(yīng)當事先明確一條數(shù)據(jù)中應(yīng)該由多少個項。每個不同長度的Tuple都是獨立的類型可以有單元素的List,但Tuple不行Tuple也可以比較大小,只是你不可以像比較不同長度的List那樣比較不同長度的TupleTuple(元組)fst
返回一個序?qū)Φ氖醉棥hci>
fst
(8,11)
8
ghci>
fst
("Wow",
False)
"Wow"snd
返回序?qū)Φ奈岔?。ghci>
snd
(8,11)
11
ghci>
snd
("Wow",
False)
FalseTuple(元組)兩個函數(shù)僅對序?qū)τ行В荒軕?yīng)用于三元組,四元組和五元組之上它取兩個List,然后將它們交叉配對,形成一組序?qū)Φ腖ist。它把元素配對并返回一個新的List。第一個元素配第一個,第二個元素配第二個..以此類推。注意,由于序?qū)χ锌梢院胁煌念愋?,zip函數(shù)可能會將不同類型的序?qū)M合在一起。函數(shù)zipghci>
zip
[1,2,3,4,5]
[5,5,5,5,5]
[(1,5),(2,5),(3,5),(4,5),(5,5)]
ghci>
zip
[1
..
5]
["one",
"two",
"three",
"four",
"five"]
[(1,"one"),(2,"two"),(3,"three"),(4,"four"),(5,"five")]
若是兩個不同長度的List會怎么樣?較長的那個會在中間斷開,去匹配較短的那個。由于Haskell是惰性的,使用zip同時處理有限和無限的List也是可以的:函數(shù)zipghci>
zip
[5,3,2,6,2,7,2,5,4,6,6]
["im","a","turtle"]
[(5,"im"),(3,"a"),(2,"turtle")]ghci>
zip
[1..]
["apple",
"orange",
"cherry",
"mango"]
[(1,"apple"),(2,"orange"),(3,"cherry"),(4,"mango")]如何取得所有三邊長度皆為整數(shù)且小于等于10,周長為24的直角三角形?首先,把所有三遍長度小于等于10的三角形都列出來:它添加一個限制條件,令其必須為直角三角形。同時也考慮上b邊要短于斜邊,a邊要短于b邊情況:一個例子ghci>
let
triangles
=
[
(a,b,c)
|
c
<-
[1..10],
b
<-
[1..10],
a
<-
[1..10]
]let
rightTriangles
=
[
(a,b,c)
|
c
<-
[1..10],
b
<-
[1..c],
a
<-
[1..b],
a^2
+
b^2
==
c^2]修改函數(shù),告訴它只要周長為24的三角形。一個例子let
rightTriangles'
=
[
(a,b,c)
|
c
<-
[1..10],
b
<-
[1..c],
a
<-
[1..b],
a^2
+
b^2
==
c^2,
a+b+c
==
24]
ghci>
rightTriangles'
[(6,8,10)]函數(shù)式編程語言的一般思路:先取一個初始的集合并將其變形,執(zhí)行過濾條件,最終取得正確的結(jié)果。第二章Haskell類型和類型類Haskell是StaticTypehaskell支持類型推導。寫下一個數(shù)字,你就沒必要另告訴haskell說“它是個數(shù)字”,它自己能推導出來。類型是每個表達式都有的某種標簽,它標明了這一表達式所屬的范疇。例如,表達式True是boolean型,"hello"是個字符串使用ghci來檢測表達式的類型。使用:t命令后跟任何可用的表達式,即可得到該表達式的類型haskell的類型ghci>
:t
'a'
'a'
::
Char
ghci>
:t
True
True
::
Bool
ghci>
:t
"HELLO!"
"HELLO!"
::
[Char]
ghci>
:t
(True,
'a')
(True,
'a')
::
(Bool,
Char)
ghci>
:t
4
==
5
4
==
5
::
Bool
函數(shù)也有類型。編寫函數(shù)時,給它一個明確的類型聲明是個好習慣函數(shù)類型removeNonUppercase
::
[Char]
->
[Char]
removeNonUppercase
st
=
[
c
|
c
<-
st,
c
`elem`
['A'..'Z']]
removeNonUppercase::String->String要是多個參數(shù)的函數(shù)該怎樣?addThree
::
Int
->
Int
->
Int
->
Int
addThree
x
y
z
=
x
+
y
+
z參數(shù)之間由->分隔,而與回傳值之間并無特殊差異?;貍髦凳亲詈笠豁棧瑓?shù)就是前三項。Int
表示整數(shù)。Int是有界的,也就是說它有上限和下限。Integer表示...厄...也是整數(shù),但它是無界的。這就意味著可以用它存放非常非常大的數(shù)Float表示單精度的浮點數(shù)。Double
表示雙精度的浮點數(shù)。Bool
表示布林Char表示一個字符。一個字符由單引號括起,一組字符的List即為字串。值,它只有兩種值:True和False。幾個常見的類型:factorial
::
Integer
->
Integer
factorial
n
=
product
[1..n]ghci>
factorial
50
00000000000Typevariablesghci>
:t
head
head
::
[a]
->
aa是啥?類型嗎?類型變量,意味著a可以是任意的類型。在Haskell中要更為強大。它可以讓我們輕而易舉地寫出類型無關(guān)的函數(shù)。使用到類型變量的函數(shù)被稱作"多態(tài)函數(shù)“head函數(shù)的類型聲明里標明了它可以取任意類型的List并回傳其中的第一個元素。在命名上,類型變量使用多個字符是合法的,不過約定俗成,通常都是使用單個字符,如a,bghci>
:t
fst
fst
::
(a,
b)
->
aa和b是不同的類型變量,但它們不一定非得是不同的類型,它只是標明了首項的類型與回傳值的類型相同。類類型定義行為的接口,可以把它看做是Java的interface。Typeclasses
101"xianyu:"
:t(==)(==)::(Eqa)=>a->a->Bool判斷相等的==運算符是函數(shù),+-*/之類的運算符也是同樣符號:它左邊的部分叫做類型約束;Eq
這一Typeclass提供了判斷相等性的接口,凡是可比較相等性的類型必屬于Eqclass表示:相等函數(shù)取兩個相同類型的值作為參數(shù)并回傳一個布林值,而這兩個參數(shù)的類型同在Eq類之中(即類型約束)Eq包含可判斷相等性的類型。提供實現(xiàn)的函數(shù)是==和/=。所以,只要一個函數(shù)有Eq類的類型限制,那么它就必定在定義中用到了==和/=。剛才說了,除函數(shù)以外的所有類型都屬于Eq,所以它們都可以判斷相等性。Ord包含可比較大小的類型。除了函數(shù)以外,我們目前所談到的所有類型都屬于Ord類。Ord包中包含了<,>,<=,>=之類用于比較大小的函數(shù)。compare函數(shù)取兩個Ord類中的相同類型的值作參數(shù),回傳比較的結(jié)果。這個結(jié)果是如下三種類型之一:GT,LT,EQ。幾個基本的Typeclassghci>
:t
(>)
(>)
::
(Ord
a)
=>
a
->
a
->
Boolghci>
"Abrakadabra"
<
"Zebra"
True
ghci>
"Abrakadabra"
`compare`
"Zebra"
LT
ghci>
5
>=
2
True
ghci>
5
`compare`
3
GT值為Showtype的實例可用字串表示,將給定的值作為字符串打印出來:Read
是與Show相反的Typeclass。read函數(shù)可以將一個字串轉(zhuǎn)為Read的某成員類型ShowandReadtypeclass嘗試read"4"又會怎樣?Readtypeclassghci>
:t
read
read
::
(Read
a)
=>
String
->
a不知該表達式的類型:需要在一個表達式后跟::的類型簽名,以明確其類型。ghci>
read
"5"
::
Int
5
ghci>
read
"5"
::
Float
5.0
ghci>
(read
"5"
::
Float)
*
4
20.0
ghci>
read
"[1,2,3,4]"
::
[Int]
[1,2,3,4]
ghci>
read
"(3,
'a')"
::
(Int,
Char)
(3,
'a')有序排列,可被枚舉。Enum類存在的主要好處就在于我們可以在Range中用到它的值。每個值都有后繼子(successer)和前置子(predecesor),分別可以通過succ函數(shù)和pred函數(shù)得到。該Typeclass包含的類型有:(),Bool,Char,Ordering,Int,Integer,Float和Double。Enum類型ghci>
['a'..'e']
"abcde"
ghci>
[LT
..
GT]
[LT,EQ,GT]
ghci>
[3
..
5]
[3,4,5]
ghci>
succ
'B'
'C'都有一個上限和下限Boundedtypeghci>
minBound
::
Int
-2147483648
ghci>
maxBound
::
Char
'\1114111'
ghci>
maxBound
::
Bool
True
ghci>
minBound
::
Bool
FalseminBound和maxBound函數(shù)它們的類型都是(Boundeda)=>a。它們都是多態(tài)常量!如果其中的項都屬于BoundedTypeclass,那么該Tuple也屬于Boundedghci>
maxBound
::
(Bool,
Int,
Char)
(True,2147483647,'\1114111')數(shù)字的Typeclass,它的成員類型都具有數(shù)字的特征;ghci>
:t
20
20
::
(Num
t)
=>
t所有的數(shù)字都是多態(tài)常量,它可以作為所有NumTypeclass中的成員類型那樣運算,包括Int,Integer,Float,orDouble。例如檢查*運算符:"xianyu:"
:t(*)(*)::(Numa)=>a->a->aNumtype(5::Int)*(6::Integer)Integral
同樣是表示數(shù)字的Typeclass。Num包含所有的數(shù)字:實數(shù)和整數(shù)。而Intgral僅包含整數(shù),其中的成員類型有Int和Integer。floating
僅包含浮點類型:Float和Double。有個函數(shù)在處理數(shù)字時會非常有用,fromIntegral如果取了一個List長度的值再給它加3.2其他類型fromIntegral::(Numb,Integrala)=>a->bghci>fromIntegral(length[1,2,3,4])+3.27.2第四章函數(shù)的語法模式匹配通過檢查數(shù)據(jù)的特定結(jié)構(gòu)來檢查其是否匹配,并按模式從中取得數(shù)據(jù)??梢詾椴煌哪J椒謩e定義函數(shù)本身,這就讓代碼更加簡潔易讀。按模式從中取得數(shù)據(jù)??梢云ヅ湟磺袛?shù)據(jù)類型---數(shù)字,字符,List,元組,等等模式匹配(Patternmatching)檢查我們傳給它的數(shù)字是不是7。調(diào)用lucky時,模式會從上至下進行檢查,一旦有匹配,那對應(yīng)的函數(shù)體就被應(yīng)用了。這個模式中的唯一匹配是參數(shù)為7,如果不是7,就轉(zhuǎn)到下一個模式,它匹配一切數(shù)值并將其綁定為x簡單例子lucky
::
(Integral
a)
=>
a
->
String
lucky
7
=
"LUCKY
NUMBER
SEVEN!"
lucky
x
=
"Sorry,
you're
out
of
luck,
pal!"
要個分辨1到5中的數(shù)字,而無視其它數(shù)的函數(shù)該怎么辦?簡單例子sayMe
::
(Integral
a)
=>
a
->
String
sayMe
1
=
"One!"
sayMe
2
=
"Two!"
sayMe
3
=
"Three!"
sayMe
4
=
"Four!"
sayMe
5
=
"Five!"
sayMe
x
=
"Not
between
1
and
5"
如果我們把最后匹配一切的那個模式挪到最前,它的結(jié)果就是--------把n的階乘定義成product[1..n]遞歸實現(xiàn),先說明0的階乘是1,再說明每個正整數(shù)的階乘都是這個數(shù)與它前驅(qū)(predecessor)對應(yīng)的階乘的積。模式的順序重要:它總是優(yōu)先匹配最符合的那個,最后才是那個萬能的。簡單例子factorial
::
(Integral
a)
=>
a
->
a
factorial
0
=
1
factorial
n
=
n
*
factorial
(n
-
1)
拿個它沒有考慮到的字符去調(diào)用它,你就會看到這個:模式匹配也會失敗charName
::
Char
->
String
charName
'a'
=
"Albert"
charName
'b'
=
"Broseph"
charName
'c'
=
"Cecil"
ghci>
charName
'a'
"Albert"
ghci>
charName
'b'
"Broseph"
ghci>
charName
'h'
"***
Exception:
tut.hs:(53,0)-(55,21):
Non-exhaustive
patterns
in
function
charName
一定要留一個萬能匹配的模式寫個函數(shù),將二維空間中的矢量相加?采用模式匹配:Tuple與模式匹配addVectors
::
(Num
a)
=>
(a,
a)
->
(a,
a)
->
(a,
a)
addVectors
a
b
=
(fst
a
+
fst
b,
snd
a
+
snd
b)
addVectors
::
(Num
a)
=>
(a,
a)
->
(a,
a)
->
(a,
a)
addVectors
(x1,
y1)
(x2,
y2)
=
(x1
+
x2,
y1
+
y2)
對List本身也可以使用模式匹配。你可以用[]或:來匹配它。因為[1,2,3]本質(zhì)就是1:2:3:[]的語法糖。你也可以使用前一種形式,像x:xs這樣的模式可以將List的頭部綁定為x,尾部綁定為xs。如果這List只有一個元素,那么xs就是一個空List。x:xs這模式的應(yīng)用非常廣泛,尤其是遞歸函數(shù)。不過它只能匹配長度大于等于1的List。List
模式匹配ghci>
let
xs
=
[(1,3),
(4,3),
(2,4),
(5,3),
(5,6),
(3,1)]
ghci>
[a+b
|
(a,b)
<-
xs]
[4,7,6,8,11,4]用非標準的英語給我們展示List的前幾項。實現(xiàn)自己的head函數(shù)。head'
::
[a]
->
a
head'
[]
=
error
"Can't
call
head
on
an
empty
list,
dummy!"
head'
(x:_)
=
x
tell
::
(Show
a)
=>
[a]
->
String
tell
[]
=
"The
list
is
empty"
tell
(x:[])
=
"The
list
has
one
element:
"
++
show
x
tell
(x:y:[])
=
"The
list
has
two
elements:
"
++
show
x
++
"
and
"
++
show
y
tell
(x:y:_)
=
"This
list
is
long.
The
first
two
elements
are:
"
++
show
x
++
"
and
"
++
show
y
函數(shù)很相似。先定義好未知輸入的結(jié)果---空List,這也叫作邊界條件。再在第二個模式中將這List分割為頭部和尾部。說,List的長度就是其尾部的長度加1。匹配頭部用的_,因為我們并不關(guān)心它的值。同時顧及了List所有可能的模式:第一個模式匹配空List,第二個匹配任意的非空List。模式匹配和遞歸:length函數(shù)length'
::
(Num
b)
=>
[a]
->
b
length'
[]
=
0
length'
(_:xs)
=
1
+
length'
xs
我們知道空List的和是0,就把它定義為一個模式。我們也知道一個List的和就是頭部加上尾部的和的和。實現(xiàn)sumsum'
::
(Num
a)
=>
[a]
->
a
sum'
[]
=
0
sum'
(x:xs)
=
x
+
sum'
xs
就是將一個名字和@置于模式前,可以在按模式分割什么東西時仍保留對其整體的引用。如這個模式xs@(x:y:ys),它會匹配出與x:y:ys對應(yīng)的東西,同時你也可以方便地通過xs得到整個List,而不必在函數(shù)體中重復(fù)x:y:ys使用as模式通常就是為了在較大的模式中保留對整體的引用,從而減少重復(fù)性的工作。as模式capital
::
String
->
String
capital
""
=
"Empty
string,
whoops!"
capital
all@(x:xs)
=
"The
first
letter
of
"
++
all
++
"
is
"
++
[x]
guard用來檢查一個值的某項屬性是否為真。if語句;處理多個條件分支時guard的可讀性要高些,并且與模式匹配契合的很好。先看一個用到guard的函數(shù)。它會依據(jù)你的BMI值(bodymassindex,身體質(zhì)量指數(shù))來不同程度地侮辱你。BMI值即為體重除以身高的平方。如果小于18.5,就是太瘦;如果在18.5到25之間,就是正常;25到30之間,超重;如果超過30,肥胖。guardbmiTell
::
(RealFloat
a)
=>
a
->
String
bmiTell
bmi
|
bmi
<=
18.5
=
"You're
underweight,
you
emo,
you!"
|
bmi
<=
25.0
=
"You're
supposedly
normal.
Pffft,
I
bet
you're
ugly!"
|
bmi
<=
30.0
=
"You're
fat!
Lose
some
weight,
fatty!"
|
otherwise
=
"You're
a
whale,
congratulations!"
guard由跟在函數(shù)名及參數(shù)后面的豎線標志,通常他們都是靠右一個縮進排成一列。一個guard就是一個布爾表達式,如果為真,就使用其對應(yīng)的函數(shù)體。如果為假,就送去見下一個guard,如之繼續(xù)。if-else的大樹比較雜亂,若是出現(xiàn)問題會很難發(fā)現(xiàn),guard對此則十分清楚。最后的那個guard往往都是otherwise,它的定義就是簡單一個otherwise=True,捕獲一切。guardguard可以在含有任意數(shù)量參數(shù)的函數(shù)中使用。另外一個簡單例子:guardbmiTell
::
(RealFloat
a)
=>
a
->
a
->
String
bmiTell
weight
height
|
weight
/
height
^
2
<=
18.5
=
"You're
underweight,
you
emo,
you!"
|
weight
/
height
^
2
<=
25.0
=
"You're
supposedly
normal.
Pffft,
I
bet
you're
ugly!"
|
weight
/
height
^
2
<=
30.0
=
"You're
fat!
Lose
some
weight,
fatty!"
|
otherwise
=
"You're
a
whale,
congratulations!"
max'
::
(Ord
a)
=>
a
->
a
->
a
max'
a
b
|
a
>
b
=
a
|
otherwise
=
b
修改前面例子,去掉重復(fù)計算:避免了重復(fù)。如果我們打算換種方式計算bmi,只需進行一次修改就行了關(guān)鍵字wherebmiTell
::
(RealFloat
a)
=>
a
->
a
->
String
bmiTell
weight
height
|
bmi
<=
18.5
=
"You're
underweight,
you
emo,
you!"
|
bmi
<=
25.0
=
"You're
supposedly
normal.
Pffft,
I
bet
you're
ugly!"
|
bmi
<=
30.0
=
"You're
fat!
Lose
some
weight,
fatty!"
|
otherwise
=
"You're
a
whale,
congratulations!"
where
bmi
=
weight
/
height
^
2函數(shù)在where綁定中定義的名字只對本函數(shù)可見,因此我們不必擔心它會污染其他函數(shù)的命名空間。where綁定也可以使用模式匹配!再做下修改bmiTell
::
(RealFloat
a)
=>
a
->
a
->
String
bmiTell
weight
height
|
bmi
<=
skinny
=
"You're
underweight,
you
emo,
you!"
|
bmi
<=
normal
=
"You're
supposedly
normal.
Pffft,
I
bet
you're
ugly!"
|
bmi
<=
fat
=
"You're
fat!
Lose
some
weight,
fatty!"
|
otherwise
=
"You're
a
whale,
congratulations!"
where
bmi
=
weight
/
height
^
2
skinny
=
18.5
normal
=
25.0
fat
=
30.0where
bmi
=
weight
/
height
^
2
(skinny,
normal,
fat)
=
(18.5,
25.0,
30.0)
定義函數(shù):讓它告訴我們姓名的首字母;where綁定可以定義名字,也可以定義函數(shù)。計算一組bmi的函數(shù):例子initials
::
String
->
String
->
String
initials
firstname
lastname
=
[f]
++
".
"
++
[l]
++
"."
where
(f:_)
=
firstname
(l:_)
=
lastname
calcBmis
::
(RealFloat
a)
=>
[(a,
a)]
->
[a]
calcBmis
xs
=
[bmi
w
h
|
(w,
h)
<-
xs]
where
bmi
weight
height
=
weight
/
height
^
2
let綁定與where綁定很相似。where綁定是在函數(shù)底部定義名字,對包括所有g(shù)uard在內(nèi)的整個函數(shù)可見。let綁定則是個表達式,允許你在任何位置定義局部變量,而對不同的guard不可見。Let的格式:let[bindings]in[expressions]。在let中綁定的名字僅對in部分可見。let里面定義的名字也得對齊到一列。關(guān)鍵字Letcylinder
::
(RealFloat
a)
=>
a
->
a
->
a
cylinder
r
h
=
let
sideArea
=
2
*
pi
*
r
*
h
topArea
=
pi
*
r
^2
in
sideArea
+
2
*
topArea
let綁定本身是個表達式,而where綁定則是個語法結(jié)構(gòu)let也可以定義局部函數(shù):要在一行中綁定多個名字Let與whereghci>
4
*
(let
a
=
9
in
a
+
1)
+
2
42
ghci>
[let
square
x
=
x
*
x
in
(square
5,
square
3,
square
2)]
[(25,9,4)]
ghci>
(let
a
=
100;
b
=
200;
c
=
300
in
a*b*c,
let
foo="Hey
";
bar
=
"there!"
in
foo
++
bar)
(6000000,"Hey
there!")
把let綁定放到ListComprehensionlet中綁定的名字在輸出函數(shù)及限制條件中都可見let綁定中使用模式匹配ghci>
(let
(a,b,c)
=
(1,2,3)
in
a+b+c)
*
100
600
calcBmis
::
(RealFloat
a)
=>
[(a,
a)]
->
[a]
calcBmis
xs
=
[bmi
|
(w,
h)
<-
xs,
let
bmi
=
w
/
h
^
2]calcBm
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 協(xié)助收購合同范例
- 作家助手簽約標準合同范本
- 兼職短期有效合同范本
- 加盟協(xié)議英文合同范本
- 單位借款三方協(xié)議合同范本
- 劇本買賣合同范本
- 單位超市采購合同范本
- 個人承包勞務(wù)合同范本
- 單位廚師勞務(wù)合同范本
- 鄉(xiāng)村公路開挖合同范本
- SCI期刊的名稱縮寫與全稱對照表
- 人本位醫(yī)療培訓課件
- 《供應(yīng)鏈管理》課程整體設(shè)計
- 水利工程危險源辨識評價及風險管控清單
- 桂西北丹池成礦帶主要金屬礦床成礦特征及成礦規(guī)律
- 申論范文:社區(qū)微治理 共建美好家園
- 高等工程熱力學教案課件
- 2023年征信知識競賽基礎(chǔ)題考試復(fù)習題庫(帶答案)
- 汽車機械基礎(chǔ)PPT(第3版)全套完整教學課件
- 醫(yī)療器械質(zhì)量管理制度
- 【招標控制價編制研究文獻綜述(論文)4800字】
評論
0/150
提交評論