以Int为单位表示枚举列表

Jak*_*kub 5 enums haskell bit-manipulation

为了在外部保存用户帐户权限(例如在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]
Run Code Online (Sandbox Code Playgroud)

将这样的列表转换Int为a的函数很容易编写:

enumsToInt :: (Enum a, Eq a) => [a] -> Int
enumsToInt = foldr (\p acc -> acc + 2 ^ fromEnum p) 0 . nub
Run Code Online (Sandbox Code Playgroud)

请注意,接受的答案包含更有效的实现.

让我感到困扰的是倒车功能.我可以想象它应该有这种类型:

intToEnums :: (Bounded a, Enum a) => Int -> [a]
intToEnums = undefined               -- What I'm asking about
Run Code Online (Sandbox Code Playgroud)

我该如何处理这个问题?

Nik*_*kov 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
Run Code Online (Sandbox Code Playgroud)

使用以下命令运行它

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)
Run Code Online (Sandbox Code Playgroud)

输出

[]
[IsAllowedToLogin]
[IsModerator]
[IsAllowedToLogin,IsModerator]
[IsAdmin]
[IsAllowedToLogin,IsAdmin]
[IsModerator,IsAdmin]
[IsAllowedToLogin,IsModerator,IsAdmin]
Nothing
Just IsAllowedToLogin
Just IsModerator
Just IsAdmin
Run Code Online (Sandbox Code Playgroud)