为了在外部保存用户帐户权限(例如在DB中),我想表示枚举的元素列表,其中派生Enum
实例为Int
。
数字的每一位都被视为一个标志(或布尔),表示列表中是否存在第i个元素
用不同的词语表示 - 每个2的幂表示一个元素,这些幂的总和表示唯一元素列表。
示例:
data Permissions = IsAllowedToLogin -- 1
| IsModerator -- 2
| IsAdmin -- 4
deriving (Bounded, Enum, Eq, Show)
enumsToInt [IsAllowedToLogin, IsAdmin] == 1 + 4 == 5
intToEnums 3 == intToEnums (1 + 2) == [IsAllowedToLogin, IsModerator]
将这样的列表转换为Int
的函数很容易编写:
enumsToInt :: (Enum a, Eq a) => [a] -> Int
enumsToInt = foldr (\p acc -> acc + 2 ^ fromEnum p) 0 . nub
请注意,接受的答案包含更有效的实施。
真正困扰我的是倒车功能。我可以想象它应该有这种类型:
intToEnums :: (Bounded a, Enum a) => Int -> [a]
intToEnums = undefined -- What I'm asking about
我该如何解决这个问题?
答案 0 :(得分:10)
以下是一个完整的解决方案。它应该表现更好,因为它的实现基于按位而不是算术运算,这是一种更有效的方法。解决方案也尽力概括。
{-# LANGUAGE DefaultSignatures #-}
import Data.Bits
import Control.Monad
data Permission = IsAllowedToLogin -- 1
| IsModerator -- 2
| IsAdmin -- 4
deriving (Bounded, Enum, Eq, Show)
class ToBitMask a where
toBitMask :: a -> Int
-- | Using a DefaultSignatures extension to declare a default signature with
-- an `Enum` constraint without affecting the constraints of the class itself.
default toBitMask :: Enum a => a -> Int
toBitMask = shiftL 1 . fromEnum
instance ToBitMask Permission
instance ( ToBitMask a ) => ToBitMask [a] where
toBitMask = foldr (.|.) 0 . map toBitMask
-- | Not making this a typeclass, since it already generalizes over all
-- imaginable instances with help of `MonadPlus`.
fromBitMask ::
( MonadPlus m, Enum a, Bounded a, ToBitMask a ) =>
Int -> m a
fromBitMask bm = msum $ map asInBM $ enumFrom minBound where
asInBM a = if isInBitMask bm a then return a else mzero
isInBitMask :: ( ToBitMask a ) => Int -> a -> Bool
isInBitMask bm a = let aBM = toBitMask a in aBM == aBM .&. bm
使用以下
运行它main = do
print (fromBitMask 0 :: [Permission])
print (fromBitMask 1 :: [Permission])
print (fromBitMask 2 :: [Permission])
print (fromBitMask 3 :: [Permission])
print (fromBitMask 4 :: [Permission])
print (fromBitMask 5 :: [Permission])
print (fromBitMask 6 :: [Permission])
print (fromBitMask 7 :: [Permission])
print (fromBitMask 0 :: Maybe Permission)
print (fromBitMask 1 :: Maybe Permission)
print (fromBitMask 2 :: Maybe Permission)
print (fromBitMask 4 :: Maybe Permission)
输出
[]
[IsAllowedToLogin]
[IsModerator]
[IsAllowedToLogin,IsModerator]
[IsAdmin]
[IsAllowedToLogin,IsAdmin]
[IsModerator,IsAdmin]
[IsAllowedToLogin,IsModerator,IsAdmin]
Nothing
Just IsAllowedToLogin
Just IsModerator
Just IsAdmin
答案 1 :(得分:4)
我确信hackage上有一些东西可以做到这一点,但它很简单,可以使用the Data.Bits
module手动滚动自己。
您可以将enumsToInt
简化为类似foldl' (.|.) . map (bit . fromEnum)
的内容,即转换为整数索引然后转换为单个位,然后按位OR折叠。如果不出意外,这可以避免担心删除重复项。
对于intToEnums
,没有什么不方便的,但是为了快速解决方案,你可以做filter (testBit foo . fromEnum) [minBound .. maxBound]
之类的事情。这当然仅适用于Bounded
类型,并且假设枚举没有比外部类型具有更多值的值,并且fromEnum
使用从0开始的连续整数,但它听起来像你是无论如何,从这里作为一个前提开始。
答案 2 :(得分:2)
EnumSet可能就是你想要的。它甚至具有intToEnums
功能(虽然它似乎只与我尝试的类型的T Integer a
一致 - 特别是T Int Char
给出意想不到的结果)并且不会被期望重新创建序列化/反序列化后的重复条目(假定它是一组),而列表可能带有该期望。