TDA 452
DIT 143
HT 2018

Functional Programming 2018
Lab 4 Graphical Calculator

In this Lab Assignment, which is one of the options for Lab 4, you will design and implement a simple graphical calculator. Like the previous labs, this lab assignment consists of two parts: in Part I, you will implement the parts of your program that have to do with expressions. In part II, you will implement the graphical part of your program.

To do the graphical part in a platform-independent, but still fairly simple way, we will use a web browser for the user interface. To prepare for this, and avoid last-minute problems, you might want to take a look at the introduction to Part II even before you start working on Part I.

Hints

Some assignments have hints. Often, these involve particular standard Haskell functions that you could use. Some of these functions are defined in modules that you have to import yourself explicitly. You can use the following resources to find more information about those functions:

We encourage you to actually go and find information about the functions that are mentioned in the hints!

A Graphical Calculator

The graphical calculator will be implemented as a web page that might look like this:

[Calculator screen dump]

The page consists of a drawing area, and a text entry field below it. The user can type mathematical expressions in the text entry field, which, after pressing the Draw graph button (or maybe the Enter key on the keyboard), will be shown graphically in the drawing area.

Part I

In this part, you are going to design a datatype for modelling mathematical expressions containing a variable x. For example: In other words, an expression consists of: You will also implement a number of useful functions over this datatype.

Put the answers for Part I in a module called Expr.hs.

Assignments

A. Design a (recursive) datatype Expr that represents expressions of the above kind.

You may represent integer numbers by floating point numbers; it is not necessary to have two different constructor functions for this. For variables, since the variable name is always x, the representation of variables does not need to include the variable name.

Your data type should be designed to make it easy to add more functions and more binary operators to the language.

B. Implement a function
showExpr :: Expr -> String
that converts any expression to string. The strings that are produced should look something like the example expressions shown earlier. Use as little parentheses as possible, but consider that we want to be able to read an expression back without loss of information, as discussed in assignment D and E below.

It is not required to show floating point numbers that represent integer numbers without the decimal part. For example, you may choose to always show 2.0 as "2.0" and not as "2". (But you are allowed to do this.)

C. Implement a function
eval :: Expr -> Double -> Double
that, given an expression, and the value for the variable x, calculates the value of the expression.
D. Implement a function
readExpr :: String -> Maybe Expr
that, given a string, tries to interpret the string as an expression, and returns Just of that expression if it succeeds. Otherwise, Nothing will be returned. You are required to use an unmodified version of the module Parsing.hs to construct this function. See the lectures from Week 4 on parsing expressions.

The next assignment is about checking that your definition of readExpr matches up with your definition of showExpr. One could define a property that simply checks that, for any expression e1, if you show it, and then read it back in again as an expression e2, then e1 and e2 should be the same.

However, this doesn't work if there is certain information loss in showing an expression. This happens, for example, if the expressions (1+2)+3 and 1+(2+3), which have different representations in your datatype (and are not equal), are both shown as "1+2+3".

A simple solutions to this problem is to include the parentheses when showing 1+(2+3) and only omit them when showing (1+2)+3. A nicer solution is perhaps to use an equality test that takes the associativity of operators into account (e.g. it considers 1+(2+3) and (1+2)+3 equal). See the function assoc in exercise 3B from week 5 for an example of how you could implement this.

E. Write a property
prop_ShowReadExpr :: Expr -> Bool
that says that first showing and then reading an expression (using your functions showExpr and readExpr) should produce "the same" result as the expression you started with.

Also define a generator for expressions:

arbExpr :: Int -> Gen Expr
Do not forget to take care of the size argument in the generator.

Make Expr an instance of the class Arbitrary and QuickCheck the result!

instance Arbitrary Expr where
  arbitrary = sized arbExpr
quickCheck will call arbExpr with sizes in the range [0..99], so make sure arbExpr produces reasonably sized expressions for testing with this range of sizes.
F. Define a function
simplify :: Expr -> Expr
which simplifies expressions so that subexpressions not involving variables are always simplified to their smallest representation, and that (sub)expressions representing x + 0 , 0 * x and 1 * x and similar terms are always simplified. Define (and run) QuickCheck properties that check that the simplifier is correct (e.g. 1+1 does not simplify to 3), and that it simplifies as much as possible (or more accurately: as much as you expect!).
G. Define a function
differentiate :: Expr -> Expr
which differentiates the expression (with respect to x). You should use the simplify function to simplify the result.

Hints

Part II

Creating graphical user interfaces with Haskell

In this part, you are going to implement the graphical part of the calculator. To do this in a platform-independent way, we will use a web browser for the user interface.

