Introduction to Functional Programming -- Lab 4: "Drawing Functions" | TDA555 / DIT440, LP1, HT2012 | ||||||||||||||||||||||||||
Home | Schedule | Labs | Exercises | Exam | About | Fire | Forum | TimeEdit | Links | 2011 | ||||||||||||||||||||||||||
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 standard library Gtk2Hs for the graphical part. Read more about it here: using Gtk2Hs. If you want to do this lab on your own computer, you need to first install Gtk2Hs. Read more about that here: installing Gtk2Hs at home. 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 15 at 9.00 in the morning. Part II of the lab needs to be submitted before Wednesday October 24 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 29 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 you are going to implement is a program that produces a window that might look like this:
![]() The window 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 return, 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 panel widget of a certain size (you decide yourself, but let us suppose it has width and height of 300). A panel 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 panel to draw our functions is the following: For example, the coordinate system for our functions might work like this:
So, some conversion is needed between pixels (represented using Int) and floating point numbers (represented using Double). Put the answers for Part II in a module called Calculator.hs. The module Calculator should of course import the module Expr. In this part of the assignment, you will have to use Gtk2Hs. Make sure you write import Graphics.UI.Gtk import Graphics.UI.Gtk.Gdk.GC import Exprat the top of your file Calculator.hs, in order to import the correct modules. Assignments
Hints To convert back and forth between Ints and Doubles, the following functions might come in handy: round :: Double -> Int fromIntegral :: Int -> DoubleThese 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 pixToReal :: Int -> Double -- converts a pixel x-coordinate to a real x-coordinate pixToReal x = ... realToPix :: Double -> Int -- converts a real y-coordinate to a pixel y-coordinate realToPix y = ... For an example use of the panels, and an example of how to build a graphical user interface, look at the examples provided in the lecture on Gtk2Hs 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! |