在我探索伊德里斯的旅程中,我试图以“惯用”的方式编写一个小日期处理模块 . 这是我到目前为止所拥有的 .
首先,我有一些基本类型来表示日,月和年:
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
我想实现一个函数 addDays
来为 Date
添加一些天数 . 为此我定义了以下辅助功能:
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
...
最后尝试将 addDays
定义为:
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
我被困在最基本的情况下,增加的天数适合当前月份的持续时间 . 这是编译器的输出:
在Date.idr:92:11中使用期望类型Date检查addDays中Date.case块的右侧时
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
这很令人费解,因为 maxDays
的类型显然应该是 Day
,这简直就是 Fin 32
.
我怀疑这可能与 daysInMonth
的非整体性有关,这可能源于 isLeapYear
的非整体性,而 isLeapYear
本身来自 mod
类型的非全部性 .
1 回答
嗯,这并不是那么微不足道,因为Idris需要你在每一步都得到证明,特别是如果你使用的是依赖类型 . 所有基本想法都已写在这个问题中:
Is there a way to define a consistent date in a dependent type language?
我可能是阿格达(Agda)给伊德里斯 . 此外,我做了一些调整,以使您的代码总和 .
首先,
Month
可以写得更简单一些:我不会写所有12个月,这只是一个例子 .
其次,
Year
类型应该存储Nat
而不是Integer
,因为大多数使用Integer
的函数都不是全部 . 这个更好:总共
isLeapYear
检查有助于:接下来,将
Day
设为Fin 32
并不好 . 最好具体指定每个月的日期编号 .你应该稍微调整一下
Date
记录:那么,现在约
addDays
. 当您使用依赖类型时,此函数实际上非常复杂 . 正如您所注意到的,您有几种情况 . 例如:总和适合当月,总和下个月,总和跳过几个月,总和去年 . 每个这样的案件都需要证明 . 如果您想确保总和适合当月,您应该提供该事实的证明 .在我转到代码之前,我想警告你,甚至编写日期库的非类型版本是increadibly hard . 而且,我想没有人会做错了 .
然后你可以开始编写这样的函数:
那么,根据Data.Fin module的当前状态,
Fin
的数值运算非常有限 . 所以你甚至可能很难添加两个Nat
并使用给定的证明将它们转换为Fin
. 再次,写这样的东西比看起来更难:)以下是代码的完整草图:http://lpaste.net/3314444314070745088