如何在计算日期时正确处理Fin n和Integer?

时间:2017-04-11 17:24:42

标签: date dependent-type idris

在我探索伊德里斯的旅程中,我正试图在一个惯用的"中写一个小日期处理模块。办法。这是我到目前为止所拥有的。

首先,我有一些基本类型来表示日,月和年:

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> 的非全局性类型。

1 个答案:

答案 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