It is useful to give visual portrayals of various programming ideas. The
Pictures
case study in
Haskell: The
Craft of Functional Programming, Second Edition attempts to do this. The
case study represents ASCII-based pictures as lists of strings (which are
themselves lists of characters). The example is used to introduce
Int
This section presupposes that you have read Sections 4.1 to 4.3 of the text.
How does recursion work over integers? The general pattern is to write a function of the form:
fun 0
or fun 1
usually;
fun (n-1)
to the next, fun n
.
fun :: Int -> ... fun n | n==1 = ... | n>0 = ... fun (n-1) ...and this can come over visually if used with
Pictures
sideBySide
with (n-1) squares:
so as a recursive definition,
blackSquares :: Int -> Picture blackSquares n | n==1 = black | n>1 = black `sideBySide` blackSquares (n-1)
n
beginning with a black square is got by
sticking a black square on the front of a line beginning with a white square:
and as a definition we have
blackLine, whiteLine:: Int -> Picture blackLine n | n==1 = black | n>1 = black `sideBySide` whiteLine (n-1) whiteLine n | n==1 = white | n>1 = white `sideBySide` blackLine (n-1)
and as a definition we have
blackChess:: Int -> Int -> Picture blackChess n m | n==1 = blackLine m | n>1 = blackLine m `above` whiteChess (n-1) mwith an n by n chessboard given by
chessBoard :: Int -> Picture chessBoard n = blackChess n n
1. How would you define a function to give you a column of pictures,
picCol :: Int -> Picture -> Pictureas illustrated here
2. Give a Haskell definition of a function which takes an integer
n
and returns an n
by n
square which is white apart
from a
diagonal black line from top left to bottom right:
3. Give a Haskell definition of a function which takes an integer
n
and returns an n
by n
square which is white apart
from a
diagonal black line from top right to bottom left:
4. Give a Haskell definition of a function which takes an integer
n
and returns an n
by n
square which is white apart
from a
black cross joining diagonally-opposite corners:
5. Can you give a direct recursive definition of chessBoard
so that
chessBoard n = ... chessBoard (n-1) ...Your solution to question 1 might well give you a guide here.
Pictures
[String]
.
String
. The functions we developed over pictures were
above, sideBySide :: Picture -> Picture -> Picture flipH, flipV, rotate :: Picture -> Picture invertColour :: Picture -> Picture superimpose :: Picture -> Picture -> Picture printPicture :: Picture -> IO ()We'll discuss these functions when looking at the alternatives.
As an example we take the picture
##..# ..###which in the current representation is
[ "##..#" , "..###" ]
.
String
"##..#\n..###\n"
, which on the
face of it seems the simplest representation.
To place one picture above
another, the two strings are joined
using ++
(that's why there is a final \n
at the
end of the string). Similarly to invertColour
you just need to
invert each character (apart from \n
).
However, flipping in a horizontal or vertical mirror is mot easy using this
representation, as you need to do things with the individual lines. It is
possible, almost, to do rotate
by reversing the list. In the
example, reversing the list gives
"\n###..\n#..##"which is (almost) a representation of
###.. #..##
[String]
.
[ "#." , "#." , ".#" , ".#" , "##" ]All the functions over pictures can be implemented over this representation: it is simply that the roles of horizontal and vertical are reversed. For instance, to place two pictures
sideBySide
the lists of columns are joined using
++
; in the original representation above
was implemented by ++
and sideBySide
thus
sideBySide pic1 pic2 = [ line1 ++ line2 | (line1,line2) <- zip pic1 pic2 ]
Is there anything to choose between the column representation and the original (row)
one? It
is only in the function printPicture
,
which produces a printable version of a
picture. It is easier to implement this with rows than with columns, as the
natural printing routine prints one row (line) at a time.
(String,Int)
.
("##..#..###" , 5)
.
The pros and cons are similar to the simple string representation, but this has the slight advantage that splitting the string into lines is slightly simpler when the length of each line is given explicitly.
[[(Char,Int)]]
.
###..is represented by
[ ('#',3) , ('.',2) ]and the rotated example picture by
[ [ ('#',3) , ('.',2) ] , [ ('#',1) , ('.',2) , ('#',2) ] ]It's interesting to see how the functions over Picture work now. For all the functions
above, sideBySide invertColour flipH, flipV, rotateexactly the same programs work! This because these programs operate over lists of lines, and this representation simply changes the representation of the individual lines.
The one difficulty is to implement superimpose
. Why? Try an
example to see the problem.
To convert one of these representations to a string, we can use
convert :: [ (a,Int) ] -> [a] convert xs = concat [ replicate n x | (x,n) <- xs ]
[Int]
"###.."
by [3,2]
and
".##.."
by [0,1,2,2]
.
1. It is a nice exercise to work out the details of the implementation of the picture-manipulating functions
above, sideBySide :: Picture -> Picture -> Picture flipH, flipV, rotate :: Picture -> Picture invertColour :: Picture -> Picture superimpose :: Picture -> Picture -> Picture printPicture :: Picture -> IO ()for each of the representations given in this section.
2. For each of the representations given in this section, define a function
rotate90 :: Picture -> Picturewhich rotates a picture through 90 degrees anticlockwise. Using this, or otherwise, define a function which rotates a picture through 90 degrees clockwise.
3. For each of the representations given in this section, define a function
frame :: Char -> Picture -> Pictureso that
frame ch pic
adds a frame of the character
ch
to the picture pic
.
1. Build further functions to allow