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.