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.

Pre-lab Checklist

  • 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.

Learning Outcomes

  • 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.

Getting Started

  1. 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/2024s1/2024s1studentfiles/comp1100-2024s1-lab04

  2. 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/2024s1/2024s1studentfiles/comp1100-2024s1-lab04

  3. 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.

Gitlab CI

You may have noticed a new box on your repo on the Gitlab web view, looking something like this:

ci-success

To help you ensure your submitted code is compiling, we’ve added a Continuous Integration (CI) script to this lab, and it will also be present in your assignments.

If your code is compiling successfully, the CI banner in the web interface will show a green tick, otherwise it will display a red x.

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
cabal v2-build Compiles all of the necessary Haskell files needed for the project.
cabal v2-run Runs the executables, compiling files as necessary. In this case, it will run Main.hs by default as listed in the .cabal file.
cabal v2-repl                                                               REPL stands for Read-Evaluate-Print Loop. This will launch the GHCi interpreter and load the main program.
cabal v2-clean 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 ls we learnt in week 1.

Modules

This project has a framework that divides the functionality into three primary files in the src folder: Model.hs, View.hs, and 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 interfaces (GUI).

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.

Model

The Model defines the abstract problem domain, independently of the user interface. The Model.hs has been left empty in this lab since we are only using types that are already defined in the codeworld-api.

View

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 View.hs.

Controller

The Controller implements the GUI-based mouse and keyboard input functionality of the application, allowing users to control the application.

CodeWorld API

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 follows:

module View where

import CodeWorld

myPicture :: Picture
myPicture = blank

This line imports the functions defined by the CodeWorld API:

import CodeWorld

The next few lines declare a variable with name myPicture of type Picture (“::” is pronounced “has type”), and defines its value to be blank:

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.

In this lab you will be modifying the function myPicture a number of times to make different Pictures appear. Keep a copy of your old versions of myPicture in your file, commented out, so that your tutor can give you your participation mark for your code.

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.

The 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 Int, 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 follows:

module Main where

import CodeWorld
import View

main :: IO ()
main = drawingOf myPicture

This imports the functions defined in the local View module:

import View

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:

$ 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/

Click on 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 blank.

Based on this, we can re-visit

main :: IO ()
main = drawingOf myPicture

So, main is the name of a function that invokes a drawingOf 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 (), also 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).

The function 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

coordinateplane

Modify the myPicture function 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 the Pictures section since we are looking to return an output of the type Picture. In this section, we can see that the only function that meets our requirements would be 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 Picture.

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:

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 cabal v2-run.

    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.

Submission required: Exercise 1 Coordinate Plane. Remember to add, commit and push your work.

What is a coordinate plane?

This plane combines two axes:

  1. 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.
  2. 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 signature:

rectangle :: HasCallStack => Double -> Double -> Picture

Again, ignore the HasCallStack term, so we can read the type signature as follows:

rectangle :: Double -> Double -> Picture
Write a function named myRectangle that returns a rectangle of width and height of 2 units.

The function should have the following type signature:

myRectangle :: Picture

Submission required: Exercise 2 Rectangle

Accessing cabal interpreter session

While 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 :q or 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”.

Rewrite myPicture function so the output of the function looks like the following:

rec_plane

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 picture2.

Test your function on your browser by following the steps in Exercise 1.

Submission required: 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.

Write a new function myRectangle' that returns a solid rectangle with the same dimensions as myRectangle.

solid-rec-plane

Submission required: Exercise 4 Solid Rectangle

Transforming shapes

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

The 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 usage.

Modify your myRectangle' function to move the solid rectangle 3 units to the right and 2 units down.

trans-rec

Submission required: Exercise 5 Translated Rectangle

Exercise 6 Colouring

The 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 usage.

Write a colouredRectangle function that does not take any input but returns a blue myRectangle'.

Submission required: Exercise 6 Coloured Rectangle

Let’s use these tools to design a cool logo. This is definitely not the logo of a big tech company!

macrosoft

Your task is to create the same logo using the tools that we have learnt.

Call the function colouredSquares. You will need to use the colours blue, yellow, green and orange.

Submission required: Exercise 7 Logo

Exercise 8 Rotation

The 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.

Remember that rotated function needs the input angle in radians and will turn the picture anticlockwise.

Modify colouredSquares so the function now returns the shape but rotated 45 degrees.

rotated-macrosoft

For more transformations, you can check out the scaled and dilated functions along with their usage on CodeWorld API documentation. Try to use these transformations on the drawings we have already generated.

Submission required: 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 polyLine (ignoring HasCallStack) in the Codeworld documentation:

polyLine :: [Point] -> Picture

What is a Point? If we click on Point in the documentation, we discover:

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 these Points and draws line segments in between them.

Modify the definition of myPicture in 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)]

simpleLambda

Your final task is to write a function solidLambda that creates a drawing as below:

solidLambda

Submission required: 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.

Extensions: Codeworld Events, and Animation

[COMP1100 Optional, Semester 1 COMP1130 Compulsory]

The next few exercises are quite difficult and require you to dive deep into CodeWorld documentation. The material of these exercises will not be examined but COMP1130 students are expected to attempt it.

This exercise is a challenge in reading and understanding code documentation. Our goal is to complete the following three exercises to end up with a rotating logo:

rotating_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 that has this general type in Codeworld:

visualisation :: a -> Picture
visualisation model = ...

where a is replaced by whichever type makes sense for this particular function.

Write a rotateLogo function in View.hs, as a visualisation for rotating our logo picture.

  • We need to change the rotation of the logo according to some Double input.
  • 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.

Write an eventHandler function that only responds to the TimePassing event, 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 drawingOf with an activityOf. 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

References

[1] Cabal User Guide, https://cabal.readthedocs.io/en/3.4/index.html

[2] CodeWorld API library, https://hackage.haskell.org/package/codeworld-api-0.8.0/docs/CodeWorld.html

bars search times arrow-up