module Functions where import Question functions = do h2 $ text "A programmable calculator" p <#>During the first lecture, you were showed a method for converting between different currencies. Here is a similar example: <#>As you perhaps know, the Fahrenheit scale for temperature is still used in some countries. 0° Celsius (C) corresponds to 32° Fahrenheit (F) and an increase of 5°C corresponds to an increase of 9°F. How many degrees in Fahrenheit is 28°C? ?> do prompts' [ ("32+28/5*9", "82.4") ] p <#>The answer is 82.4°F. In what order are the operations above performed? p <#>We have seen how Hugs can be used for simple calculations. But this is only the beginning! We can define a function that extends the abilities of the system. As a first example we consider the temperature converting problem above. If we wish to repeatedly calculate how many °F a temperature given in °C corresponds to, we can define a function that expresses this. Defining a function is done in several steps: let def = "celsiusToFahrenheit celsius = 32 + celsius/5*9" ul $ do li $ p <#>Give the function a name. The name chosen should be an indication of what the function does. Here we decide on the name celsiusToFahrenheit. (Note that names of functions must begin with lower-case letters.) li $ do p <#>Decide what arguments (input) the function should require and what result (output) to return. In this case it is easy: ul $ do li $ p <#>Input: A number, representing a temperature in °C. li $ p <#>Output: A number, representing the corresponding temperature in °F. li $ p <#>Decide on names for the arguments to the function. Each name stands for an arbitrary value of the argument. Since the argument here is a temperature in °C, we decide to use the name celsius for the first and only argument. <#>Describe how the result that we want can be computed using the arguments. How can this be done for the current example? .?> do p <#>In this case the result is 32 + celsius/5*9. -- TODO: Say something about the missing units? li $ do p <#>Write the function definition in Haskell. In this case fixed def p <#>Before we can use this definition we have to give it to Hugs. However, we can't give a function definition directly to Hugs. Instead we must work in two steps: ul $ do li $ do p <#>Use a text editor (e.g. emacs) to write the function definition in a file. Do it, i.e. start emacs, type in the line fixed def p <#>and save this in a file with the name Temperature.hs. (Haskell files usually have the extension .hs.) Make sure the file is located in the directory where Hugs was started. If you need to, quit Hugs, check (with the Unix command ls) that there is a file with the name Temperature.hs and make sure (with the Unix command less) that this file has the correct contents. li $ do p <#>Give Hugs the command to load this file: prompts "" [ ("Prelude> :l Temperature.hs", "Main>") ] p <#>Note that the complete name of the command is :load, thus the symbol after the colon is the letter l, and not the number 1. p <#>We can now use the definition: prompts "Main>" [ ("celsiusToFahrenheit 28", "82.4") , ("celsiusToFahrenheit 0", "32.0") ] p <#>Let's define some other functions. Write them in a new file. ul $ do <#>Write a function that calculates the area of a circle, given the circle's radius. .?> do ul $ do li <#>Name: area. li <#>Input: The radius of the circle. li <#>Output: The area of the circle. li <#>Argument names: radius. <#>The area is pi times the square of the radius, i.e.... .?> do fixed "pi * radius ^ 2" p <#>Find out information about pi by using :i pi. <#>In the end the definition becomes... .?> fixed "area radius = pi * radius ^ 2" <#>Write a function that calulates the radius of a circle, given the circle's area. (Note that if you write the new definition in the same file as the old one, then you need to reload the file in order to use the new definition. This is done by :r (reload).) .?> fixed "radius area = sqrt (area / pi)" <#>Write a function that calculates the length of the hypothenuse of a right-angled triangle, given the lengths of the two other sides of the triangle. .?> fixed "hypothenuse leg1 leg2 = sqrt (leg1 ^ 2 + leg2 ^ 2)" -- <#>Write a function that tests whether radius (area r) is -- equal to r. What happens when you try out your -- definition? .?> do -- fixed "prop_radiusArea r = radius (area r) == r" -- p <#>Note that to reuse old definitions they have to be in the -- same file. (Or you can use import statements, see -- further down.) <#>In the file Temperature.hs, define a function fahrenheitToCelsius that converts from degrees Fahrenheit to degrees Celsius. ?> do fixed "fahrenheitToCelsius fahrenheit = ?" <#>You were not given the full answer to the last exercise. Is your new definition correct? There are many ways to certify the correctness of a function. One is to prove it. That gives reliable answers, but can take a lot of time. Another way is to test the function. This method is never completely reliable unless the function can be tested for all possible inputs, but it is a cheap way to detect many errors. Test your new function now. ?> do <#>Of course there are many different ways of testing. One is to manually test the function giving some different inputs. This, however, takes a lot of time and effort. Those of you who remember the lecture know that you can automate the testing. Read up on how this works, and try it out yourselves. ?> do <#>The first question that comes up is what it is that we want to test. We want to know whether fahrenheitToCelsius is correct. What is the meaning of correct in this case? ?> do p <#>We want fahrenheitToCelsius to convert from °F to °C. Assuming celsiusToFahrenheit is correct, this means that we want to show the following: fixed "celsiusToFahrenheit (fahrenheitToCelsius fahrenheit) \ \== fahrenheit" p <#>The above should be true for all possible inputs fahrenheit, or at least those that correspond to actual temperatures. (The function == compares two values and determines whether they are equal or not.) p <#>When studying functions in the mathematics course, think about what the domain of definition of fahrenheitToCelsius should be. Today we will simply test the function for all possible numerical inputs, regardless of whether they correspond to real temperatures or not. p <#>Now we know what we want to test. Let's write the property below in the file Temperature.hs: fixed "prop_fahrenheitToCelsius fahrenheit =\n\ \ celsiusToFahrenheit (fahrenheitToCelsius fahrenheit) \ \== fahrenheit" <#>In order to test this property, we must type import QuickCheck at the top of the file. (This will ensure that the testing library called QuickCheck is "imported", i.e. becomes available.) Test your program by typing quickCheck prop_fahrenheitToCelsius. (Don't forget to reload the file first.) What happens? ?> do prompts "Main>" [ ( "quickCheck prop_fahrenheitToCelsius" , "ERROR - Unresolved overloading\n\ \*** Type : (Fractional a, Arbitrary a) => IO ()\n\ \*** Expression : quickCheck prop_fahrenheitToCelsius" ) ] p <#>Hugs couldn't figure out what type prop_fahrenheitToCelsius has, and complains. <#>We must tell Hugs what kind of temperature we wish to use when testing. "What do you mean, what kind of temperature?" you may ask. Find out what the type of fahrenheitToCelsius is. ?> do p <#>To learn the type of an expression, use :t (:type): prompts "Main>" [ ( ":t fahrenheitToCelsius" , "fahrenheitToCelsius :: Fractional a => a -> a" ) ] p <#>This type means that fahrenheitToCelsius takes a fraction (Fractional) as input and returns a fraction as output. p <#>There are many different types of fractions (more about this later), so we have to state which one we mean. Let us choose Ratio Integer first, a type for exact fractions. Add the type signature fixed "prop_fahrenheitToCelsius :: Ratio Integer -> Bool" p <#>just before the definition of the property. This type means that prop_fahrenheitToCelsius takes a Ratio Integer as input and leaves a Boolean value (Bool) as output. The function takes a number as input and responds True or False depending on whether fahrenheitToCelsius is correct for the specified number or not, so it seems reasonable, doesn't it? <#>Add import Ratio to the beginning of the file, so that you can use Ratio. Test the property again. What happens now? ?> do prompts "Main>" [ ( "quickCheck prop_fahrenheitToCelsius" , "ERROR - Cannot infer instance\n\ \*** Instance : Arbitrary (Ratio Integer)\n\ \*** Expression : quickCheck prop_fahrenheitToCelsius" ) ] p <#>No one has defined how to generate random data for testing ("arbitrary" data) for the type Ratio Integer. <#>Save ArbitraryRatio.hs in the same directory as Temperature.hs and type import ArbitraryRatio in the beginning of Temperature.hs in order to have access to the definition needed. Test the property again. If you receive an error message, there is something wrong with your function. The error message lists an input that the function couldn't handle correctly. Test fahrenheitToCelsius for this value, and you will hopefully get an idea of what you have done wrong. With all errors taken care of, you will get the following result: prompts "Main>" [ ( "quickCheck prop_fahrenheitToCelsius" , "OK, passed 100 tests." ) ] <#>If you didn't manage to define the function correctly, you may now check out the correct solution. ?> do fixed "fahrenheitToCelsius fahrenheit = \ \(fahrenheit - 32)*5/9" p <#>Now change Ratio Integer to the type Double, i.e. change the type signature to fixed "prop_fahrenheitToCelsius :: Double -> Bool" p <#>Why would one want to do that? Double is a so-called floating point type, i.e. a fractional type with limited precision; the numbers have a maximum number of digits of precision: prompts "Main>" [ ("sqrt 2 :: Double", "1.4142135623731") ] p <#>You may be familiar with the square root of two (sqrt 2) having an infinite series of decimals, but we were given a finite answer. prompts "Main>" [ ( "sqrt 2 :: Ratio Integer" , "ERROR - Cannot infer instance\n\ \*** Instance : Floating (Ratio Integer)\n\ \*** Expression : sqrt 2" ) ] p <#>Ratio Integer is not a floating point type, hence the function sqrt won't work for such numbers. It isn't possible to represent the square root of two exactly with a rational number, even though one can come arbitrarily close by choosing the denominator and numerator large enough. This indicates the major disadvantage with exact fractions: they may need a large amount of space (large denominators and numerators), and thus become inefficient to work with. <#>Double is a type more widely used than Ratio Integer, so someone has already defined how to generate random testing data ("arbitrary" data) for the type. For that reason, there is no need to import any more files. Test your program again. What happens? ?> do prompts "Main>" [ ( "quickCheck prop_fahrenheitToCelsius" , "Falsifiable, after 4 tests:\n\ \3.5" ) ] p <#>(It may happen that your print-out looks a little different, since the testing temperatures are randomly generated, but it should have roughly the same look.) p <#>The test didn't succeed. This is due to Double not giving exact results: prompts "Main>" [ ( "celsiusToFahrenheit (fahrenheitToCelsius 3.5)" , "3.5" ) , ( "celsiusToFahrenheit (fahrenheitToCelsius 3.5) - 3.5" , "-3.5527136788005e-15" ) ] p <#>(-3.5527136788005e-15 stands for -3.5527136788005 times 10 to the power of -15.) p <#>We get rounding-off errors. However, the results are often good enough. Suppose we are satisfied with seven digits of precision. Just like in the lecture, we can define a new equality operator allowing small rounding-off errors: fixed "infix 4 ~==\nx ~== y = abs (x-y) < 5e-8" p <#>(Read the material from the lecture if you don't understand why we use infix.) <#>What type signature should we give for ~==? ?> do p <#>~== takes two Doubles as input and returns a Boolean value as output, so the type signature should be: fixed "(~==) :: Double -> Double -> Bool" p <#>We write an arrow (->) after each argument type. This function takes two arguments, so we write two arrows. p <#>(Since ~== is an operator, we have to put it in brackets in the type signature. Operators are functions that have non-letter symbols as names, and can be used infix, i.e. between their arguments, like for instance / in 4/3.) <#>Test again, and it should work smoothly.