我正在寻找这对功能
previous :: (Enum a, Bounded a) => a -> a
next :: (Enum a, Bounded a) => a -> a
如果结果值受边界限制,则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
如果我要添加Eq a => a
约束,这将很容易,如similar questions中所述。
但这种约束似乎是不必要的,因为Bounded 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
约束?
答案 0 :(得分:5)
Enum
类定义了两个函数
toEnum :: Enum a => Int -> a
fromEnum :: Enum a => a -> Int
如果Enum
的范围符合Enum
的大小,则可以使用这些函数将Int
与另一个函数进行比较。如果您愿意接受这种轻微限制,可以按如下方式编写函数。
{-# 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
这些功能有几个假设;给定(Enum a, Bounded a)
时,a
的每个值都介于minBound
和maxBound
之间,包括{to/from}Enum
保留此排序,与排序有关整数。这对于简单的枚举是正确的:
>import Control.Arrow
>map (next &&& previous) [A,B,C]
[(B,C),(C,A),(A,B)]
答案 1 :(得分:4)
有什么问题?如果您也可以添加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
;因为如果你只想知道列表中是否包含一个元素,那么计算列表的长度可能效率非常低。
答案 2 :(得分:2)
看起来,对于某些类型类实例,它只是像您对示例所做的那样定义函数:
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
,因为从根本上说必须有一些比较的概念(为了比较当前值的界限) ,如果不能轻易列举该类型的完整空间。