Haskell笔记1

前言

这个学期开始学习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错误而引发的种种问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,877评论 0 38
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,193评论 0 13
  • CHAPTER 1: INTRODUCTION 第一章:简介 In this chapter, we discus...
    哈小奇阅读 1,038评论 2 1
  • 一、进程和线程 1.什么是进程 进程是指在系统中正在运行的一个应用程序 每个进程之间是独立的,每个进程均运行在其专...
    ZYZZZ阅读 263评论 1 1
  • 311 如果你足够优秀,一定要去找那些和你一样优秀或比你优秀的人才,如果你不够优秀,是很难吸引到优秀人才的。再说了...
    e1f1b6c637ae阅读 258评论 0 0