在我探索伊德里斯的旅程中,我正试图在一个惯用的"中写一个小日期处理模块。办法。这是我到目前为止所拥有的。
首先,我有一些基本类型来表示日,月和年:
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
来自于mod
的非整体性,Integer
本身来自<properties>
<scala.version>2.11.8</scala.version>
<scala.binary.version>2.11</scala.binary.version>
<lagom.version>1.3.1</lagom.version>
<macwire.version>2.2.5</macwire.version>
.....
</properties>
<dependencies>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-server_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.play</groupId>
<artifactId>play-netty-server_${scala.binary.version}</artifactId>
<version>${play.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-api_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-persistence_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-logback_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-testkit_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-integration-client_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-pubsub_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-persistence-cassandra_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-dev-mode_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
</dependency>
<dependency>
<groupId>com.softwaremill.macwire</groupId>
<artifactId>macros_${scala.binary.version}</artifactId>
<version>${macwire.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-scala_${scala.binary.version}</artifactId>
<version>2.8.7</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_${scala.binary.version}</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.holdenkarau</groupId>
<artifactId>spark-testing-base_2.10</artifactId>
<version>2.0.0_0.4.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<plugin>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-maven-plugin</artifactId>
<version>${lagom.version}</version>
<configuration>
<lagomService>true</lagomService>
<unmanagedServices>
<cas_native>http://ip-address:9042</cas_native>
</unmanagedServices>
<cassandraEnabled>false</cassandraEnabled>
<kafkaAddress>ip-address:9092</kafkaAddress>
<kafkaEnabled>false</kafkaEnabled>
</configuration>
</plugin>
的非全局性类型。
答案 0 :(得分:1)
嗯,这不是那么微不足道,因为 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
。当您使用依赖类型时,此函数实际上非常复杂。正如您所注意到的,您有几种情况。例如:总和适合当月,总和下个月,总和跳过几个月,总和去年。每个这样的案件都需要证明。如果你想确保总和适合当月,你应该提供这个事实的证据。
在转到代码之前,我想警告你,甚至编写日期库的非类型版本都是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