这并非不可能,只是很丑陋。我们需要自然数。
data Nat = Z | S Nat
以及有界自然数
data Bounded (n :: Nat) where
BZ :: Bounded n
BS :: Bounded n -> Bounded (S n)
那么您的函数应该类似于:
succ :: Bounded n -> Bounded n
succ bn = fromMaybe BZ $ go bn where
go :: Bounded n -> Maybe (Bounded n)
go = ...
在
go
中,我们需要:
- 如果
n
为Z
(即如果Bounded
达到了其最大值并溢出),则将BZ
映射为Nothing
- 如果
n
不是Z
(即如果Bounded
没有达到其最大值),则将BZ
映射为Just (BS BZ)
- 对于
BS
的情况,递归调用go
然而问题在于,无法在值级别上获取
n
。Haskell并不依赖于此。通常的方法是使用
singletons。手动编写如下:
data Natty (n :: Nat) where
Zy :: Natty Z
Sy :: Natty n -> Natty (S n)
class NATTY (n :: Nat) where
natty :: Natty n
instance NATTY Z where
natty = Zy
instance NATTY n => NATTY (S n) where
natty = Sy natty
现在我们可以在 go
中使用 Bounded n
来获取 n
的值级表示:
succ :: NATTY n => Bounded n -> Bounded n
succ bn = fromMaybe BZ $ go natty bn where
go :: Natty n -> Bounded n -> Maybe (Bounded n)
go Zy BZ = Nothing
go (Sy ny) BZ = Just (BS BZ)
go (Sy ny) (BS bn) = BS <$> go ny bn
而 NATTY
类型类用于自动推断此值。
一些测试:
instance Eq (Bounded n) where
BZ == BZ = True
BS bn == BS bm = bn == bm
_ == _ = False
zero :: Bounded (S (S Z))
zero = BZ
one :: Bounded (S (S Z))
one = BS BZ
two :: Bounded (S (S Z))
two = BS (BS BZ)
main = do
print $ succ zero == zero
print $ succ zero == one
print $ succ one == two
print $ succ two == zero
以下是需要翻译的内容:
代码。
使用singletons库,我们可以定义succ
如下:
$(singletons [d| data Nat = Z | S Nat deriving (Eq, Show) |])
data Bounded n where
BZ :: Bounded n
BS :: Bounded n -> Bounded (S n)
succ :: SingI n => Bounded n -> Bounded n
succ bn = fromMaybe BZ $ go sing bn where
go :: Sing n -> Bounded n -> Maybe (Bounded n)
go SZ BZ = Nothing
go (SS ny) BZ = Just (BS BZ)
go (SS ny) (BS bn) = BS <$> go ny bn
关于将运行时内容提升到类型级别,有两种方法:
CPS和
存在类型。
succ n = 0
对于某个未知的编译时值n
成立。它将用作计数器,我希望能够无惧地增加它,而不必检查是否超出范围。我不喜欢在值内部存储边界,因为它实际上是整个类型的属性。尽管我知道这在Haskell中无法完成,但我会看一下有限集合的事情。谢谢。 - Sventimir