我有一个数据类型,它在构造函数中取一个数字,这个数字必须介于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
有人可以向我解释为什么会出现这个错误? 以及如何解决它?
答案 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
。
如果您不想这样,而是类似于引用的答案,您也可以strengthen
与with
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
的继任者是k
中FS
的继承者:
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