前言
這個(gè)學(xué)期開始學(xué)習(xí)Haskelll(主要關(guān)于Codeworld和ghci),感覺很多東西和OOP不一樣,最近感覺也應(yīng)該開始記錄點(diǎn)東西,以備日后可以查看,提高學(xué)習(xí)的效率。
1.animationOf:: (Double?->?Picture)
這個(gè)函數(shù)接收一個(gè)?Double?->?Picture?型的函數(shù)作為參數(shù),并把此函數(shù)中的double變量視作時(shí)間變量,時(shí)間變量名字通常情況下命名為t,但也可以用其它任意名稱。
2.函數(shù)作用的先后順序
以下代碼
import CodeWorld
main::IO()
main = drawingOf scene
scene? :: Picture
scene = (colored red (solidRectangle 2 2))& (colored blue (solidRectangle 3 3))
可以看到藍(lán)色的矩形被紅色矩形蓋住了一部分,如果把他們的順序?qū)φ{(diào)即最后一句改為
scene = (colored blue (solidRectangle 2 2))&(colored red (solidRectangle 3 3))
就變成了紅色被藍(lán)色矩形蓋住了一部分。
類似的還有
import CodeWorld
main::IO()
main = animationOf scene
scene :: Double->Picture
scene t = rotated t?(translated 2 0 ((circle 1)&polyline [(0,-1),(0,1)]))&coordinatePlane
此處可以看到球以原點(diǎn)為圓心,2為半徑旋轉(zhuǎn)。
如果將rotated和transalated對(duì)調(diào)即
scene t = translated 2 0 (rotated t ((circle 1)&polyline [(0,-1),(0,1)]))&coordinatePlane
可以看到圓在(2,0)處自轉(zhuǎn)。
為了更好的說(shuō)明,rotated函數(shù)在codeworld中的解釋如下
rotated?:: HasCallStack => Double ->?Picture?->?Picture#
A picture rotated by this angle.
Angles are in radians.
它接收一個(gè)double型變量,并以這個(gè)變量作為弧度,以原點(diǎn)為圓心做旋轉(zhuǎn)。正如第一個(gè)例子,我們對(duì)處于(2,0)的一個(gè)圓進(jìn)行旋轉(zhuǎn),它就會(huì)圍繞著原點(diǎn)轉(zhuǎn)。但如果我們先構(gòu)造了一個(gè)旋轉(zhuǎn)的圓,之后對(duì)他進(jìn)行平移,那么它將不再表現(xiàn)得與之前一樣。這是因?yàn)閞otated函數(shù)以作用完畢,它返回的Picture變量作為了translated的參數(shù)。
通過(guò)上面兩個(gè)例子可以知道,函數(shù)的作用是在返回的那一刻就結(jié)束了,所以通過(guò)不同的順序調(diào)用函數(shù),會(huì)產(chǎn)生不同的效果。
3.應(yīng)用List繪制多個(gè)圖形
以下代碼展示了繪制多個(gè)圖形的一個(gè)方法
import CodeWorld
primes :: [Integer]
primes = sieve [2..]
? where sieve cs =
? ? ? ? ? let p = head cs
? ? ? ? ? in [ p ] ++ sieve [ c | c <- tail cs, c `mod` p /= 0 ]
g::[a]->[b]->[(a,b)]
g (x:xs) (y:ys) = (x,y):(zip xs ys)
g _ [] = []
g [] _ = []
f::[(Integer,Color)]->[Picture]
f ((a,b):as) = (translated (fromIntegral (fst(a,b))) 0.0 (colored (snd(a,b)) (solidRectangle 5 5))):(f as)
f [] = []
scene :: Int -> Picture
scene n = pictures(f(reverse (g (take n primes) (take n assortedColors))))
main :: IO ()
main = drawingOf (coordinatePlane & (scene 6))
重點(diǎn)為這一句
f ((a,b):as) = (translated (fromIntegral (fst(a,b))) 0.0 (colored (snd(a,b)) (solidRectangle 5 5))):(f as)
更直觀的來(lái)說(shuō),針對(duì)繪制多個(gè)圖形我們可以用如下幾種方式
import CodeWorld
scene :: Int -> Picture
scene n = pictures[translated (fromIntegral x) 0.0 (rectangle 5 5)|x<-[0,3..3*n]]
main :: IO ()
main = drawingOf (coordinatePlane & (scene 6))
繪制了平移的圖形,其中在List里,每一個(gè)x都會(huì)創(chuàng)造一個(gè)新的矩形。
為了讓矩形區(qū)別的更加明顯,我們給矩形涂上顏色。
import CodeWorld
scene :: Picture
scene = pictures[translated (fromIntegral x) 0.0 (colored y (solidRectangle 5 5))
? ? ? ? ? ? ? ? |x<-[0,3..12],y<-[red,green,purple,orange]]
main :: IO ()
main = drawingOf (coordinatePlane & scene)
有意思的是,如果用如上代碼繪制圖形,你可能期待它應(yīng)該為4個(gè)矩形涂上4中不同的顏色,但卻只看到了紅色的矩形。
這是因?yàn)?,每個(gè)x和y都會(huì)生成一個(gè)新的矩形,上面的代碼實(shí)際上是在每個(gè)位置生成了4種不同顏色的矩形,由于遮擋關(guān)系,你只會(huì)看到第一個(gè)紅色的矩形!
那么如何讓每一個(gè)x和y產(chǎn)生對(duì)應(yīng)關(guān)系呢?我們可以選擇tuple來(lái)處理
上面代碼修改為
scene = pictures[translated (fromIntegral x) 0.0 (colored y (solidRectangle 5 5))
? ? ? ? ? ? ? ? |(x,y)<-zip [0,3..12] [red,green,purple,orange]]
通過(guò)規(guī)定了x,y的對(duì)應(yīng)關(guān)系,我們?yōu)槊恳粋€(gè)x涂上了顏色。
4.Recursion
由于Haskell是函數(shù)式編程,我們?cè)诤瘮?shù)定義中使用如i=i+1,n=n+f(x)之類的語(yǔ)句進(jìn)行迭代。為了處理這種情況,我們需要使用另一種形式的recursion。
例如階乘我們可以寫成
f::Integer->Integer
f 0 = 1
f n = n * f n-1
由于我們定義了f 0的值,所以當(dāng)?shù)M(jìn)行到0是將賦入1完成n*(n-1)*..*1的計(jì)算。
我們考慮另外一個(gè)問(wèn)題:
{- A block digit sum combines several digits before summing up,
- beginning with the last position of the original number.
- For example, the 3-block digit sum of 1234567 is the number
- 1 + 234 + 567 = 802.
- An alternating digit sum switches between addition and
- subtraction, here in a fashion such that altogether no
- negative number is obtained. For example, the alternating
- 3-block digit sum of 1234567 is the number 1 - 234 + 567 = 334,
- while the alternating 2-block digit sum of 54321 is obtained
- as -5 + 43 - 21 = 17.
-
- Write a function that computes such generalized digit sums.
- The function is controlled by arguments for the block length
- and (as a Boolean value) the information whether or not an
- alternating digit sum is to be computed.
- Thus, for example:
-
-? genDigsum 3 False 1234567 = 802
-? genDigsum 3 True? 1234567 = 334
-? genDigsum 2 True? 54321? = 17
-}
對(duì)于分離成幾個(gè)數(shù)字塊,我們可以使用`quotRem`方法,它返回(q,r),q為商,r為余數(shù)。例如,1234`quotRem`我們將會(huì)得到(12,34),此時(shí)我們將它分離了一次,再對(duì)12進(jìn)行一次處理即可得到(0,12)。至此,我們將1234分離為,12與34.
對(duì)應(yīng)的數(shù)字塊之間的加法我們應(yīng)該如何實(shí)現(xiàn)?由于調(diào)用的函數(shù)在返回值之后就不再進(jìn)行運(yùn)算,我們需要對(duì)函數(shù)進(jìn)行recursion處理。一個(gè)基本的OOP思路可以是在loop中進(jìn)行運(yùn)算,用一個(gè)參數(shù)存儲(chǔ)所需要的中間值。但是由于Haskell不支持類似的操作。我們可以用一下迭代來(lái)實(shí)現(xiàn):
normalDigsum::Integer->Integer->Integer
normalDigsum 0 p = 0
normalDigsum n p = r + (normalDigsum q p)
? ? ? ? ? ? ? ? where (q,r) = n`quotRem`(10^p)
仔細(xì)來(lái)看,normalDigsum將求和變?yōu)榱藃0+(r1+(r2+..)),這是可行的。但是對(duì)于alternat類型來(lái)說(shuō)它卻變成了r0+(r1-(r2+..))負(fù)號(hào)是錯(cuò)誤的,那么我們?cè)撊缫?guī)避?
我們可以為alternat型設(shè)立一個(gè)額外的參數(shù),它專門用于存儲(chǔ)結(jié)果,可以避免此類情況的發(fā)生,我喜歡叫它為“記憶參數(shù)”。實(shí)現(xiàn)代碼如下:
alternatDigsum::Integer->Integer->Bool->Integer->Integer
alternatDigsum 0 p b c = c
alternatDigsum n p b c = if b==True
? ? ? ? ? ? ? ? ? ? then alternatDigsum q p (not b) (c+r)
? ? ? ? ? ? ? ? ? ? else alternatDigsum q p (not b) (c-r)
? ? ? ? ? ? ? ? ? ? where (q,r) = n`quotRem`(10^p)
當(dāng)數(shù)字塊源為0時(shí)代表結(jié)束,我們可以返回c。在運(yùn)算過(guò)程中,針對(duì)每一次不同的運(yùn)算,在記憶參數(shù)中進(jìn)行加法或者減法,由于每次調(diào)用函數(shù)時(shí)都會(huì)直接傳遞給它上一次的結(jié)果,因而保證了負(fù)號(hào)的準(zhǔn)確性。
5.Indexitis與wholemeal programming
Indexitis是一個(gè)混合的自創(chuàng)詞匯,Index+itis可以譯作索引炎。它的意思是指需要對(duì)索引進(jìn)行繁瑣的操作,尤其會(huì)在修改時(shí)因?yàn)樗饕疱e(cuò)誤。
wholemeal programming就是它的反意,在編程時(shí)對(duì)整個(gè)所需要處理的數(shù)據(jù)進(jìn)行限定,在haskell中即利用List來(lái)行使loop職能。由于List修改時(shí)的簡(jiǎn)易,它會(huì)減少因?yàn)閕ndex錯(cuò)誤而引發(fā)的種種問(wèn)題。