在Idris中,如何将1添加到Fin中,直到" max"到达

时间:2016-07-10 18:50:14

标签: enums dependent-type idris

我有一个数据类型,它在构造函数中取一个数字,这个数字必须介于1到5之间(表示为0..4):

import Data.Fin
data Stars = MkStars (Fin 5)

我想创建一个向现有star添加一个的函数,如果它已经是5 stars,则不会做任何事情

我试过

addOneStar: Stars -> Stars
addOneStar (MkStars FZ) = MkStars (FS FZ)
addOneStar (MkStars (FS x)) = if x < 3 
                              then MkStars (FS (FS x)) 
                              else MkStars (FS x)

但它没有编译错误:

Type mismatch between
            Fin 4 (Type of x)
    and
            Fin 3 (Expected type)

    Specifically:
            Type mismatch between
                    1
            and
                    0

有人可以向我解释为什么会出现这个错误? 以及如何解决它?

4 个答案:

答案 0 :(得分:4)

Cactus' answer解释了问题并给出了一个可能的解决方案。既然我们在Idris中有依赖类型,我至少会宣传一种利用它的解决方案的可能性,因此可以被视为更惯用:

nextFin : (m : Fin (S n)) -> {auto p : (toNat m) `LT` n} -> Fin (S n)
nextFin {n = Z} FZ {p} = absurd $ succNotLTEzero p
nextFin {n = Z} (FS FZ) impossible
nextFin {n = S k} FZ = FS FZ
nextFin {n = S k} (FS l) {p} = let p = fromLteSucc p in
  FS $ nextFin l

此函数将参数视为给定Fin不是最后一个的证明。通过这种方式,您可以确定类型检查程序的级别,该程序甚至不会尝试为您提供nextFin

如果您不想这样,而是类似于引用的答案,您也可以strengthenwith rules一起使用以避免大多数情况:

nextFin : Fin (S n) -> Maybe $ Fin (S n)
nextFin m with (strengthen m)
  | Left k = Nothing
  | Right k = Just $ FS k

答案 1 :(得分:3)

构造函数FS的类型是FS : Fin n -> Fin (S n),所以如果你有x : Fin 5,即使你知道它小于3 : Fin 5,它的类型仍然是Fin 5 1}},因此您无法将其传递给FS并获得另一个Fin 5;你会得到一个Fin 6

您可以编写一个函数nextFin : Fin n -> Maybe (Fin n),为最大的Nothing返回Fin;但该函数必须重建新的Fin,它不能只在最顶层应用FS。我们的想法是使用FZ : Fin n具有或不具有后继的事实,具体取决于n是1还是更大;而FS k的继任者是kFS的继承者:

import Data.Fin

total nextFin : Fin n -> Maybe (Fin n)
nextFin {n = Z}         k      = absurd k
nextFin {n = (S Z)}     _      = Nothing
nextFin {n = (S (S n))} FZ     = Just (FS FZ)
nextFin {n = (S (S n))} (FS k) = map FS $ nextFin k

答案 2 :(得分:1)

Fin实现了Enum接口,为后继函数succ提供了所需的语义,这使得addOneStar的实现变得微不足道:

addOneStar: Stars -> Stars
addOneStar (MkStars s) = MkStars $ succ s

答案 3 :(得分:0)

其他答案直接解决了您的问题。我在这里提供另类设计。

你提到你的号码必须在1到5之间。(看起来你正在构建某种电影评级系统?)而不是间接地将其表示为0到4之间的有界自然数,为什么不呢?只是直接枚举少数允许的案例?你不需要依赖类型;以下是有效的Haskell 98。

data Stars = OneStar
           | TwoStars
           | ThreeStars
           | FourStars
           | FiveStars
           deriving (Eq, Ord, Show, Read, Bounded, Enum)

addOneStar FiveStars = FiveStars
addOneStar s = succ s