In this lab, we will meet the Cabal package manager, which helps us work on
projects with dependencies between multiple files. We will then program with the
codeworld-api library, which provides us with types and functions for
drawing and transforming various shapes.
Before you start the lab, please make sure you have installed the
correct version of
CodeWorld as detailed in the installation instructions.
The constraints have been updated to ensure consistency throughout the course.
The content of this lab will be very useful for Assignment 1 Part B, and for Assignment 2.
- Cabal build tool
- CodeWorld API
- A blank screen
- Drawing a shape
- Accessing cabal interpreter session
- Transforming shapes
- Extensions (OPTIONAL)
- You should be comfortable using command line arguments in the terminal, as in the Week 1 lab.
- You should be comfortable using Git Integration with VSCodium, as in the Week 2 lab.
- You should be able to read type signatures of functions in Haskell.
- You should be comfortable with writing function definitions, as in the Week 3 lab.
- Read external documentation and understand how to use functions based on the type signature and description.
- Use the cabal package manager to maintain modules with internal dependencies.
- Use the codeworld-api library to draw shapes based on requirements.
- Gain introductory understanding in using lists.
Go to the project’s dashboard under the Project tab and click on the Fork button. The Gitlab url for this lab is https://gitlab.cecs.anu.edu.au/comp1100/2023s2/studentfiles/lab04-1100_s2_2023
You will be asked where to fork the repository. Click on the user or group to where you’d like to add the forked project. You should see your own name here. Once you have successfully forked a project, you will be redirected to a new webpage where you will notice your name before
> project-name. This means you are now the owner of this repository. The url in your browser should reflect the change: https://gitlab.cecs.anu.edu.au/uXXXXXXX/2023s2/studentfiles/lab04-1100_s2_2023
Finally, clone your forked repository to your computer according to the instructions in the Week 2 Lab.
This lab contains additional exercises for COMP1130 students, which are optional but encouraged for COMP1100 students. Make sure that you submit attempts at all exercises required for your course in order to receive full participation marks. The deadline for this is the start of your next lab.
Cabal build tool
For this lab, we are using cabal: a build tool for Haskell that avoids the need to build all of the pieces of your project separately. The comp1100-lab04.cabal file lists all the requirements for the project. You do not need to understand how this file is written. In your own time, if you are interested feel free to explore the file with the documentation here.
There are several useful commands for cabal:
Cabal commands are executed in Terminal using Bash (not in ghci).
|Cabal Command||Command Description|
||Compiles all of the necessary Haskell files needed for the project.|
||Runs the executables, compiling files as necessary. In this case, it will run
||REPL stands for Read-Evaluate-Print Loop. This will launch the GHCi interpreter and load the main program.|
||Ensures a fresh start for building, and prevents potential issues caused by lingering files from previous build operations.|
If you are having issues with cabal, run
cabal v2-clean and then run rerun the required command.
You will execute these cabal commands in the top-level directory of
your project, the directory containing the .cabal file (i.e., the directory
you are in when you clone/launch the VSCodium Terminal tool for your project).
You can check the presence of .cabal file by using the bash command
we learnt in week 1.
This project has a framework that divides the functionality into three primary
files in the src folder:
Controller.hs. This partitioning of functionality is called the
Model-View-Controller (MVC) software
architecture. It is a common pattern used in implementing graphical user
This lab does not use Model or Controller since all the functionalities can be captured with the View module (as we will only be drawing pictures directly to the screen, and not reading any input from the keyboard or mouse). The information in this section is intended only to help guide you through Assignment 1 Part B, which does use this architecture.
The reason that the program is broken up like this is to increase readability and maintainability (so that you can find and fix problems more easily) by grouping related functions together. Moreover, there may be some functions in one module that other modules do not need. With modularisation, you can control what is exposed to other modules and what you want to use when importing.
The Model defines the abstract problem domain, independently of the user
Model.hs has been left empty in this lab since we are only
using types that are already defined in the
The View defines how we render the model for presentation to the user, in
graphical form. In this lab, we will be using
codeworld-api functions for
drawing shapes. All the functions in this lab will be implemented in this module
The Controller implements the GUI-based mouse and keyboard input functionality of the application, allowing users to control the application.
This week we will be using the CodeWorld API (Application Programming Interface) to draw graphical pictures in a browser window. The API helps you easily draw pictures to the screen by writing Haskell code, using shapes, colours and transformations.
Let us begin by understanding the given lines of code.
Double click on the file
Lab04/src/View.hs. You should see code as
module View where import CodeWorld myPicture :: Picture myPicture = blank
This line imports the functions defined by the CodeWorld API:
The next few lines declare a variable with name
myPicture of type
::” is pronounced “has type”), and defines its value to be
myPicture :: Picture myPicture = blank
You can think of
myPicture as a function that takes no input, and returns
something of type
Picture. The idea of a function taking no input might seem
strange, but just as we can have functions that take one input, \(f(x) = 2x\) or
functions that take two inputs, \(f(x,y) = x+y\), we can have a function that
takes no inputs, \(f() = 3\). Note how unlike functions in previous labs, there
is no variable describing the input.
Documentation for CodeWorld can be found at https://hackage.haskell.org/package/codeworld-api-0.8.0/docs/CodeWorld.html.
In this lab, we will focus on reading the documentation and using it to write functions. So, at any time during this lab (or later), feel free to refer to the documentation if you are not sure of how a function works. We suggest you open the documentation on a separate tab in your browser for ease of use.
Picture type and the value
blank were imported from the
CodeWorld API library. They are not part of the standard Haskell Prelude that
defines the standard types such as
Double etc. So the
import statement is necessary before you can use any of these functions.
Double click on the file
Lab04/src/Main.hs. You should see code as
module Main where import CodeWorld import View main :: IO () main = drawingOf myPicture
This imports the functions defined in the local View module:
Before we explain the following lines
main :: IO () main = drawingOf myPicture
let’s have a look at what the functions in this module can achieve.
A blank screen
Make sure you are in the top-level directory (i.e. if you run
ls you should see the folder
src). In the terminal, execute
$ cabal v2-run Resolving dependencies... Configuring comp1100-lab04-1.0... Preprocessing executable 'Main' for comp1100-lab04-1.0.. Building executable 'Main' for comp1100-lab04-1.0.. [1 of 4] Compiling Controller ( src/Controller.hs, dist/build/Main/Main-tmp/Controller.o ) [2 of 4] Compiling Model ( src/Model.hs, dist/build/Main/Main-tmp/Model.o ) [3 of 4] Compiling View ( src/View.hs, dist/build/Main/Main-tmp/View.o ) [4 of 4] Compiling Main ( src/Main.hs, dist/build/Main/Main-tmp/Main.o ) Linking dist/build/Main/Main ... Running Main... Open me on http://127.0.0.1:3000/
http://127.0.0.1:3000/ to open your Web browser at that link. What
you see should not be too surprising, since
myPicture was defined to be
Based on this, we can re-visit
main :: IO () main = drawingOf myPicture
main is the name of a function that invokes a
function. If we explore the CodeWorld documentation, we find the following:
drawingOf :: Picture --The picture to show on the screen (in this case, myPicture). -> IO () -- Draws a Picture.
What do you need to know about IO ()? I/O (Input/Output) allows you to write
programs that have input and output while they run. The type
known as the unit type, contains one element only, and here means only that
the function is not intended to return anything useful, but causes
side-effects (in this case, drawing pictures to the screen).
drawingOf takes a
Picture as an argument and performs
an output action but does not return anything useful.
Note: You are more than welcome to explore IO in your own time, but we will not invest time on this topic during this course. The only functions that you will be expected to write in this course will always be side-effect free.
Drawing a shape
Drawing a blank does not seem very useful. Let us draw something more interesting.
Before we get started, it may be useful to draw a grid.
Exercise 1 Coordinate Plane
myPicturefunction so it returns a coordinate plane.
You can use the CodeWorld documentation to find the appropriate function to draw such a plane. In this case, we will guide you towards the function required.
Once you have navigated to the webpage with the documentation, we can explore
section since we are looking to return an output of the type
this section, we can see that the only function that meets our requirements
coordinatePlane (scroll down the page a little to find it).
coordinatePlane :: HasCallStack => Picture
Feel free to ignore the
HasCallStack term, which you will also find in any
function of the type
For anyone interested,
=> is called
the typeclass constraint, a concept we will explore later in this course.
Ignoring those details, we can read the type signature as follows:
coordinatePlane :: Picture
Now, we can modify the drawing by changing the function
myPicture = coordinatePlane
In order to test the function:
If you are still running the program displaying a blank screen, click on the Terminal and press
Ctrl+c. This will kill the process that was running the previous I/O action. You can then reload the module using
Linking dist/build/Main/Main ... Running Main... Open me on http://127.0.0.1:3000/ ^C $ cabal v2-run Preprocessing executable 'Main' for comp1100-lab04-0.1.0.. Building executable 'Main' for comp1100-lab04-0.1.0.. Running Main... Open me on http://127.0.0.1:3000/
If you had already closed the program earlier, you can follow the instructions provided earlier to draw the blank screen.
Exercise 1 Coordinate Plane.
Remember to add, commit and push your work.
What is a coordinate plane?
This plane combines two axes:
- The first axis line is horizontal, with positive numbers to the right and negative numbers to the left. You can use this axis to describe how far left or right to draw a picture.
- The second axis line is vertical, with positive numbers on top, and negative numbers on the bottom. You use this axis describe how far up or down to draw a picture.
You might also have heard of this grid by the name of a cartesian plane.
Exercise 2 Rectangle
Now that we have a grid, lets draw a shape. We will start with a rectangle. Draw a rectangle with a width of 2 units and height of 2 units.
You can use the appropriately named
rectangle function with the type
rectangle :: HasCallStack => Double -> Double -> Picture
Again, ignore the
HasCallStack term, so we can read the type signature as
rectangle :: Double -> Double -> Picture
myRectanglethat returns a rectangle of width and height of 2 units.
The function should have the following type signature:
myRectangle :: Picture
Exercise 2 Rectangle
Accessing cabal interpreter session
cabal v2-run configures, builds and runs the executable, it may be
useful to make the modules available in an interpreter session by using
cabal v2-repl. Again, you need to be in the top-level directory to execute
the cabal command.
$ cabal v2-repl Preprocessing executable 'Main' for comp1100-lab04-0.1.0.. GHCi, version 9.2.5: http://www.haskell.org/ghc/ :? for help [1 of 4] Compiling Controller ( src/Controller.hs, interpreted ) [2 of 4] Compiling Model ( src/Model.hs, interpreted ) [3 of 4] Compiling View ( src/View.hs, interpreted ) [4 of 4] Compiling Main ( src/Main.hs, interpreted ) Ok, four modules loaded. ghci>
In this session, you will be able to try out your existing code and also, test built-in functions in the loaded modules. It may also be useful to load modules outside the project and experiment with functions together with the already available modules.
You will find using cabal v2-repl very helpful for debugging code both here and in the assignment.
Try out the following.
ghci> drawingOf (thickRectangle 2 10 6) Open me on http://127.0.0.1:3000/ Program is starting...
As usual for ghci, you can use
Ctrl + D to exit back to the Bash prompt.
Feel free to use
cabal v2-repl throughout the lab.
Exercise 3 Combining shapes
You can use the
(&) function to combine the pictures: the coordinate plane
and rectangle. which you can read as “and” or “in front of”.
myPicturefunction so the output of the function looks like the following:
Hint: You can use the
(&) function as follows. It can only combine
two pictures at a time:
combinedPicture = picture1 & picture2
In this case,
picture1 will be in front of
Test your function on your browser by following the steps in Exercise 1.
Exercise 3 Rectangle on coordinate plane
Exercise 4 Make it solid
So you’ve just drawn an outline of a rectangle. What about a solid rectangle? Or a solid circle? Refer back to the CodeWorld API documentation if you are stuck.
myRectangle'that returns a solid rectangle with the same dimensions as
Exercise 4 Solid Rectangle
In CodeWorld, a transformation is a sort of function whose inputs are the original picture, along with a description of how to change the picture (colouring, moving, stretching etc.). The transformation produces a new picture that is like the original except for that change. There are a few different transformations such as colouring, translation, rotation, dilation, and scaling.
Exercise 5 Translation
translated function can change the position of a picture within the
coordinate plane. The function takes as arguments the original picture and two
distances to move the picture, and returns a new picture with the same content
as the original, but shifted either horizontally, or vertically, or both. Check
the documentation to find out the type signature of the function and learn its
myRectangle'function to move the solid rectangle 3 units to the right and 2 units down.
Exercise 5 Translated Rectangle
Exercise 6 Colouring
coloured function modifies the colour of a picture. The function takes
as arguments the original picture and an input of type
Colour. The output
is a new picture with a colour depending on the input of the function. Check the
documentation to find out the type signature of the function and learn its
colouredRectanglefunction that does not take any input but returns a blue
Exercise 6 Coloured Rectangle
Exercise 7 Create a logo
Let’s use these tools to design a cool logo. This is definitely not the logo of a big tech company!
Call the function
You will need to use the colours
Exercise 7 Logo
Exercise 8 Rotation
rotated function takes two arguments: the original picture, and an
angle to rotate it. With this transformation, the result is a new picture that
has been rotated but retains all other features of the original picture.
rotated function needs the input angle in
radians and will turn the picture anticlockwise.
colouredSquaresso the function now returns the shape but rotated 45 degrees.
For more transformations, you can check out the
functions along with their usage on CodeWorld API documentation. Try to use
these transformations on the drawings we have already generated.
Exercise 8 Rotated Logo
Exercise 9 Drawing Lambda
In this exercise, we will be using lists, which were introduced in the lectures. We will only be using them to draw simple shapes, so you are not required to know how to write functions that manipulate lists yet - we will be exploring this in more detail next week.
Custom shapes (lines and polygons) can be implemented using built-in functions in CodeWorld. We can use coordinates to define pictures composed of a sequence of line segments.
For example, try the following:
simpleLambda = coordinatePlane & polyline [(3,7), (7,1), (5, 4), (3,1)]
How does polyLine work?
If we look at the type definition for
HasCallStack) in the Codeworld
polyLine :: [Point] -> Picture
What is a
Point? If we click on
Point in the documentation, we
type Point = (Double, Double)
We have seen tuples before in Lab03. (Check them out there if you don’t remember.)
Point is simply a pair of
Doubles that represent a location in the
coordinate plane. This means that the function
polyLine takes a list of
Points and draws line segments in between them.
myPicturein the file so we can now visualize the drawing in the browser.
lambda = coordinatePlane & polyline [(3,6), (7,0), (5, 3), (3,0)] & polyline [(2,6), (4,3), (2,0)]
solidLambdathat creates a drawing as below:
Exercise 9 Drawing Solid Lambda.
You are required to submit your attempts at the exercises above in order to receive full participation marks for this lab. You have until the start of your next lab to do this. You should submit by committing and pushing all your changes to your Gitlab repository.
The next few exercises are very difficult and require you to dive deep into CodeWorld documentation. The material of these exercises will not be examined.
All the following exercises are optional. These exercises should only be attempted after completing all the exercises above. Feel free to do this at home and discuss with your peers on Ed, or with tutors at a drop-in consultation.
This exercise is a challenge in reading and understanding code documentation. Our goal is to end up with a rotating logo:
Exercise 10 Visualising The Logo
A visualisation function is a function that takes some model and maps it to something the user sees. We could say that a visualisation is a function has this general type in Codeworld:
visualisation :: a -> Picture visualisation model = ...
rotateLogo function in
View.hs, as a visualisation for rotating our
- We need to change the
rotationof the logo according to some
- A large number should correspond to a large rotation, a small number to a small rotation.
- A negative number should correspond to rotation in the opposite direction.
rotateLogo :: Double -> Picture rotateLogo theta = undefined
Exercise 11 Handling Events (Challenging)
An event is something that happens in our program that drives some new behaviour. Some examples of common program events are:
- The user clicks a button.
- The user presses a key.
- The current time ticks over to 5pm in the evening.
- A packet of data is received across the network.
The docs give us a lot of detail around events that Codeworld already recognises.
An event handler function is used to process events by taking the previous state as input, changing it to respond to an event, and returning the new state as output.
eventHandler function that only responds to the
while ignoring other events. This function should take in the
x :: Double
state that changes as time passes, and update it depending on what event
occured. As you will see in the next exercise, this will interact with
rotatoLogo to ensure continuous rotation.
eventHandler :: Event -> Double -> Double eventHandler e x = undefined
Exercise 12 Entry Point
Replace the entry point into our program in
Main.hs from a
Make sure you give your activity a suitable initial value for your world.
Then give it the eventHandler and rotateLogo Functions.
activityOf is a function that can take other functions as arguments. This is called a higher-order function, and we will examine in later in the course. For now, we have supplied the arguments for you below.
initialValue :: Double initialValue = undefined main :: IO () main = activityOf initialValue eventHandler rotateLogo
Exercise 13 Try Your Own (Challenging)
Write your own custom animation. Some examples could include:
- The logo flies apart and sends each box far into its quadrant for a period of time before returning to a whole.
- The logo follows a path to move around the screen over time.
- Animate another shape to change in size and position over time.
- Animate multiple shapes together.
 Cabal User Guide, https://cabal.readthedocs.io/en/3.4/index.html
 CodeWorld API library, https://hackage.haskell.org/package/codeworld-api-0.8.0/docs/CodeWorld.html