threepenny-gui is a Haskell library for creating web pages with interactive parts (buttons, sliders, text entry boxes, etc). To install it, you can use the cabal tool (which you already have, if you have installed the Haskell Platform) in a terminal window:

cabal install threepenny-gui
To make things easier, you can use the following two modules: You can use these directly, or just study them as examples of how to use threepenny-gui.

Structure of the User Interface

The graphical interface consists of at least two parts: (1) the drawing area, where the function is going to be drawn, and (2) the text entry field.

The drawing area is a "canvas" element of a certain size (you decide yourself, but let us suppose it has width and height of 300). A canvas has a coordinate system that works in pixels. Here is how it works:

(0,0) (300,0)
(0,300) (300,300)

Perhaps surprising is that y-coordinates are upside down; they are 0 at the top, and 300 at the bottom.

The tricky thing using this canvas to draw our functions is that the coordinate system we are used to in mathematics has (0,0) in the middle, and the y-coordinates are not upside down. For example, the coordinate system for our functions might work like this:

(-6.0,6.0) (6.0,6.0)
(0,0)
(-6.0,-6.0) (6.0,-6.0)

So, some conversion is needed between pixels and mathematical coordinates. Note, though, that both coordinate systems are represented using the type Double.

Assignments

The answers for Part II should be put in a module called Calculator.hs. It should of course import the module Expr that you created in Part I.

In this assignment you are free to be creative when designing the calculator. But for those who just want to focus on the important stuff, we have prepared a stub program that constructs the web page for you: Calculator.hs, which uses ThreepennyPages.hs mentioned above. These files serve as examples of how to use threepenny-gui, so it might be useful to look at them even if you decide not to use them directly.

H. Implement a function with the following type.
points :: Expr -> Double -> (Int,Int) -> [Point]
The function gets three arguments; points exp scale (width,height): The type Point is defined in threepenny-gui and is just a pair of numbers:
type Point = (Double, Double)
The idea is that points will calculate all the points of the graph in terms of pixels. The scaling value tells you the ratio between pixels and floating point numbers. The arguments width and height tell you how big the drawing area is. We assume that the origin (0,0) point is in the middle of our drawing area.

For the canvas and function coordinate systems above, we would use 0.04 for the scale (since 1 pixel corresponds to 0.04 in the floating point world, this is (6.0 + 6.0) / 300), and (300,300) for the width and height.

I. Implement the graphical user interface that connects assignments A–F into a web-based graphical calculator.

If you are basing your code on the provided module Calculator.hs, this part only involves completing the function

readAndDraw :: Element -> Canvas -> UI ()
which reads the expression from the given input element and draws the graph on the given canvas.

When the user types in something that is not an expression, you may decide yourself what to do. The easiest thing is to draw nothing.

J. Implement some form of zooming. There are a number of ways to do this. Either the user clicks somewhere on the canvas, and the drawing function zooms around that point. Or there could be a text entry where the scaling factor can be given. Or there could be a slider that the user can drag.

Make sure there is a way to zoom out after you have zoomed in.

K. Implement a Differentiate button which displays both the differentiated expression and its graph.

The recommended solution is to let the differentiated expression replace the expression in the text entry field. This allows you to differentiate many times, and provides a nice way to test that showExpr and simplify work as expected.)

Hints

To convert back and forth between Ints and Doubles, the following function might come in handy:

fromIntegral :: Int -> Double
round        :: Double -> Int
These functions have more general types than the ones given above. For other conversion functions, use Hoogle!

To implement the function points, it is probably a good idea to define the following two local helper functions:

where
  – converts a pixel x-coordinate to a real x-coordinate
  pixToReal :: Double -> Double
  pixToReal x = ...

  – converts a real y-coordinate to a pixel y-coordinate
  realToPix :: Double -> Double
  realToPix y = ...

The easiest way to draw the graph on the canvas is to use the function path defined in ThreepennyPages.hs

path :: String -> [Point] -> Canvas -> UI ()
which connects a sequence of points with lines. The first argument is a web colour name (e.g. "black", "#000", "blue", "#00f").

Extra Assignments

You may freely extend your solution to include other features, but if possible do so in a way that does not make it more difficult to understand the code for the parts of the lab described above.

One idea:

Submission

Submit your solutions using the Fire system.

Your submission should consist of the following files:

Before you submit your code, spend some time on cleaning up your code; make it simpler, remove unneccessary things, etc. We will reject your solution if it is not clean. Clean code:

Feel free to use the hlint program to help with many of these issues and other haskell style issues.

This year we have added Automatic checks in Fire that will run when you submit your answers. The tests include runing hlint and perhaps testing some of your functions with QuickCheck. The purpose of this is to give you some quick feedback and to help speed up the grading process. If the feedback you get is not helpful, you can safely ignore it.

To the Fire System

Good Luck!