CA215 Languages and Computability

 

Functional Programming Lab 3: Defining More Functions

 

Aim

 

The aim of this class is to help you learn more about defining simple Haskell functions

 

and to introduce you to the idea of developing definitions by breaking the problem down

into more manageable parts, then combining them to give a complete solution.

 

The Day of the Week

 

You are to write a function dayOfWeek that, given a 21st century date in the format (day,month,year)tells you the day of the week for that date. For example:

 

> putStr (dayOfWeek (1,1,2000))

Saturday

> putStr (dayOfWeek (2,1,2000))

Sunday

> putStr (dayOfWeek (7,1,2000))

Friday

> putStr (dayOfWeek (13,3,2054))

Friday

 

There is actually a rather complicated formula called Zeller’s Congruence which works out the number of the day of the week given any date on the modern calendar, but we will try and solve it ourselves by breaking it down into smaller more manageable parts.

 

If we knew what day the first day of the 21st century was, and we could work out the total number of days up to the date we are asking about, then that would give us a good start. Four our purposes, we will consider that the first day of the 21st century was the 1st January 2000 (although some people might dispute this), which was a Saturday. There are quite a few things that need to be considered e.g. the problem of leap years.

 

To begin, here are some type synonym declarations that will make your script more readable:

 

type Day = Int

type Month = Int

type Year = Int

type Date = (Day,Month,Year)

 

First, you should write a function called leap that determines if a given year is a leap year:

 

> leap 2000

True

> leap 2034

False

> leap 2048

True

 

A year is a leap year if the remainder on dividing it by 4 is zero. In Haskell, a `rem` b

is the remainder when a is divided by b.

 

Before proceeding, thoroughly test your leap function to make sure that it works properly.

 

Now we need to deal with the numbers of days in each month. The following two functions will help with this.

 

mLengths takes a year and returns the list containing the number of days in each month of that year:

 

> mLengths 2016

[31,29,31,30,31,30,31,31,30,31,30,31]

 

Here is the definition:

 

mLengths :: Year -> [Int]

mLengths year = [31,feb,31,30,31,30,31,31,30,31,30,31]

                where feb

                        | leap year = 29

                        | otherwise = 28

 

The following function numDays is given a date in the form described above and returns

the number of days since 31st December 1999:

 

numDays :: Date -> Int

numDays (day,month,year)

    = day                                                             -- days this month

      + sum (take (month-1) (mLengths year))                                   -- days this year

      + (year-2000) * 365 + length [yr | yr <- [2000..year-1], leap yr] -- days this century

 

To understand how this function works, consider numDays(13,3,2054).  The last line works out the number of days in the whole years 2000-2053 at 365 days per year, and an extra day is added on for each leap year in that period. The second line uses mLengths to give us the days of the months of the year 2054. Then it takes the completed months of the year and adds them together. Finally, the first line adds on 13, the number of days so far in the month we are asking about. For example:

> numDays (13,3,2054)

20161

 

You should type these definitions into your script. Notice how numDays uses the function mLengths, and mLengths uses the function leap. The rest of the development of your script should follow the same approach, by building on numDays.

 

The next obvious function we need to define is a function dayName which, given the number of a day in the range 0-6, returns a string containing the name of the day. The type of this function should be declared as follows:

 

 dayName :: Int -> String

 

Some example applications of the function are as follows:

 

> dayName 0   

“Monday”

> dayName 5

“Saturday”

> dayName 6

“Sunday”

 

Once you have dayName working (and thoroughly tested), you should be able to use

NumDays, dayName, `mod` and the fact that 31st December 1999 was a Friday, to construct a function:

 

dayOfWeek :: Date -> String

 

Try to write a function that works correctly for dates between (1,1,2000) and (6,1,2000) first, then see if you can modify it to work in general. Remember that your function does not have to work for dates before (1,1,2000). Make sure that you test your function against a calendar (try the unix cal command e.g. cal 1 2000).

 

Finally, extend the dayOfWeek function so that it works for dates before the 21st century. Note that, in general, a year is a leap year if it is divisible by 4, unless it is also divisible by 100. A year which is divisible by 100 is only a leap year if it is also divisible by 400. Thus, the following years are not leap years:

 

1300, 1400, 1500, 1700, 1800, 1900

 

because they are divisible by 100 but not by 400, but the following years are leap years:

 

1200, 1600, 2000

 

because they are divisible by both 100 and 400.

 

Use your extended dayOfWeek function to find out what day of the week you were born on.