这个系列会记录个人学习Haskell的过程,并假设读者至少了解一门OO语言和数据结构
课程地址:https://www.seas.upenn.edu/~cis194/fall16/index.html
online IDE(vital, 因为用到了提供的库):https://code.world/haskell
Haskell 是一种函数式编程语言,Functional, Pure, Lazy and Statically typed
第一段程序是画一个点出来
非常简单,主要是熟悉这门语言的运用
import CodeWorld
--引入CodeWorld库
ourPicture :: Picture
--这段含义是"ourPicture has type Picture"
ourPicture = solidCircle 1
main :: IO ()
--main 和C里的main相似,但注意类型是IO() 可能会导致违反 Pure
main = drawingOf ourPicture
要点1 :: 解释为 “has type” 声明类型使用,注意类型是固定的
要点2 ourPicture 声明为 solidCircle 1 之后不能再被声明,否则会报错"Multiple Declarations"重复声明 Haskell 中的=不是赋值,在其他常见的编程语言中重复赋值是可以接受的,因为赋值仅仅是把一个变量跟新值绑定而已。在Haskell中,=更接近 “定义”
接下来详细看下CodeWorld 提供的函数SolidCircle, 点击Online IDE 左下角的guide, search for solidCircle
solidCircle :: HasCallStack => Double -> Picture
A solid circle, with this radius
注意=> 和 -> 的区别, => 的左边是约束类型,右边是实际类型, -> means "the arrow" 用来描述函数,在这个例子中解释为solidCircle函数输入为一个Double类型,输出一个Picture类型
注意Haskell的Functional特性,函数为第一公民,请注意以下区别
solidCircle (1+1)
solidCircle 1+1
第二行报错:No instance for (Num Picture) arising from a use of ‘+’
第二行会报错,因为函数是第一公民,编译器会先执行solidCircle 1,返回一个Picture。此时语句变成了Picture+1,注意不能把不同类型(看报错信息,Num Picture)相加,想要达到目的必须用小括号提高优先级
画一个黑点太单调了,接下来我们要把它变的好看一些
更改代码中ourPicture的声明,再次运行,点变成绿的了
ourPicture = colored green (solidCircle 1)
这里用到了一个新函数colored,查看IDE的guide, 查找colored,以下为描述
colored :: HasCallStack => Color -> Picture -> Picture
A picture drawn entirely in this color.
colored函数接受两个输入,类型分别是Color 和 Picture,返回一个Picture,所以上面那一行代码会先执行小括号中的solidCircle 1 返回一个Picture类型参数,这样就满足了colored的输入要求“Color -> Picture”,注意参数是用空格隔开的,而不是其他常见语言中的 ","
下面需要画两个点出来
将ourPicture声明更改,然后就看见两个不同颜色的点了
ourPicture = colored green (solidCircle 1) & solidCircle 2
查看guide中对&的描述,很明显接受两个Picture类型输入返回一个Picture,注意执行顺序为先执行小括号,然后执行所有函数,再执行&,所以只有一个点是绿的。
(&) :: Picture -> Picture -> Picture
Binary composition of pictures.
接下来我们要移动这些点,并且画一个红绿灯出来
这里注意负值在Haskell中的写法,如果直接写x=-1是要报错的,因为"-"被看作一种操作符,所以必须写成x=(0-1)或者简写为x=(-1)
botCircleGreen = colored green (translated 0 (-1.5) (solidCircle 1))
topCircleRed = colored red (translated 0 1.5 (solidCircle 1))
frame = rectangle 2.5 5.5
trafficLight = botCircleGreen & topCircleRed & frame
ourPicture :: Picture
ourPicture = trafficLight
这里用到了一个新的函数translated, 查看guide,可知它接受三个参数,前两个Double为x,y方向上移动的距离,第三个为原来的Picture,最后返回一个新的Picture
translated :: HasCallStack => Double -> Double -> Picture -> Picture
A picture drawn translated in these directions.
画出来之后如下图
接下来定义自己的函数
代码如下
botCircle c = colored c (translated 0 (-1.5) (solidCircle 1))
topCircle c = colored c (translated 0 1.5 (solidCircle 1))
frame = rectangle 2.5 5.5
trafficLight True = botCircle green & topCircle black & frame
trafficLight False = botCircle black & topCircle red & frame
ourPicture :: Picture
ourPicture = trafficLight True
这里定义了3个函数botCircle :: Color -> Picture, topCircle :: Color -> Picture 和 trafficLight :: Bool -> Picture,上面代码执行完之后会得到一个上面绿下面黑的红绿灯
接下来要让红绿灯动起来
做下面的更改
import CodeWorld
botCircle c = colored c (translated 0 (-1.5) (solidCircle 1))
topCircle c = colored c (translated 0 1.5 (solidCircle 1))
frame = rectangle 2.5 5.5
trafficLight True = botCircle green & topCircle black & frame
trafficLight False = botCircle black & topCircle red & frame
trafficController :: Double -> Picture
trafficController t
| round (t/3) `mod` 2 == 0 = trafficLight True
| otherwise = trafficLight False
main :: IO ()
main = animationOf trafficController
注意main函数,更改为animationOf,查看guide发现该函数提供Double类型时间,接受第一个参数Picture,返回IO()。所以函数trafficController的输入Double其实是 animationOf 提供的,这部分需要仔细思考一下。
Haskell里和数字有关的类型和操作
Int: Int 最大值和操作系统的位数有关
Integer: Integer 只和机器的内存大小有关
Float和Double: 单双精度浮点数
操作:+ - * 对所有的数字类型都有效, /只对Double有效,对于整数请使用 div (除法) mod(取余),下面是一个直观的例子(in ghci)
Prelude> 6/2
3.0
Prelude> div 6 2
3
不能将不同类型的数据一起操作,比如浮点数加整数,注意Haskell不会自动转换类型,所以需要数据类型转换。假若需要将浮点型转为整形,需要round/floor/ceiling 即向上或向下取整。如果将整形转为其他数字类型则需要fromIntegral
注意不等于不是 != 而是 /=
递归
递归的意思是一个函数调用自己,并最终达到递归基。假设要画n个红绿灯,完整代码如下,注意lights函数,递归基是输入整形为0,则返回空,否则画出当前的Picture并&lights(n-1),由于translate位移的缘故这个函数可能看起来比较复杂。下面这段代码运行完之后应该画出三个红绿灯(或者说黑绿灯,hh). 注意这个online IDE可以被玩坏的,如果你调用一个负值的话代码会一直运行下去,hh
import CodeWorld
botCircle c = colored c (translated 0 (-1.5) (solidCircle 1))
topCircle c = colored c (translated 0 1.5 (solidCircle 1))
frame = rectangle 2.5 5.5
trafficLight True = botCircle green & topCircle black & frame
trafficLight False = botCircle black & topCircle red & frame
lights :: Integer -> Picture
lights 0 = blank
lights n = trafficLight True & translated 3 0 (lights (n-1))
ourPicture = lights 3
main = drawingOf ourPicture
代码注释
-- 这个和Java中的// 或者Python的#类似 单行注释
{-
多行注释
-}
第一周很简单,基本上就是了解语言的概念和基本语法/数据类型
编程作业: https://www.seas.upenn.edu/~cis194/fall16/hw/01-intro.html
ex1 红绿灯,没啥难的,四个阶段每个阶段持续时间不同(我用的是3s, 1s, 3s, 1s),对时间取余就行
botCircle, topCircle, midCircle :: Color -> Picture
botCircle c = colored c (translated 0 (-2.5) (solidCircle 1))
midCircle c = colored c (solidCircle 1)
topCircle c = colored c (translated 0 2.5 (solidCircle 1))
frame :: Picture
frame = rectangle 2.5 8.5
trafficLight :: Int -> Picture
trafficLight 0 = botCircle green & midCircle black & topCircle black & frame --green phase
trafficLight 1 = botCircle black & midCircle yellow & topCircle black & frame --amber phase
trafficLight 2 = botCircle black & midCircle black & topCircle red & frame --red phase
trafficLight 3 = botCircle black & midCircle yellow & topCircle red & frame --red and amber phase
trafficController :: Double -> Picture
trafficController t
| mod (round t) 8 >= 0 && mod (round t) 8<3 = trafficLight 0 --0,1,2
| mod (round t) 8 == 3 = trafficLight 1 --3
| mod (round t) 8 >= 4 && mod (round t) 8<7 = trafficLight 2 --4,5,6
| mod (round t) 8 == 7 = trafficLight 3 --7
trafficLightAnimation :: Double -> Picture
trafficLightAnimation = trafficController
exercise1 :: IO ()
exercise1 = animationOf trafficLightAnimation
ex2 树上长花,和红绿灯一样
ex3 推箱子 自己定义外形,然后用translated来改变位置,最后画出UI