前言
这个学期开始学习Haskelll(主要关于Codeworld和ghci),感觉很多东西和OOP不一样,最近感觉也应该开始记录点东西,以备日后可以查看,提高学习的效率。
1.animationOf:: (Double -> Picture)
这个函数接收一个 Double -> Picture 型的函数作为参数,并把此函数中的double变量视作时间变量,时间变量名字通常情况下命名为t,但也可以用其它任意名称。
2.函数作用的先后顺序
以下代码
import CodeWorld
main::IO()
main = drawingOf scene
scene :: Picture
scene = (colored red (solidRectangle 2 2))& (colored blue (solidRectangle 3 3))
可以看到蓝色的矩形被红色矩形盖住了一部分,如果把他们的顺序对调即最后一句改为
scene = (colored blue (solidRectangle 2 2))&(colored red (solidRectangle 3 3))
就变成了红色被蓝色矩形盖住了一部分。
类似的还有
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
此处可以看到球以原点为圆心,2为半径旋转。
如果将rotated和transalated对调即
scene t = translated 2 0 (rotated t ((circle 1)&polyline [(0,-1),(0,1)]))&coordinatePlane
可以看到圆在(2,0)处自转。
为了更好的说明,rotated函数在codeworld中的解释如下
rotated :: HasCallStack => Double -> Picture -> Picture#
A picture rotated by this angle.
Angles are in radians.
它接收一个double型变量,并以这个变量作为弧度,以原点为圆心做旋转。正如第一个例子,我们对处于(2,0)的一个圆进行旋转,它就会围绕着原点转。但如果我们先构造了一个旋转的圆,之后对他进行平移,那么它将不再表现得与之前一样。这是因为rotated函数以作用完毕,它返回的Picture变量作为了translated的参数。
通过上面两个例子可以知道,函数的作用是在返回的那一刻就结束了,所以通过不同的顺序调用函数,会产生不同的效果。
3.应用List绘制多个图形
以下代码展示了绘制多个图形的一个方法
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))
重点为这一句
f ((a,b):as) = (translated (fromIntegral (fst(a,b))) 0.0 (colored (snd(a,b)) (solidRectangle 5 5))):(f as)
更直观的来说,针对绘制多个图形我们可以用如下几种方式
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里,每一个x都会创造一个新的矩形。
为了让矩形区别的更加明显,我们给矩形涂上颜色。
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)
有意思的是,如果用如上代码绘制图形,你可能期待它应该为4个矩形涂上4中不同的颜色,但却只看到了红色的矩形。
这是因为,每个x和y都会生成一个新的矩形,上面的代码实际上是在每个位置生成了4种不同颜色的矩形,由于遮挡关系,你只会看到第一个红色的矩形!
那么如何让每一个x和y产生对应关系呢?我们可以选择tuple来处理
上面代码修改为
scene = pictures[translated (fromIntegral x) 0.0 (colored y (solidRectangle 5 5))
|(x,y)<-zip [0,3..12] [red,green,purple,orange]]
通过规定了x,y的对应关系,我们为每一个x涂上了颜色。
4.Recursion
由于Haskell是函数式编程,我们在函数定义中使用如i=i+1,n=n+f(x)之类的语句进行迭代。为了处理这种情况,我们需要使用另一种形式的recursion。
例如阶乘我们可以写成
f::Integer->Integer
f 0 = 1
f n = n * f n-1
由于我们定义了f 0的值,所以当迭代进行到0是将赋入1完成n*(n-1)*..*1的计算。
我们考虑另外一个问题:
{- 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
-}
对于分离成几个数字块,我们可以使用`quotRem`方法,它返回(q,r),q为商,r为余数。例如,1234`quotRem`我们将会得到(12,34),此时我们将它分离了一次,再对12进行一次处理即可得到(0,12)。至此,我们将1234分离为,12与34.
对应的数字块之间的加法我们应该如何实现?由于调用的函数在返回值之后就不再进行运算,我们需要对函数进行recursion处理。一个基本的OOP思路可以是在loop中进行运算,用一个参数存储所需要的中间值。但是由于Haskell不支持类似的操作。我们可以用一下迭代来实现:
normalDigsum::Integer->Integer->Integer
normalDigsum 0 p = 0
normalDigsum n p = r + (normalDigsum q p)
where (q,r) = n`quotRem`(10^p)
仔细来看,normalDigsum将求和变为了r0+(r1+(r2+..)),这是可行的。但是对于alternat类型来说它却变成了r0+(r1-(r2+..))负号是错误的,那么我们该如规避?
我们可以为alternat型设立一个额外的参数,它专门用于存储结果,可以避免此类情况的发生,我喜欢叫它为“记忆参数”。实现代码如下:
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)
当数字块源为0时代表结束,我们可以返回c。在运算过程中,针对每一次不同的运算,在记忆参数中进行加法或者减法,由于每次调用函数时都会直接传递给它上一次的结果,因而保证了负号的准确性。
5.Indexitis与wholemeal programming
Indexitis是一个混合的自创词汇,Index+itis可以译作索引炎。它的意思是指需要对索引进行繁琐的操作,尤其会在修改时因为索引而引起错误。
wholemeal programming就是它的反意,在编程时对整个所需要处理的数据进行限定,在haskell中即利用List来行使loop职能。由于List修改时的简易,它会减少因为index错误而引发的种种问题。