Introduction to Functional Programming – Lab 4 | TDA555 / DIT440, LP1, HT2013 | ||||||||||||||||||||||||||
Home | Schedule | Labs | Exercises | Exam | About | Fire | Forum | TimeEdit | Links | 2012 | ||||||||||||||||||||||||||
Some notes:
Good luck!
Lab Assignment 4 – Drawing Functions In this Lab Assignment, you will design and implement a very simple graphical calculator. The idea is to use the web browser for the graphical part. The Haste compiler is able to compile Haskell code to Javascript, which the browser can run. Read more about it here: Running Haskell in the Browser. To do this lab, you need to install Haste (even on Chalmers student computers). Read more about that here: Installing Haste. Assignments and Deadlines In this lab, you have a little bit more freedom than in the previous labs; we will not guide you towards a solution as much as in the previous lab assignments. The lab consists (again) of two parts. Part I of the lab needs to be submitted before Monday October 14 at 9.00 in the morning. Part II of the lab needs to be submitted before Wednesday October 23 at 9.00 in the morning. There are also extra assignments. You can choose freely whether to do one of these. The final deadline for the lab is on Monday (!) October 28 at 9.00 in the morning. 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:
![]() The page consists of a drawing area, and a text entry field below it. The user can type mathematical expressions in the text entry, which, after pressing the Draw graph button, will be graphically shown on the drawing area. The 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.
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
The last 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 is too strict; there is certain information loss in showing an expression. For example, the expressions "(1+2)+3" and "1+(2+3)" have different representations in your datatype (and are not equal), but showing them yields "1+2+3" for both. Read the lecture notes from Week 6 for 2 possible solutions to this problem.
Hints * To design the datatype, you might be inspired by the Expr datatype discussed in the lectures in Week 4. Read the slides and look at the example code! It is important that your datatype is simple and elegant; try not to use too many data constructors for example. If two different cases can be effectively expressed using one constructor, then do that. When designing your datatype Expr, think carefully about how you want to express the variable x. You may get inspired by the Expr datatype from the lectures, but remember that there is a difference; in your datatype you only have to represent one variable, called x, whereas in the lecture we allowed for several different variables! * When showing and reading expressions, we have to decide where we allow and require parentheses. Parentheses are required only in the following cases: In all other cases, you should not require parentheses. For example: Make sure that the above expressions are all parsed correctly by your program!* For the functions eval, showExpr, readExpr, arbExpr, and prop_ShowReadExpr also read the relevant slides for week 4 and week 5, and look at the example code that is provided. * For the function readExpr, to allow spaces in the expression, simply filter out all spaces from the string before you use the parser. In this way, spaces will not mess up your parser and keep it nice and clean. * For the function readExpr, to be able to parse floating point numbers (Doubles) and sin and cos, you only have to change the parser for factors. To parse floating point numbers, do not try to adapt the function "number" from the lecture, instead take a look at the standard Haskell functions read and reads. Use it on the right type (Doubles), and see what happens! Main> read "17.34" :: Double ... Main> read "17.34cykel" :: Double ... Main> reads "17.34cykel" :: [(Double,String)] ... * When working with the property prop_ShowReadExpr, it might be a good idea to make sure that the property you define will not crash, even if there is something wrong with your functions! A common way for the property to crash is when the readExpr function (unexpectedly) delivers Nothing. Instead of crashing, your property should return False in that case. You can do this by doing a case expression on the result of readExpr, or by adding that the result of readExpr should not be equal to Nothing before you check that the result is of the shape Just e. * If you have a hard time understanding the generated counter examples for your property, it is probably a good idea to let Haskell derive the show function for your Expr datatype, instead of making your own instance of Show. So, use "deriving Show" on your expression datatype while testing! * To solve the problem of associative operators (+ and *) in prop_ShowReadExpr, you can choose one of the two solutions mentioned in the lecture notes. If you decide to evaluate both sides of the equation before you compare, you might find that (x+y)+z is not always the same as x+(y+z) because of rounding errors! An easy way to fix that is to define a function "almostEqual", and use that instead of "==". "almostEqual x y" should only be True when x and y lie very close to each other (for example when their difference is less than 0.001). Part II In this part, you are going to implement the graphical part of the calculator. The graphical interface consists of 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:
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:
So, some conversion is needed between pixels and mathematical coordinates. Note, though, that both coordinate systems are represented using Double. The Haste compiler does not come with QuickCheck included (and I wasn't able to install it separately either). For this reason, you need to remove the testing code (assignment E) from Expr.hs and put it in a separate module ExprQC.hs. The answers for Part II should be put in a module called Calculator.hs. The modules Calculator and ExprQC should of course import the module Expr. You also need to use an HTML file for the user interface. You are welcome to design your own interface, but for those who prefer a standard solution, feel free to use calculator.html. To make use of Haste's libraries for interfacing with web pages, make sure you write import Haste hiding (eval) import Haste.Graphics.Canvasat the top of your file Calculator.hs. These libraries are documented here. Assignments
Hints To convert back and forth between Ints and Doubles, the following function might come in handy: fromIntegral :: Int -> DoubleThis function has a more general type than the one 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 pixToReal :: Double -> Double – converts a pixel x-coordinate to a real x-coordinate pixToReal x = ... realToPix :: Double -> Double – converts a real y-coordinate to a pixel y-coordinate realToPix y = ... For an example use of the canvas, and an example of how to build a graphical user interface, look at the examples provided in the lecture on Haste of Week 6. In particular: OBS: For the standard version of the assignment, you do not have to use a state variable to keep track of the state!
Extra Assignments You can choose freely whether to do one of these. You will learn more when you do! Extra Assignments
Submission Submit your solutions using the Fire system. Your submission should consist of the following files: Before you submit your code, Clean It Up! Remember, submitting clean code is Really Important, and simply the polite thing to do. After you feel you are done, spend some time on cleaning your code; make it simpler, remove unneccessary things, etc. We will reject your solution if it is not clean. Clean code: Good Luck! |