Haskell 循环一个值(枚举a,有界a)=>;A.
我正在寻找这对函数Haskell 循环一个值(枚举a,有界a)=>;A.,haskell,enums,Haskell,Enums,我正在寻找这对函数 previous :: (Enum a, Bounded a) => a -> a next :: (Enum a, Bounded a) => a -> a toEnum :: Enum a => Int -> a fromEnum :: Enum a => a -> Int 如果结果值受边界约束,则previous为pred::Enum a=>a->a,否则循环到另一侧(与next和succ对称) 范例
previous :: (Enum a, Bounded a) => a -> a
next :: (Enum a, Bounded a) => a -> a
toEnum :: Enum a => Int -> a
fromEnum :: Enum a => a -> Int
如果结果值受边界约束,则previous
为pred::Enum a=>a->a
,否则循环到另一侧(与next
和succ
对称)
范例
data X = A | B | C deriving (Bounded, Enum)
next A = B
next B = C
next C = A
如果我添加eqa=>a
约束,这将很容易,如本文中所讨论的。
但这个约束似乎没有必要,因为有界a=>a
类型在我看来应该能够判断它是否在有界范围内。succ
和pred
函数本身对此有某种控制,但我不希望在我的纯代码中使用异常:
Prelude> succ (maxBound :: Int)
*** Exception: Prelude.Enum.succ{Int}: tried to take `succ' of maxBound
succ
/pred
如何在不要求Eq a=>a
(甚至Bounded a=>a
)的情况下对边界进行测试?我可以在自己的函数中复制这种行为吗?或者,是否有其他方法可以编写具有此行为的函数,而不需要Eq
约束?对于某些typeclass实例,它只是像您在示例中所做的那样定义函数:
instance Enum Ordering where
succ LT = EQ
succ EQ = GT
succ GT = error "Prelude.Enum.Ordering.succ: bad argument"
pred GT = EQ
pred EQ = LT
pred LT = error "Prelude.Enum.Ordering.pred: bad argument"
否则,正如您所说,您很可能需要考虑
Eq
,因为基本上必须有一些比较的概念(为了将边界与当前值进行比较),如果类型的完整空间无法轻松枚举。Enum类定义了两个函数
previous :: (Enum a, Bounded a) => a -> a
next :: (Enum a, Bounded a) => a -> a
toEnum :: Enum a => Int -> a
fromEnum :: Enum a => a -> Int
如果Enum
的范围符合Int
的大小,则可以使用这些函数将一个Enum
与另一个进行比较。如果您愿意接受这个小小的限制,您可以按如下方式编写函数
{-# LANGUAGE ScopedTypeVariables #-}
previous :: forall a . (Enum a, Bounded a) => a -> a
previous x | from x > from minBound = pred x
| otherwise = maxBound where from :: a -> Int; from = fromEnum
next :: forall a . (Enum a, Bounded a) => a -> a
next x | from x < from maxBound = succ x
| otherwise = minBound where from :: a -> Int; from = fromEnum
有什么问题吗?如果您也可以添加
Eq
类,则很容易:
module CycleEnum where
data XEnum = A1 | A2 | A3 deriving (Bounded, Enum, Eq, Show)
cyclePrev :: (Eq a, Enum a, Bounded a) => a -> a
cyclePrev x = if x == minBound then maxBound else pred x
cycleNext :: (Eq a, Enum a, Bounded a) => a -> a
cycleNext x = if x == maxBound then minBound else succ x
如果没有Eq
,它会稍微复杂一些
module CycleEnum where
data XEnum = A1 | A2 | A3 deriving (Bounded, Enum, Show)
isSingle :: [a] -> Bool
isSingle [x] = True
isSingle _ = False
isLastElement :: Enum a => a -> Bool
isLastElement x = isSingle $ enumFrom x
isFirstElement :: (Enum a, Bounded a) => a -> Bool
isFirstElement x = isSingle $ enumFromTo minBound x
cyclePrev :: (Enum a, Bounded a) => a -> a
cyclePrev x = if isFirstElement x then maxBound else pred x
cycleNext :: (Enum a, Bounded a) => a -> a
cycleNext x = if isLastElement x then minBound else succ x
请注意,我使用模式匹配编写了
isSingle
,而不是调用length
;因为如果您只想知道列表是否只包含一个元素,那么计算列表的长度可能会非常低效。suc
是由一些可以确定它是否有效的实现提供的。如果没有(有界a,等式a)=>a
,如何计算列表的长度?这是一个“原语可以打破规则”的案例吗?我认为这只是因为succ
不需要任何边界或相等性测试;它只需要有一个函数a->a
。确切地说,它可以(毫无用处地)与id
函数相同。@Mephy:在给定类型的Enum
实例中,succ
是专门为该类型定义的,而不是一般的,并且可以利用该类型的任何信息。例如,这里为排序定义了succ
。任何关于排序的功能都是公平的游戏<代码>成功
通常可以使用,但每种类型只实现一次。@LouisWasserman我的疏忽,谢谢你的解释。这看起来很棒!这些限制是否总是被类型系统捕获?在GHCi上试用时,previous 10
会出现编译时间错误,而previous(10::Int)
工作正常。不,它们不是。如果Enum
的实例包含的元素多于Int
的大小,则from/toEnum
只需中断succ
和pred
可能仍然有效。previous 10
是一个有效的表达式,其类型为Num a,Enum a,Bounded a=>a
,但是,GHCI无法打印它,因为它不知道为a
选择哪种类型。通常它选择整数
,但这不是有界的
。如果您在GHCI会话中键入default(Int)
,那么它将尝试默认为Int
——因此之前的10个将默认为Int
,并且在没有类型签名的情况下工作。此解决方案显然与用户2407038的答案具有相同的限制(cycleNext 10
给出编译时间错误,cycleNext(10::Int)
工作正常),但不需要-XScopedTypeVariables
。感谢您的贡献。