In my journey exploring Idris, I am trying to write a small dates-handling module in an "idiomatic" way. Here is what I have so far.
First I have some basic types to represent days, months and years:
module Date
import Data.Fin
Day : Type
Day = Fin 32
data Month : Type where
January : Month
February : Month
....
toNat : Month -> Nat
toNat January = 1
toNat February = 2
...
data Year : Type where
Y : Integer -> Year
record Date where
constructor MkDate
day : Day
month : Month
year : Year
I would like to implement a function addDays
to add some number of days to a Date
. To this end I defined the following auxiliary functions:
isLeapYear : Year -> Bool
isLeapYear (Y y) =
(((y `mod` 4) == 0) && ((y `mod` 100) /= 0)) || ((y `mod` 400) == 0)
daysInMonth : Month -> Year -> Day
daysInMonth January _ = 31
daysInMonth February year = if isLeapYear year then 29 else 28
daysInMonth March _ = 31
...
And finally try to define addDays
as :
addDays : Date -> Integer -> Date
addDays (MkDate d m y) days =
let maxDays = daysInMonth m y
shiftedDays = finToInteger d + days
in case integerToFin shiftedDays (finToNat maxDays) of
Nothing => ?hole_1
Just x => MkDate x m y
I am stuck with the very basic case where the added number of days fit in the current month's duration. Here is the output of compiler:
When checking right hand side of Date.case block in addDays at Date.idr:92:11 with expected type Date
When checking argument day to constructor Date.MkDate:
Type mismatch between
Fin (finToNat maxDays) (Type of x)
and
Day (Expected type)
Specifically:
Type mismatch between
finToNat maxDays
and
32
This is puzzling because the type of maxDays
should be obviously Day
which is simply Fin 32
.
I suspect this might be related to non-totality of daysInMonth
which stems from non-totality of isLeapYear
which itself comes from non-totality of mod
for Integer
type.