计算日期时如何正确处理 Fin n 和 Integer?
How to properly handle Fin n and Integer when computing dates?
在探索 Idris 的过程中,我尝试以 "idiomatic" 的方式编写一个小型日期处理模块。这是我目前所拥有的。
首先我有一些基本类型来表示日、月和年:
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 的 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
的非总体性 [=23] =]类型。
好吧,这不是那么简单,因为 Idris 需要您在每一步都提供证明,尤其是在您使用依赖类型的情况下。所有的基本思路都已经写在这个问题中了:
Is there a way to define a consistent date in a dependent type language?
我会评论你的实现并将答案中的代码(可能是 Agda)翻译成 Idris。我也做了一些调整以使您的代码完整。
首先,Month
可以写得简单一点:
data Month = January
| February
| March
我不会写全部12个月,这只是一个例子。
其次,Year
类型应该存储 Nat
而不是 Integer
,因为大多数使用 Integer
的函数都不是全部的。这个比较好:
data Year : Type where
Y : Nat -> Year
它有助于 isLeapYear
检查总数:
isLeapYear : Year -> Bool
isLeapYear (Y y) = check4 && check100 || check400
where
check4 : Bool
check4 = modNatNZ y 4 SIsNotZ == 0
check100 : Bool
check100 = modNatNZ y 100 SIsNotZ /= 0
check400 : Bool
check400 = modNatNZ y 400 SIsNotZ == 0
接下来,不好把Day
做成Fin 32
。最好具体指定每个月的天数。
daysInMonth : Month -> Year -> Nat
daysInMonth January _ = 31
daysInMonth February year = if isLeapYear year then 29 else 28
daysInMonth March _ = 31
Day : Month -> Year -> Type
Day m y = Fin (daysInMonth m y)
你应该稍微调整一下你的 Date
记录:
record Date where
constructor MkDate
year : Year
month : Month
day : Day month year
嗯,现在大约 addDays
。当您使用依赖类型时,此函数实际上非常复杂。正如您正确注意到的那样,您有几种情况。例如:sum 适合当月,sum 到下个月,sum 跳过几个月,sum 到年。每个这样的案例都需要证据。如果您想确保总和适合当月,您应该提供该事实的证明。
在开始编写代码之前,我想警告您,即使是编写非类型化版本的日期库也是 increadibly hard。此外,我想没有人还没有尝试过使用具有依赖类型的某种语言来实现功能齐全的版本。所以我的解决方案可能远非最佳。但至少应该让您了解自己做错了什么。
然后你可以开始写一些像这样的函数:
daysMax : (d: Date) -> Nat
daysMax (MkDate y m _) = daysInMonth m y
addDaysSameMonth : (d : Date)
-> (n : Nat)
-> Prelude.Nat.LT (finToNat (day d) + n) (daysMax d)
-> Date
addDaysSameMonth d n x = ?addDaysSameMonth_rhs
嗯,根据 Data.Fin
module 的当前状态,Fin
的数值运算非常有限。因此,即使添加两个 Nat
并使用给定的证据将它们转换为 Fin
,您也可能会遇到困难。再一次,写这样的东西比看起来更难:)
这里是完整的代码草图:http://lpaste.net/3314444314070745088
在探索 Idris 的过程中,我尝试以 "idiomatic" 的方式编写一个小型日期处理模块。这是我目前所拥有的。
首先我有一些基本类型来表示日、月和年:
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 的 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
的非总体性 [=23] =]类型。
好吧,这不是那么简单,因为 Idris 需要您在每一步都提供证明,尤其是在您使用依赖类型的情况下。所有的基本思路都已经写在这个问题中了:
Is there a way to define a consistent date in a dependent type language?
我会评论你的实现并将答案中的代码(可能是 Agda)翻译成 Idris。我也做了一些调整以使您的代码完整。
首先,Month
可以写得简单一点:
data Month = January
| February
| March
我不会写全部12个月,这只是一个例子。
其次,Year
类型应该存储 Nat
而不是 Integer
,因为大多数使用 Integer
的函数都不是全部的。这个比较好:
data Year : Type where
Y : Nat -> Year
它有助于 isLeapYear
检查总数:
isLeapYear : Year -> Bool
isLeapYear (Y y) = check4 && check100 || check400
where
check4 : Bool
check4 = modNatNZ y 4 SIsNotZ == 0
check100 : Bool
check100 = modNatNZ y 100 SIsNotZ /= 0
check400 : Bool
check400 = modNatNZ y 400 SIsNotZ == 0
接下来,不好把Day
做成Fin 32
。最好具体指定每个月的天数。
daysInMonth : Month -> Year -> Nat
daysInMonth January _ = 31
daysInMonth February year = if isLeapYear year then 29 else 28
daysInMonth March _ = 31
Day : Month -> Year -> Type
Day m y = Fin (daysInMonth m y)
你应该稍微调整一下你的 Date
记录:
record Date where
constructor MkDate
year : Year
month : Month
day : Day month year
嗯,现在大约 addDays
。当您使用依赖类型时,此函数实际上非常复杂。正如您正确注意到的那样,您有几种情况。例如:sum 适合当月,sum 到下个月,sum 跳过几个月,sum 到年。每个这样的案例都需要证据。如果您想确保总和适合当月,您应该提供该事实的证明。
在开始编写代码之前,我想警告您,即使是编写非类型化版本的日期库也是 increadibly hard。此外,我想没有人还没有尝试过使用具有依赖类型的某种语言来实现功能齐全的版本。所以我的解决方案可能远非最佳。但至少应该让您了解自己做错了什么。
然后你可以开始写一些像这样的函数:
daysMax : (d: Date) -> Nat
daysMax (MkDate y m _) = daysInMonth m y
addDaysSameMonth : (d : Date)
-> (n : Nat)
-> Prelude.Nat.LT (finToNat (day d) + n) (daysMax d)
-> Date
addDaysSameMonth d n x = ?addDaysSameMonth_rhs
嗯,根据 Data.Fin
module 的当前状态,Fin
的数值运算非常有限。因此,即使添加两个 Nat
并使用给定的证据将它们转换为 Fin
,您也可能会遇到困难。再一次,写这样的东西比看起来更难:)
这里是完整的代码草图:http://lpaste.net/3314444314070745088